diff --git a/wpilibc/src/main/native/cpp/LEDPattern.cpp b/wpilibc/src/main/native/cpp/LEDPattern.cpp index f2cc968adf..d06d1be526 100644 --- a/wpilibc/src/main/native/cpp/LEDPattern.cpp +++ b/wpilibc/src/main/native/cpp/LEDPattern.cpp @@ -263,7 +263,8 @@ LEDPattern LEDPattern::Steps( return Steps(std::span{steps.begin(), steps.end()}); } -LEDPattern LEDPattern::Gradient(std::span colors) { +LEDPattern LEDPattern::Gradient(GradientType type, + std::span colors) { if (colors.size() == 0) { // no colors specified return LEDPattern::Off(); @@ -273,11 +274,19 @@ LEDPattern LEDPattern::Gradient(std::span colors) { return LEDPattern::Solid(colors[0]); } - return LEDPattern{[colors = std::vector(colors.begin(), colors.end())]( + return LEDPattern{[type, colors = std::vector(colors.begin(), colors.end())]( auto data, auto writer) { size_t numSegments = colors.size(); auto bufLen = data.size(); - int ledsPerSegment = bufLen / numSegments; + int ledsPerSegment = 0; + switch (type) { + case kContinuous: + ledsPerSegment = bufLen / numSegments; + break; + case kDiscontinuous: + ledsPerSegment = (bufLen - 1) / (numSegments - 1); + break; + } for (size_t led = 0; led < bufLen; led++) { int colorIndex = (led / ledsPerSegment) % numSegments; @@ -295,8 +304,9 @@ LEDPattern LEDPattern::Gradient(std::span colors) { }}; } -LEDPattern LEDPattern::Gradient(std::initializer_list colors) { - return Gradient(std::span{colors.begin(), colors.end()}); +LEDPattern LEDPattern::Gradient(GradientType type, + std::initializer_list colors) { + return Gradient(type, std::span{colors.begin(), colors.end()}); } LEDPattern LEDPattern::Rainbow(int saturation, int value) { diff --git a/wpilibc/src/main/native/include/frc/LEDPattern.h b/wpilibc/src/main/native/include/frc/LEDPattern.h index 9a35032621..f2dcf28485 100644 --- a/wpilibc/src/main/native/include/frc/LEDPattern.h +++ b/wpilibc/src/main/native/include/frc/LEDPattern.h @@ -315,29 +315,52 @@ class LEDPattern { static LEDPattern Steps( std::initializer_list> steps); - /** - * Creates a pattern that displays a non-animated gradient of colors across - * the entire length of the LED strip. The gradient wraps around so the start - * and end of the strip are the same color, which allows the gradient to be - * modified with a scrolling effect with no discontinuities. Colors are evenly - * distributed along the full length of the LED strip. - * - * @param colors the colors to display in the gradient - * @return a motionless gradient pattern - */ - static LEDPattern Gradient(std::span colors); + /** Types of gradients. */ + enum GradientType { + /** + * A continuous gradient, where the gradient wraps around to allow for + * seamless scrolling effects. + */ + kContinuous, + /** + * A discontinuous gradient, where the first pixel is set to the first color + * of the gradient and the final pixel is set to the last color of the + * gradient. There is no wrapping effect, so scrolling effects will display + * an obvious seam. + */ + kDiscontinuous + }; /** * Creates a pattern that displays a non-animated gradient of colors across - * the entire length of the LED strip. The gradient wraps around so the start - * and end of the strip are the same color, which allows the gradient to be - * modified with a scrolling effect with no discontinuities. Colors are evenly - * distributed along the full length of the LED strip. + * the entire length of the LED strip. Colors are evenly distributed along the + * full length of the LED strip. The gradient type is configured with the + * {@code type} parameter, allowing the gradient to be either continuous (no + * seams, good for scrolling effects) or discontinuous (a clear seam is + * visible, but the gradient applies to the full length of the LED strip + * without needing to use some space for wrapping). * + * @param type the type of gradient (continuous or discontinuous) * @param colors the colors to display in the gradient * @return a motionless gradient pattern */ - static LEDPattern Gradient(std::initializer_list colors); + static LEDPattern Gradient(GradientType type, std::span colors); + + /** + * Creates a pattern that displays a non-animated gradient of colors across + * the entire length of the LED strip. Colors are evenly distributed along the + * full length of the LED strip. The gradient type is configured with the + * {@code type} parameter, allowing the gradient to be either continuous (no + * seams, good for scrolling effects) or discontinuous (a clear seam is + * visible, but the gradient applies to the full length of the LED strip + * without needing to use some space for wrapping). + * + * @param type the type of gradient (continuous or discontinuous) + * @param colors the colors to display in the gradient + * @return a motionless gradient pattern + */ + static LEDPattern Gradient(GradientType type, + std::initializer_list colors); /** * Creates an LED pattern that displays a rainbow across the color wheel. The diff --git a/wpilibc/src/test/native/cpp/LEDPatternTest.cpp b/wpilibc/src/test/native/cpp/LEDPatternTest.cpp index 5ea506c3c0..cd9e7d76b7 100644 --- a/wpilibc/src/test/native/cpp/LEDPatternTest.cpp +++ b/wpilibc/src/test/native/cpp/LEDPatternTest.cpp @@ -48,7 +48,8 @@ TEST(LEDPatternTest, SolidColor) { TEST(LEDPatternTest, EmptyGradientSetsToBlack) { std::array colors; - LEDPattern pattern = LEDPattern::Gradient(colors); + LEDPattern pattern = + LEDPattern::Gradient(LEDPattern::GradientType::kContinuous, colors); std::array buffer; pattern.ApplyTo(buffer); for (int i = 0; i < 5; i++) { @@ -58,7 +59,8 @@ TEST(LEDPatternTest, EmptyGradientSetsToBlack) { TEST(LEDPatternTest, SingleColorGradientSetsSolid) { std::array colors{Color::kYellow}; - LEDPattern pattern = LEDPattern::Gradient(colors); + LEDPattern pattern = + LEDPattern::Gradient(LEDPattern::GradientType::kContinuous, colors); std::array buffer; pattern.ApplyTo(buffer); for (int i = 0; i < 5; i++) { @@ -68,7 +70,8 @@ TEST(LEDPatternTest, SingleColorGradientSetsSolid) { TEST(LEDPatternTest, Gradient2Colors) { std::array colors{Color::kYellow, Color::kPurple}; - LEDPattern pattern = LEDPattern::Gradient(colors); + LEDPattern pattern = + LEDPattern::Gradient(LEDPattern::GradientType::kContinuous, colors); std::array buffer; pattern.ApplyTo(buffer); AssertIndexColor(buffer, 0, Color::kYellow); @@ -80,9 +83,21 @@ TEST(LEDPatternTest, Gradient2Colors) { AssertIndexColor(buffer, 98, Color::kYellow); } +TEST(LEDPatternTest, DiscontinuousGradient2Colors) { + std::array colors{Color::kYellow, Color::kPurple}; + LEDPattern pattern = + LEDPattern::Gradient(LEDPattern::GradientType::kDiscontinuous, colors); + std::array buffer; + pattern.ApplyTo(buffer); + AssertIndexColor(buffer, 0, Color::kYellow); + AssertIndexColor(buffer, 49, LerpColors(Color::kYellow, Color::kPurple, 0.5)); + AssertIndexColor(buffer, 98, Color::kPurple); +} + TEST(LEDPatternTest, Gradient3Colors) { std::array colors{Color::kYellow, Color::kPurple, Color::kWhite}; - LEDPattern pattern = LEDPattern::Gradient(colors); + LEDPattern pattern = + LEDPattern::Gradient(LEDPattern::GradientType::kContinuous, colors); std::array buffer; pattern.ApplyTo(buffer); @@ -99,6 +114,20 @@ TEST(LEDPatternTest, Gradient3Colors) { LerpColors(Color::kWhite, Color::kYellow, 32 / 33.0)); } +TEST(LEDPatternTest, DiscontinuousGradient3Colors) { + std::array colors{Color::kYellow, Color::kPurple, Color::kWhite}; + LEDPattern pattern = + LEDPattern::Gradient(LEDPattern::GradientType::kDiscontinuous, colors); + std::array buffer; + pattern.ApplyTo(buffer); + + AssertIndexColor(buffer, 0, Color::kYellow); + AssertIndexColor(buffer, 25, LerpColors(Color::kYellow, Color::kPurple, 0.5)); + AssertIndexColor(buffer, 50, Color::kPurple); + AssertIndexColor(buffer, 75, LerpColors(Color::kPurple, Color::kWhite, 0.5)); + AssertIndexColor(buffer, 100, Color::kWhite); +} + TEST(LEDPatternTest, EmptyStepsSetsToBlack) { std::array, 0> steps; LEDPattern pattern = LEDPattern::Steps(steps); diff --git a/wpilibj/src/main/java/edu/wpi/first/wpilibj/LEDPattern.java b/wpilibj/src/main/java/edu/wpi/first/wpilibj/LEDPattern.java index adabe89da8..5e2a65a0ea 100644 --- a/wpilibj/src/main/java/edu/wpi/first/wpilibj/LEDPattern.java +++ b/wpilibj/src/main/java/edu/wpi/first/wpilibj/LEDPattern.java @@ -569,16 +569,35 @@ public interface LEDPattern { }; } + /** Types of gradients. */ + enum GradientType { + /** + * A continuous gradient, where the gradient wraps around to allow for seamless scrolling + * effects. + */ + kContinuous, + + /** + * A discontinuous gradient, where the first pixel is set to the first color of the gradient and + * the final pixel is set to the last color of the gradient. There is no wrapping effect, so + * scrolling effects will display an obvious seam. + */ + kDiscontinuous + } + /** * Creates a pattern that displays a non-animated gradient of colors across the entire length of - * the LED strip. The gradient wraps around so the start and end of the strip are the same color, - * which allows the gradient to be modified with a scrolling effect with no discontinuities. - * Colors are evenly distributed along the full length of the LED strip. + * the LED strip. Colors are evenly distributed along the full length of the LED strip. The + * gradient type is configured with the {@code type} parameter, allowing the gradient to be either + * continuous (no seams, good for scrolling effects) or discontinuous (a clear seam is visible, + * but the gradient applies to the full length of the LED strip without needing to use some space + * for wrapping). * + * @param type the type of gradient (continuous or discontinuous) * @param colors the colors to display in the gradient * @return a motionless gradient pattern */ - static LEDPattern gradient(Color... colors) { + static LEDPattern gradient(GradientType type, Color... colors) { if (colors.length == 0) { // Nothing to display DriverStation.reportWarning("Creating a gradient with no colors!", false); @@ -595,7 +614,11 @@ public interface LEDPattern { return (reader, writer) -> { int bufLen = reader.getLength(); - int ledsPerSegment = bufLen / numSegments; + int ledsPerSegment = + switch (type) { + case kContinuous -> bufLen / numSegments; + case kDiscontinuous -> (bufLen - 1) / (numSegments - 1); + }; for (int led = 0; led < bufLen; led++) { int colorIndex = (led / ledsPerSegment) % numSegments; diff --git a/wpilibj/src/test/java/edu/wpi/first/wpilibj/LEDPatternTest.java b/wpilibj/src/test/java/edu/wpi/first/wpilibj/LEDPatternTest.java index ea4c768151..adb96cde57 100644 --- a/wpilibj/src/test/java/edu/wpi/first/wpilibj/LEDPatternTest.java +++ b/wpilibj/src/test/java/edu/wpi/first/wpilibj/LEDPatternTest.java @@ -11,6 +11,8 @@ import static edu.wpi.first.units.Units.Microseconds; import static edu.wpi.first.units.Units.Percent; import static edu.wpi.first.units.Units.Seconds; import static edu.wpi.first.units.Units.Value; +import static edu.wpi.first.wpilibj.LEDPattern.GradientType.kContinuous; +import static edu.wpi.first.wpilibj.LEDPattern.GradientType.kDiscontinuous; import static edu.wpi.first.wpilibj.util.Color.kBlack; import static edu.wpi.first.wpilibj.util.Color.kBlue; import static edu.wpi.first.wpilibj.util.Color.kLime; @@ -80,7 +82,7 @@ class LEDPatternTest { @Test void gradient0SetsToBlack() { - LEDPattern pattern = LEDPattern.gradient(); + LEDPattern pattern = LEDPattern.gradient(kContinuous); AddressableLEDBuffer buffer = new AddressableLEDBuffer(99); for (int i = 0; i < buffer.getLength(); i++) { buffer.setRGB(i, 127, 128, 129); @@ -95,7 +97,7 @@ class LEDPatternTest { @Test void gradient1SetsToSolid() { - LEDPattern pattern = LEDPattern.gradient(kYellow); + LEDPattern pattern = LEDPattern.gradient(kContinuous, kYellow); AddressableLEDBuffer buffer = new AddressableLEDBuffer(99); pattern.applyTo(buffer); @@ -106,8 +108,8 @@ class LEDPatternTest { } @Test - void gradient2Colors() { - LEDPattern pattern = LEDPattern.gradient(kYellow, kPurple); + void continuousGradient2Colors() { + LEDPattern pattern = LEDPattern.gradient(kContinuous, kYellow, kPurple); AddressableLEDBuffer buffer = new AddressableLEDBuffer(99); pattern.applyTo(buffer); @@ -119,9 +121,21 @@ class LEDPatternTest { assertColorEquals(kYellow, buffer.getLED(98)); } + @Test + void discontinuousGradient2Colors() { + LEDPattern pattern = LEDPattern.gradient(kDiscontinuous, kYellow, kPurple); + + AddressableLEDBuffer buffer = new AddressableLEDBuffer(99); + pattern.applyTo(buffer); + + assertColorEquals(kYellow, buffer.getLED(0)); + assertColorEquals(Color.lerpRGB(kYellow, kPurple, 0.5), buffer.getLED(49)); + assertColorEquals(kPurple, buffer.getLED(98)); + } + @Test void gradient3Colors() { - LEDPattern pattern = LEDPattern.gradient(kYellow, kPurple, kWhite); + LEDPattern pattern = LEDPattern.gradient(kContinuous, kYellow, kPurple, kWhite); AddressableLEDBuffer buffer = new AddressableLEDBuffer(99); pattern.applyTo(buffer); @@ -134,6 +148,19 @@ class LEDPatternTest { assertColorEquals(Color.lerpRGB(kWhite, kYellow, 32.0 / 33.0), buffer.getLED(98)); } + @Test + void discontinuousGradient3Colors() { + LEDPattern pattern = LEDPattern.gradient(kDiscontinuous, kYellow, kPurple, kWhite); + AddressableLEDBuffer buffer = new AddressableLEDBuffer(101); + pattern.applyTo(buffer); + + assertColorEquals(kYellow, buffer.getLED(0)); + assertColorEquals(Color.lerpRGB(kYellow, kPurple, 0.5), buffer.getLED(25)); + assertColorEquals(kPurple, buffer.getLED(50)); + assertColorEquals(Color.lerpRGB(kPurple, kWhite, 0.5), buffer.getLED(75)); + assertColorEquals(kWhite, buffer.getLED(100)); + } + @Test void step0SetsToBlack() { LEDPattern pattern = LEDPattern.steps(Map.of());