mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-19 00:41:43 +00:00
`Color::FromHSV` didn't match the Java `Color.fromHSV` in some saturated
edge cases, introducing an off-by-one error when the HSV color should
correspond complete saturation of one or two of the primary colors.
Example:
- Java: `Color.fromHSV(0, 255, 255) -> (255, 0, 0)`
- C++: `Color::FromHSV(0, 255, 255) -> (255, 1, 1)`
This also means the following methods are also transitively affected:
- `AddressableLED::LEDData::SetHSV`
- `LEDPattern::Rainbow`
This off-by-one error is introduced by a rounding error from the chroma
calculation, which was dividing by 256 rather than the appropriate
maximum value of 255 like in Java:
7ca35e5678/wpilibj/src/main/java/edu/wpi/first/wpilibj/util/Color.java (L176-L177)
Also port appropriate tests from Java to C++ to catch this bug.
I found this bug when I tried to port `AddressableLEDBuffer` to RobotPy.
Codex found the root cause :)
1143 lines
37 KiB
C++
1143 lines
37 KiB
C++
// Copyright (c) FIRST and other WPILib contributors.
|
|
// Open Source Software; you can modify and/or share it under the terms of
|
|
// the WPILib BSD license file in the root directory of this project.
|
|
|
|
#include "wpi/hardware/led/LEDPattern.hpp"
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
#include "wpi/math/util/MathUtil.hpp"
|
|
#include "wpi/util/MathExtras.hpp"
|
|
#include "wpi/util/timestamp.h"
|
|
|
|
namespace wpi {
|
|
|
|
static LEDPattern whiteYellowPurple{[](auto data, auto writer) {
|
|
for (size_t led = 0; led < data.size(); led++) {
|
|
switch (led % 3) {
|
|
case 0:
|
|
writer(led, wpi::util::Color::WHITE);
|
|
break;
|
|
case 1:
|
|
writer(led, wpi::util::Color::YELLOW);
|
|
break;
|
|
case 2:
|
|
writer(led, wpi::util::Color::PURPLE);
|
|
break;
|
|
}
|
|
}
|
|
}};
|
|
|
|
void AssertIndexColor(std::span<AddressableLED::LEDData> data, int index,
|
|
wpi::util::Color color);
|
|
wpi::util::Color LerpColors(wpi::util::Color a, wpi::util::Color b, double t);
|
|
|
|
TEST(LEDPatternTest, SolidColor) {
|
|
LEDPattern pattern = LEDPattern::Solid(wpi::util::Color::YELLOW);
|
|
std::array<AddressableLED::LEDData, 5> buffer;
|
|
|
|
// prefill
|
|
for (int i = 0; i < 5; i++) {
|
|
buffer[i].SetLED(wpi::util::Color::PURPLE);
|
|
}
|
|
|
|
pattern.ApplyTo(buffer);
|
|
for (int i = 0; i < 5; i++) {
|
|
AssertIndexColor(buffer, i, wpi::util::Color::YELLOW);
|
|
}
|
|
}
|
|
|
|
TEST(LEDPatternTest, EmptyGradientSetsToBlack) {
|
|
std::array<wpi::util::Color, 0> colors;
|
|
LEDPattern pattern =
|
|
LEDPattern::Gradient(LEDPattern::GradientType::CONTINUOUS, colors);
|
|
std::array<AddressableLED::LEDData, 5> buffer;
|
|
pattern.ApplyTo(buffer);
|
|
for (int i = 0; i < 5; i++) {
|
|
AssertIndexColor(buffer, i, wpi::util::Color::BLACK);
|
|
}
|
|
}
|
|
|
|
TEST(LEDPatternTest, SingleColorGradientSetsSolid) {
|
|
std::array<wpi::util::Color, 1> colors{wpi::util::Color::YELLOW};
|
|
LEDPattern pattern =
|
|
LEDPattern::Gradient(LEDPattern::GradientType::CONTINUOUS, colors);
|
|
std::array<AddressableLED::LEDData, 5> buffer;
|
|
pattern.ApplyTo(buffer);
|
|
for (int i = 0; i < 5; i++) {
|
|
AssertIndexColor(buffer, i, wpi::util::Color::YELLOW);
|
|
}
|
|
}
|
|
|
|
TEST(LEDPatternTest, Gradient2Colors) {
|
|
std::array<wpi::util::Color, 2> colors{wpi::util::Color::YELLOW,
|
|
wpi::util::Color::PURPLE};
|
|
LEDPattern pattern =
|
|
LEDPattern::Gradient(LEDPattern::GradientType::CONTINUOUS, colors);
|
|
std::array<AddressableLED::LEDData, 99> buffer;
|
|
pattern.ApplyTo(buffer);
|
|
AssertIndexColor(buffer, 0, wpi::util::Color::YELLOW);
|
|
AssertIndexColor(buffer, 25,
|
|
LerpColors(wpi::util::Color::YELLOW,
|
|
wpi::util::Color::PURPLE, 25 / 49.0));
|
|
AssertIndexColor(buffer, 49, wpi::util::Color::PURPLE);
|
|
AssertIndexColor(buffer, 74,
|
|
LerpColors(wpi::util::Color::PURPLE,
|
|
wpi::util::Color::YELLOW, 25 / 49.0));
|
|
AssertIndexColor(buffer, 98, wpi::util::Color::YELLOW);
|
|
}
|
|
|
|
TEST(LEDPatternTest, DiscontinuousGradient2Colors) {
|
|
std::array<wpi::util::Color, 2> colors{wpi::util::Color::YELLOW,
|
|
wpi::util::Color::PURPLE};
|
|
LEDPattern pattern =
|
|
LEDPattern::Gradient(LEDPattern::GradientType::DISCONTINUOUS, colors);
|
|
std::array<AddressableLED::LEDData, 99> buffer;
|
|
pattern.ApplyTo(buffer);
|
|
AssertIndexColor(buffer, 0, wpi::util::Color::YELLOW);
|
|
AssertIndexColor(
|
|
buffer, 49,
|
|
LerpColors(wpi::util::Color::YELLOW, wpi::util::Color::PURPLE, 0.5));
|
|
AssertIndexColor(buffer, 98, wpi::util::Color::PURPLE);
|
|
}
|
|
|
|
TEST(LEDPatternTest, Gradient3Colors) {
|
|
std::array<wpi::util::Color, 3> colors{wpi::util::Color::YELLOW,
|
|
wpi::util::Color::PURPLE,
|
|
wpi::util::Color::WHITE};
|
|
LEDPattern pattern =
|
|
LEDPattern::Gradient(LEDPattern::GradientType::CONTINUOUS, colors);
|
|
std::array<AddressableLED::LEDData, 99> buffer;
|
|
pattern.ApplyTo(buffer);
|
|
|
|
AssertIndexColor(buffer, 0, wpi::util::Color::YELLOW);
|
|
AssertIndexColor(buffer, 25,
|
|
LerpColors(wpi::util::Color::YELLOW,
|
|
wpi::util::Color::PURPLE, 25 / 33.0));
|
|
AssertIndexColor(buffer, 33, wpi::util::Color::PURPLE);
|
|
AssertIndexColor(
|
|
buffer, 58,
|
|
LerpColors(wpi::util::Color::PURPLE, wpi::util::Color::WHITE, 25 / 33.0));
|
|
AssertIndexColor(buffer, 66, wpi::util::Color::WHITE);
|
|
AssertIndexColor(
|
|
buffer, 91,
|
|
LerpColors(wpi::util::Color::WHITE, wpi::util::Color::YELLOW, 25 / 33.0));
|
|
AssertIndexColor(
|
|
buffer, 98,
|
|
LerpColors(wpi::util::Color::WHITE, wpi::util::Color::YELLOW, 32 / 33.0));
|
|
}
|
|
|
|
TEST(LEDPatternTest, DiscontinuousGradient3Colors) {
|
|
std::array<wpi::util::Color, 3> colors{wpi::util::Color::YELLOW,
|
|
wpi::util::Color::PURPLE,
|
|
wpi::util::Color::WHITE};
|
|
LEDPattern pattern =
|
|
LEDPattern::Gradient(LEDPattern::GradientType::DISCONTINUOUS, colors);
|
|
std::array<AddressableLED::LEDData, 101> buffer;
|
|
pattern.ApplyTo(buffer);
|
|
|
|
AssertIndexColor(buffer, 0, wpi::util::Color::YELLOW);
|
|
AssertIndexColor(
|
|
buffer, 25,
|
|
LerpColors(wpi::util::Color::YELLOW, wpi::util::Color::PURPLE, 0.5));
|
|
AssertIndexColor(buffer, 50, wpi::util::Color::PURPLE);
|
|
AssertIndexColor(
|
|
buffer, 75,
|
|
LerpColors(wpi::util::Color::PURPLE, wpi::util::Color::WHITE, 0.5));
|
|
AssertIndexColor(buffer, 100, wpi::util::Color::WHITE);
|
|
}
|
|
|
|
TEST(LEDPatternTest, EmptyStepsSetsToBlack) {
|
|
std::array<std::pair<double, wpi::util::Color>, 0> steps;
|
|
LEDPattern pattern = LEDPattern::Steps(steps);
|
|
std::array<AddressableLED::LEDData, 5> buffer;
|
|
|
|
// prefill
|
|
for (int i = 0; i < 5; i++) {
|
|
buffer[i].SetLED(wpi::util::Color::PURPLE);
|
|
}
|
|
|
|
pattern.ApplyTo(buffer);
|
|
|
|
for (int i = 0; i < 5; i++) {
|
|
AssertIndexColor(buffer, i, wpi::util::Color::BLACK);
|
|
}
|
|
}
|
|
|
|
TEST(LEDPatternTest, SingleStepSetsSolid) {
|
|
std::array<std::pair<double, wpi::util::Color>, 1> steps{
|
|
std::pair{0.0, wpi::util::Color::YELLOW}};
|
|
LEDPattern pattern = LEDPattern::Steps(steps);
|
|
std::array<AddressableLED::LEDData, 5> buffer;
|
|
|
|
pattern.ApplyTo(buffer);
|
|
|
|
for (int i = 0; i < 5; i++) {
|
|
AssertIndexColor(buffer, i, wpi::util::Color::YELLOW);
|
|
}
|
|
}
|
|
|
|
TEST(LEDPatternTest, SingleHalfStepSetsHalfOffHalfColor) {
|
|
std::array<std::pair<double, wpi::util::Color>, 1> steps{
|
|
std::pair{0.5, wpi::util::Color::YELLOW}};
|
|
LEDPattern pattern = LEDPattern::Steps(steps);
|
|
std::array<AddressableLED::LEDData, 99> buffer;
|
|
|
|
pattern.ApplyTo(buffer);
|
|
|
|
// [0, 48] should be black...
|
|
for (int i = 0; i < 49; i++) {
|
|
AssertIndexColor(buffer, i, wpi::util::Color::BLACK);
|
|
}
|
|
|
|
// ... and [49, <end>] should be the color that was set
|
|
for (int i = 49; i < 99; i++) {
|
|
AssertIndexColor(buffer, i, wpi::util::Color::YELLOW);
|
|
}
|
|
}
|
|
|
|
TEST(LEDPatternTest, ScrollRelativeForward) {
|
|
// A black to white gradient
|
|
LEDPattern pattern = LEDPattern{[=](auto data, auto writer) {
|
|
for (size_t led = 0; led < data.size(); led++) {
|
|
int ch = static_cast<int>(led % 256);
|
|
writer(led, wpi::util::Color{ch, ch, ch});
|
|
}
|
|
}};
|
|
std::array<AddressableLED::LEDData, 256> buffer;
|
|
|
|
// Scrolling at 1/256th of the buffer per second,
|
|
// or 1 individual diode per second
|
|
auto scroll =
|
|
pattern.ScrollAtRelativeVelocity(wpi::units::hertz_t{1 / 256.0});
|
|
|
|
static uint64_t now = 0ull;
|
|
WPI_SetNowImpl([] { return now; });
|
|
|
|
for (int time = 0; time < 500; time++) {
|
|
// convert time (seconds) to microseconds
|
|
now = time * 1000000ull;
|
|
|
|
scroll.ApplyTo(buffer);
|
|
|
|
for (size_t led = 0; led < buffer.size(); led++) {
|
|
SCOPED_TRACE(
|
|
fmt::format("LED {} of 256, run {} of 500", led + 1, time + 1));
|
|
// Base: [(0, 0, 0) (1, 1, 1) (2, 2, 2) (3, 3, 3) (4, 4, 4) ... (255, 255,
|
|
// 255)] Value for every channel should DECREASE by 1 in each timestep,
|
|
// wrapping around 0 and 255
|
|
|
|
// t=0, channel value = (0, 1, 2, ..., 254, 255)
|
|
// t=1, channel value = (255, 0, 1, ..., 253, 254)
|
|
// t=2, channel value = (254, 255, 0, ..., 252, 253)
|
|
// t=255, channel value = (1, 2, 3, ..., 255, 0)
|
|
// t=256, channel value = (0, 1, 2, ..., 254, 255)
|
|
int ch = wpi::math::FloorMod(static_cast<int>(led - time), 256);
|
|
AssertIndexColor(buffer, led, wpi::util::Color{ch, ch, ch});
|
|
}
|
|
}
|
|
|
|
WPI_SetNowImpl(nullptr); // cleanup
|
|
}
|
|
|
|
TEST(LEDPatternTest, ScrollRelativeBackward) {
|
|
// A black to white gradient
|
|
LEDPattern pattern = LEDPattern{[=](auto data, auto writer) {
|
|
for (size_t led = 0; led < data.size(); led++) {
|
|
int ch = static_cast<int>(led % 256);
|
|
writer(led, wpi::util::Color{ch, ch, ch});
|
|
}
|
|
}};
|
|
std::array<AddressableLED::LEDData, 256> buffer;
|
|
|
|
// Scrolling at 1/256th of the buffer per second,
|
|
// or 1 individual diode per second
|
|
auto scroll =
|
|
pattern.ScrollAtRelativeVelocity(wpi::units::hertz_t{-1 / 256.0});
|
|
|
|
static uint64_t now = 0ull;
|
|
WPI_SetNowImpl([] { return now; });
|
|
|
|
for (int time = 0; time < 500; time++) {
|
|
// convert time (seconds) to microseconds
|
|
now = time * 1000000ull;
|
|
|
|
scroll.ApplyTo(buffer);
|
|
|
|
for (size_t led = 0; led < buffer.size(); led++) {
|
|
SCOPED_TRACE(
|
|
fmt::format("LED {} of 256, run {} of 500", led + 1, time + 1));
|
|
// Base: [(0, 0, 0) (1, 1, 1) (2, 2, 2) (3, 3, 3) (4, 4, 4) ... (255, 255,
|
|
// 255)] Value for every channel should DECREASE by 1 in each timestep,
|
|
// wrapping around 0 and 255
|
|
|
|
// t=0, channel value = (0, 1, 2, ..., 254, 255)
|
|
// t=1, channel value = (255, 0, 1, ..., 253, 254)
|
|
// t=2, channel value = (254, 255, 0, ..., 252, 253)
|
|
// t=255, channel value = (1, 2, 3, ..., 255, 0)
|
|
// t=256, channel value = (0, 1, 2, ..., 254, 255)
|
|
int ch = wpi::math::FloorMod(static_cast<int>(led + time), 256);
|
|
AssertIndexColor(buffer, led, wpi::util::Color{ch, ch, ch});
|
|
}
|
|
}
|
|
|
|
WPI_SetNowImpl(nullptr); // cleanup
|
|
}
|
|
|
|
TEST(LEDPatternTest, ScrollAbsoluteForward) {
|
|
// A black to white gradient
|
|
LEDPattern pattern = LEDPattern{[](auto data, auto writer) {
|
|
for (size_t led = 0; led < data.size(); led++) {
|
|
int ch = static_cast<int>(led % 256);
|
|
writer(led, wpi::util::Color{ch, ch, ch});
|
|
}
|
|
}};
|
|
std::array<AddressableLED::LEDData, 256> buffer;
|
|
// scroll at 16 m/s, LED spacing = 2cm
|
|
// buffer is 256 LEDs, so total length = 512cm = 5.12m
|
|
// scrolling at 16 m/s yields a period of 0.32 seconds,
|
|
// or 0.00125 seconds per LED (800 LEDs/s)
|
|
auto scroll = pattern.ScrollAtAbsoluteVelocity(16_mps, 2_cm);
|
|
|
|
static uint64_t now = 0ull;
|
|
WPI_SetNowImpl([] { return now; });
|
|
|
|
for (int time = 0; time < 500; time++) {
|
|
// convert time (seconds) to microseconds
|
|
now = time * 1250ull; // 1.25ms per LED
|
|
|
|
scroll.ApplyTo(buffer);
|
|
|
|
for (size_t led = 0; led < buffer.size(); led++) {
|
|
SCOPED_TRACE(
|
|
fmt::format("LED {} of 256, run {} of 500", led + 1, time + 1));
|
|
// Base: [(0, 0, 0) (1, 1, 1) (2, 2, 2) (3, 3, 3) (4, 4, 4) ... (255, 255,
|
|
// 255)] Value for every channel should DECREASE by 1 in each timestep,
|
|
// wrapping around 0 and 255
|
|
|
|
// t=0, channel value = (0, 1, 2, ..., 254, 255)
|
|
// t=1, channel value = (255, 0, 1, ..., 253, 254)
|
|
// t=2, channel value = (254, 255, 0, ..., 252, 253)
|
|
// t=255, channel value = (1, 2, 3, ..., 255, 0)
|
|
// t=256, channel value = (0, 1, 2, ..., 254, 255)
|
|
int ch = wpi::math::FloorMod(static_cast<int>(led - time), 256);
|
|
AssertIndexColor(buffer, led, wpi::util::Color{ch, ch, ch});
|
|
}
|
|
}
|
|
|
|
WPI_SetNowImpl(nullptr); // cleanup
|
|
}
|
|
|
|
TEST(LEDPatternTest, ScrollAbsoluteBackward) {
|
|
// A black to white gradient
|
|
LEDPattern pattern = LEDPattern{[](auto data, auto writer) {
|
|
for (size_t led = 0; led < data.size(); led++) {
|
|
int ch = static_cast<int>(led % 256);
|
|
writer(led, wpi::util::Color{ch, ch, ch});
|
|
}
|
|
}};
|
|
std::array<AddressableLED::LEDData, 256> buffer;
|
|
// scroll at 16 m/s, LED spacing = 2cm
|
|
// buffer is 256 LEDs, so total length = 512cm = 5.12m
|
|
// scrolling at 16 m/s yields a period of 0.32 seconds,
|
|
// or 0.00125 seconds per LED (800 LEDs/s)
|
|
auto scroll = pattern.ScrollAtAbsoluteVelocity(-16_mps, 2_cm);
|
|
|
|
static uint64_t now = 0ull;
|
|
WPI_SetNowImpl([] { return now; });
|
|
|
|
for (int time = 0; time < 500; time++) {
|
|
// convert time (seconds) to microseconds
|
|
now = time * 1250ull; // 1.25ms per LED
|
|
|
|
scroll.ApplyTo(buffer);
|
|
|
|
for (size_t led = 0; led < buffer.size(); led++) {
|
|
SCOPED_TRACE(
|
|
fmt::format("LED {} of 256, run {} of 500", led + 1, time + 1));
|
|
// Base: [(0, 0, 0) (1, 1, 1) (2, 2, 2) (3, 3, 3) (4, 4, 4) ... (255, 255,
|
|
// 255)] Value for every channel should DECREASE by 1 in each timestep,
|
|
// wrapping around 0 and 255
|
|
|
|
// t=0, channel value = (0, 1, 2, ..., 254, 255)
|
|
// t=1, channel value = (255, 0, 1, ..., 253, 254)
|
|
// t=2, channel value = (254, 255, 0, ..., 252, 253)
|
|
// t=255, channel value = (1, 2, 3, ..., 255, 0)
|
|
// t=256, channel value = (0, 1, 2, ..., 254, 255)
|
|
int ch = wpi::math::FloorMod(static_cast<int>(led + time), 256);
|
|
AssertIndexColor(buffer, led, wpi::util::Color{ch, ch, ch});
|
|
}
|
|
}
|
|
|
|
WPI_SetNowImpl(nullptr); // cleanup
|
|
}
|
|
|
|
TEST(LEDPatternTest, RainbowFullSize) {
|
|
std::array<AddressableLED::LEDData, 180> buffer;
|
|
int saturation = 255;
|
|
int value = 255;
|
|
LEDPattern pattern = LEDPattern::Rainbow(saturation, value);
|
|
pattern.ApplyTo(buffer);
|
|
|
|
for (int led = 0; led < 180; led++) {
|
|
AssertIndexColor(buffer, led,
|
|
wpi::util::Color::FromHSV(led, saturation, value));
|
|
}
|
|
}
|
|
|
|
TEST(LEDPatternTest, LEDDataSetHSVExactRgbValues) {
|
|
struct TestCase {
|
|
int h;
|
|
int s;
|
|
int v;
|
|
int r;
|
|
int g;
|
|
int b;
|
|
};
|
|
|
|
constexpr TestCase kCases[] = {
|
|
{0, 0, 0, 0, 0, 0}, {0, 0, 255, 255, 255, 255},
|
|
{0, 255, 255, 255, 0, 0}, {60, 255, 255, 0, 255, 0},
|
|
{120, 255, 255, 0, 0, 255}, {30, 255, 255, 255, 255, 0},
|
|
{90, 255, 255, 0, 255, 255}, {150, 255, 255, 255, 0, 255},
|
|
{0, 255, 128, 128, 0, 0}, {60, 255, 128, 0, 128, 0},
|
|
{120, 255, 128, 0, 0, 128},
|
|
};
|
|
|
|
for (const auto& test : kCases) {
|
|
SCOPED_TRACE(::testing::Message() << "SetHSV(" << test.h << ", " << test.s
|
|
<< ", " << test.v << ")");
|
|
AddressableLED::LEDData data;
|
|
data.SetHSV(test.h, test.s, test.v);
|
|
|
|
EXPECT_EQ(test.r, data.r & 0xFF);
|
|
EXPECT_EQ(test.g, data.g & 0xFF);
|
|
EXPECT_EQ(test.b, data.b & 0xFF);
|
|
}
|
|
}
|
|
|
|
TEST(LEDPatternTest, RainbowFullSizeExactRgbValues) {
|
|
std::array<AddressableLED::LEDData, 180> buffer;
|
|
LEDPattern::Rainbow(255, 255).ApplyTo(buffer);
|
|
|
|
struct TestCase {
|
|
int index;
|
|
int r;
|
|
int g;
|
|
int b;
|
|
};
|
|
|
|
constexpr TestCase kCases[] = {
|
|
{0, 255, 0, 0}, {30, 255, 255, 0}, {60, 0, 255, 0},
|
|
{90, 0, 255, 255}, {120, 0, 0, 255}, {150, 255, 0, 255},
|
|
};
|
|
|
|
for (const auto& test : kCases) {
|
|
SCOPED_TRACE(::testing::Message() << "LED " << test.index);
|
|
EXPECT_EQ(test.r, buffer[test.index].r & 0xFF);
|
|
EXPECT_EQ(test.g, buffer[test.index].g & 0xFF);
|
|
EXPECT_EQ(test.b, buffer[test.index].b & 0xFF);
|
|
}
|
|
}
|
|
|
|
TEST(LEDPatternTest, RainbowHalfSize) {
|
|
std::array<AddressableLED::LEDData, 90> buffer;
|
|
int saturation = 42;
|
|
int value = 87;
|
|
LEDPattern pattern = LEDPattern::Rainbow(saturation, value);
|
|
pattern.ApplyTo(buffer);
|
|
|
|
for (int led = 0; led < 90; led++) {
|
|
AssertIndexColor(buffer, led,
|
|
wpi::util::Color::FromHSV(led * 2, saturation, value));
|
|
}
|
|
}
|
|
|
|
TEST(LEDPatternTest, RainbowThirdSize) {
|
|
std::array<AddressableLED::LEDData, 60> buffer;
|
|
int saturation = 191;
|
|
int value = 255;
|
|
LEDPattern pattern = LEDPattern::Rainbow(saturation, value);
|
|
pattern.ApplyTo(buffer);
|
|
|
|
for (int led = 0; led < 60; led++) {
|
|
SCOPED_TRACE(fmt::format("LED {} of 60", led + 1));
|
|
AssertIndexColor(buffer, led,
|
|
wpi::util::Color::FromHSV(led * 3, saturation, value));
|
|
}
|
|
}
|
|
|
|
TEST(LEDPatternTest, RainbowDoubleSize) {
|
|
std::array<AddressableLED::LEDData, 360> buffer;
|
|
int saturation = 212;
|
|
int value = 93;
|
|
LEDPattern pattern = LEDPattern::Rainbow(saturation, value);
|
|
pattern.ApplyTo(buffer);
|
|
|
|
for (int led = 0; led < 360; led++) {
|
|
SCOPED_TRACE(fmt::format("LED {} of 360", led + 1));
|
|
AssertIndexColor(buffer, led,
|
|
wpi::util::Color::FromHSV(led / 2, saturation, value));
|
|
}
|
|
}
|
|
|
|
TEST(LEDPatternTest, RainbowOddSize) {
|
|
std::array<AddressableLED::LEDData, 127> buffer;
|
|
double scale = 180.0 / 127;
|
|
int saturation = 73;
|
|
int value = 128;
|
|
LEDPattern pattern = LEDPattern::Rainbow(saturation, value);
|
|
pattern.ApplyTo(buffer);
|
|
|
|
for (int led = 0; led < 127; led++) {
|
|
SCOPED_TRACE(fmt::format("LED {} of 127", led + 1));
|
|
AssertIndexColor(buffer, led,
|
|
wpi::util::Color::FromHSV(static_cast<int>(led * scale),
|
|
saturation, value));
|
|
}
|
|
}
|
|
|
|
TEST(LEDPatternTest, ReverseSolid) {
|
|
std::array<AddressableLED::LEDData, 90> buffer;
|
|
const auto color = wpi::util::Color::ROSY_BROWN;
|
|
|
|
auto solid = LEDPattern::Solid(color);
|
|
auto pattern = solid.Reversed();
|
|
|
|
pattern.ApplyTo(buffer);
|
|
|
|
for (int led = 0; led < 90; led++) {
|
|
SCOPED_TRACE(fmt::format("LED {} of 90", led + 1));
|
|
AssertIndexColor(buffer, led, wpi::util::Color::ROSY_BROWN);
|
|
}
|
|
}
|
|
|
|
TEST(LEDPatternTest, ReverseSteps) {
|
|
std::array<AddressableLED::LEDData, 100> buffer;
|
|
std::array<std::pair<double, wpi::util::Color>, 2> steps{
|
|
std::pair{0.0, wpi::util::Color::PLUM},
|
|
std::pair{0.5, wpi::util::Color::YELLOW}};
|
|
auto stepPattern = LEDPattern::Steps(steps);
|
|
auto pattern = stepPattern.Reversed();
|
|
|
|
pattern.ApplyTo(buffer);
|
|
|
|
// colors should be swapped; yellow first, then plum
|
|
for (int led = 0; led < 50; led++) {
|
|
SCOPED_TRACE(fmt::format("LED {} of 100", led + 1));
|
|
AssertIndexColor(buffer, led, wpi::util::Color::YELLOW);
|
|
}
|
|
for (int led = 50; led < 100; led++) {
|
|
SCOPED_TRACE(fmt::format("LED {} of 100", led + 1));
|
|
AssertIndexColor(buffer, led, wpi::util::Color::PLUM);
|
|
}
|
|
}
|
|
|
|
TEST(LEDPatternTest, OffsetPositive) {
|
|
std::array<AddressableLED::LEDData, 21> buffer;
|
|
auto offset = whiteYellowPurple.OffsetBy(1);
|
|
offset.ApplyTo(buffer);
|
|
|
|
for (int led = 0; led < 21; led++) {
|
|
SCOPED_TRACE(fmt::format("LED {} of 21", led + 1));
|
|
switch (led % 3) {
|
|
case 0:
|
|
AssertIndexColor(buffer, led, wpi::util::Color::PURPLE);
|
|
break;
|
|
case 1:
|
|
AssertIndexColor(buffer, led, wpi::util::Color::WHITE);
|
|
break;
|
|
case 2:
|
|
AssertIndexColor(buffer, led, wpi::util::Color::YELLOW);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST(LEDPatternTest, OffsetNegative) {
|
|
std::array<AddressableLED::LEDData, 21> buffer;
|
|
auto offset = whiteYellowPurple.OffsetBy(-1);
|
|
offset.ApplyTo(buffer);
|
|
|
|
for (int led = 0; led < 21; led++) {
|
|
SCOPED_TRACE(fmt::format("LED {} of 21", led + 1));
|
|
switch (led % 3) {
|
|
case 0:
|
|
AssertIndexColor(buffer, led, wpi::util::Color::YELLOW);
|
|
break;
|
|
case 1:
|
|
AssertIndexColor(buffer, led, wpi::util::Color::PURPLE);
|
|
break;
|
|
case 2:
|
|
AssertIndexColor(buffer, led, wpi::util::Color::WHITE);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST(LEDPatternTest, OffsetZero) {
|
|
std::array<AddressableLED::LEDData, 21> buffer;
|
|
auto offset = whiteYellowPurple.OffsetBy(0);
|
|
offset.ApplyTo(buffer);
|
|
|
|
for (int led = 0; led < 21; led++) {
|
|
SCOPED_TRACE(fmt::format("LED {} of 21", led + 1));
|
|
switch (led % 3) {
|
|
case 0:
|
|
AssertIndexColor(buffer, led, wpi::util::Color::WHITE);
|
|
break;
|
|
case 1:
|
|
AssertIndexColor(buffer, led, wpi::util::Color::YELLOW);
|
|
break;
|
|
case 2:
|
|
AssertIndexColor(buffer, led, wpi::util::Color::PURPLE);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST(LEDPatternTest, BlinkSymmetric) {
|
|
std::array<AddressableLED::LEDData, 1> buffer;
|
|
auto white = LEDPattern::Solid(wpi::util::Color::WHITE);
|
|
|
|
// on for 2 seconds, off for 2 seconds
|
|
auto pattern = white.Blink(2_s);
|
|
|
|
static uint64_t now = 0ull;
|
|
WPI_SetNowImpl([] { return now; });
|
|
for (int t = 0; t < 8; t++) {
|
|
now = t * 1000000ull; // time travel 1 second
|
|
SCOPED_TRACE(fmt::format("Time {} seconds", t));
|
|
pattern.ApplyTo(buffer);
|
|
|
|
switch (t) {
|
|
case 0:
|
|
case 1:
|
|
case 4:
|
|
case 5:
|
|
AssertIndexColor(buffer, 0, wpi::util::Color::WHITE);
|
|
break;
|
|
case 2:
|
|
case 3:
|
|
case 6:
|
|
case 7:
|
|
AssertIndexColor(buffer, 0, wpi::util::Color::BLACK);
|
|
break;
|
|
}
|
|
}
|
|
|
|
WPI_SetNowImpl(nullptr); // cleanup
|
|
}
|
|
|
|
TEST(LEDPatternTest, BlinkAsymmetric) {
|
|
std::array<AddressableLED::LEDData, 1> buffer;
|
|
auto white = LEDPattern::Solid(wpi::util::Color::WHITE);
|
|
|
|
// on for 3 seconds, off for 1 second
|
|
auto pattern = white.Blink(3_s, 1_s);
|
|
|
|
static uint64_t now = 0ull;
|
|
WPI_SetNowImpl([] { return now; });
|
|
for (int t = 0; t < 8; t++) {
|
|
now = t * 1000000ull; // time travel 1 second
|
|
SCOPED_TRACE(fmt::format("Time {} seconds", t));
|
|
pattern.ApplyTo(buffer);
|
|
|
|
switch (t) {
|
|
case 0:
|
|
case 1:
|
|
case 2: // first period
|
|
case 4:
|
|
case 5:
|
|
case 6: // second period
|
|
AssertIndexColor(buffer, 0, wpi::util::Color::WHITE);
|
|
break;
|
|
case 3:
|
|
case 7:
|
|
AssertIndexColor(buffer, 0, wpi::util::Color::BLACK);
|
|
break;
|
|
}
|
|
}
|
|
|
|
WPI_SetNowImpl(nullptr); // cleanup
|
|
}
|
|
|
|
TEST(LEDPatternTest, BlinkInSync) {
|
|
std::array<AddressableLED::LEDData, 1> buffer;
|
|
auto white = LEDPattern::Solid(wpi::util::Color::WHITE);
|
|
|
|
bool flag = false;
|
|
auto condition = [&flag]() { return flag; };
|
|
|
|
auto pattern = white.SynchronizedBlink(condition);
|
|
|
|
SCOPED_TRACE("Flag off");
|
|
pattern.ApplyTo(buffer);
|
|
AssertIndexColor(buffer, 0, wpi::util::Color::BLACK);
|
|
|
|
SCOPED_TRACE("Flag on");
|
|
flag = true;
|
|
pattern.ApplyTo(buffer);
|
|
AssertIndexColor(buffer, 0, wpi::util::Color::WHITE);
|
|
|
|
SCOPED_TRACE("Flag off");
|
|
flag = false;
|
|
pattern.ApplyTo(buffer);
|
|
AssertIndexColor(buffer, 0, wpi::util::Color::BLACK);
|
|
}
|
|
|
|
TEST(LEDPatternTest, Breathe) {
|
|
wpi::util::Color midGray{0.5, 0.5, 0.5};
|
|
std::array<AddressableLED::LEDData, 1> buffer;
|
|
auto white = LEDPattern::Solid(wpi::util::Color::WHITE);
|
|
auto pattern = white.Breathe(4_us);
|
|
|
|
static uint64_t now = 0ull;
|
|
WPI_SetNowImpl([] { return now; });
|
|
|
|
{
|
|
now = 0ull; // start
|
|
SCOPED_TRACE(fmt::format("Time {}", now));
|
|
|
|
pattern.ApplyTo(buffer);
|
|
AssertIndexColor(buffer, 0, wpi::util::Color::WHITE);
|
|
}
|
|
{
|
|
now = 1ull; // midway (down)
|
|
SCOPED_TRACE(fmt::format("Time {}", now));
|
|
|
|
pattern.ApplyTo(buffer);
|
|
AssertIndexColor(buffer, 0, midGray);
|
|
}
|
|
{
|
|
now = 2ull; // bottom
|
|
SCOPED_TRACE(fmt::format("Time {}", now));
|
|
|
|
pattern.ApplyTo(buffer);
|
|
AssertIndexColor(buffer, 0, wpi::util::Color::BLACK);
|
|
}
|
|
{
|
|
now = 3ull; // midway (up)
|
|
SCOPED_TRACE(fmt::format("Time {}", now));
|
|
|
|
pattern.ApplyTo(buffer);
|
|
AssertIndexColor(buffer, 0, midGray);
|
|
}
|
|
{
|
|
now = 4ull; // back to start
|
|
SCOPED_TRACE(fmt::format("Time {}", now));
|
|
|
|
pattern.ApplyTo(buffer);
|
|
AssertIndexColor(buffer, 0, wpi::util::Color::WHITE);
|
|
}
|
|
WPI_SetNowImpl(nullptr); // cleanup
|
|
}
|
|
|
|
TEST(LEDPatternTest, OverlaySolidOnSolid) {
|
|
std::array<AddressableLED::LEDData, 1> buffer;
|
|
|
|
auto base = LEDPattern::Solid(wpi::util::Color::WHITE);
|
|
auto overlay = LEDPattern::Solid(wpi::util::Color::YELLOW);
|
|
auto pattern = overlay.OverlayOn(base);
|
|
pattern.ApplyTo(buffer);
|
|
|
|
AssertIndexColor(buffer, 0, wpi::util::Color::YELLOW);
|
|
}
|
|
|
|
TEST(LEDPatternTest, OverlayNearlyBlack) {
|
|
std::array<AddressableLED::LEDData, 1> buffer;
|
|
|
|
auto base = LEDPattern::Solid(wpi::util::Color::WHITE);
|
|
auto overlay = LEDPattern::Solid(wpi::util::Color{1, 0, 0});
|
|
auto pattern = overlay.OverlayOn(base);
|
|
pattern.ApplyTo(buffer);
|
|
|
|
AssertIndexColor(buffer, 0, wpi::util::Color{1, 0, 0});
|
|
}
|
|
|
|
TEST(LEDPatternTest, OverlayMixed) {
|
|
std::array<AddressableLED::LEDData, 2> buffer;
|
|
|
|
auto base = LEDPattern::Solid(wpi::util::Color::WHITE);
|
|
std::array<std::pair<double, wpi::util::Color>, 2> steps{
|
|
std::pair{0.0, wpi::util::Color::YELLOW},
|
|
std::pair{0.5, wpi::util::Color::BLACK}};
|
|
auto overlay = LEDPattern::Steps(steps);
|
|
auto pattern = overlay.OverlayOn(base);
|
|
pattern.ApplyTo(buffer);
|
|
|
|
AssertIndexColor(buffer, 0, wpi::util::Color::YELLOW);
|
|
AssertIndexColor(buffer, 1, wpi::util::Color::WHITE);
|
|
}
|
|
|
|
TEST(LEDPatternTest, Blend) {
|
|
std::array<AddressableLED::LEDData, 1> buffer;
|
|
|
|
auto pattern1 = LEDPattern::Solid(wpi::util::Color::BLUE);
|
|
auto pattern2 = LEDPattern::Solid(wpi::util::Color::RED);
|
|
auto blend = pattern1.Blend(pattern2);
|
|
blend.ApplyTo(buffer);
|
|
|
|
// Individual RGB channels are averaged
|
|
// #0000FF blended with #FF0000 yields #7F007F
|
|
AssertIndexColor(buffer, 0, wpi::util::Color{127, 0, 127});
|
|
}
|
|
|
|
TEST(LEDPatternTest, BinaryMask) {
|
|
std::array<AddressableLED::LEDData, 10> buffer;
|
|
|
|
wpi::util::Color color{123, 123, 123};
|
|
auto base = LEDPattern::Solid(color);
|
|
|
|
// first 50% mask on, last 50% mask off
|
|
std::array<std::pair<double, wpi::util::Color>, 2> steps{
|
|
std::pair{0.0, wpi::util::Color::WHITE},
|
|
std::pair{0.5, wpi::util::Color::BLACK}};
|
|
auto mask = LEDPattern::Steps(steps);
|
|
auto masked = base.Mask(mask);
|
|
masked.ApplyTo(buffer);
|
|
|
|
for (int i = 0; i < 5; i++) {
|
|
AssertIndexColor(buffer, i, color);
|
|
}
|
|
|
|
for (int i = 5; i < 10; i++) {
|
|
AssertIndexColor(buffer, i, wpi::util::Color::BLACK);
|
|
}
|
|
}
|
|
|
|
TEST(LEDPatternTest, ChannelwiseMask) {
|
|
std::array<AddressableLED::LEDData, 5> buffer;
|
|
|
|
wpi::util::Color baseColor{123, 123, 123};
|
|
wpi::util::Color halfGray{0.5, 0.5, 0.5};
|
|
auto base = LEDPattern::Solid(baseColor);
|
|
std::array<std::pair<double, wpi::util::Color>, 5> steps{
|
|
std::pair{0.0, wpi::util::Color::RED},
|
|
std::pair{0.2, wpi::util::Color::LIME},
|
|
std::pair{0.4, wpi::util::Color::BLUE}, std::pair{0.6, halfGray},
|
|
std::pair{0.8, wpi::util::Color::WHITE}};
|
|
auto mask = LEDPattern::Steps(steps);
|
|
auto masked = base.Mask(mask);
|
|
masked.ApplyTo(buffer);
|
|
|
|
AssertIndexColor(buffer, 0, wpi::util::Color{123, 0, 0});
|
|
AssertIndexColor(buffer, 1, wpi::util::Color{0, 123, 0});
|
|
AssertIndexColor(buffer, 2, wpi::util::Color{0, 0, 123});
|
|
|
|
// mask channels are all 0b00111111, base is 0b00111011,
|
|
// so the AND should give us the unmodified base color
|
|
AssertIndexColor(buffer, 3, baseColor);
|
|
AssertIndexColor(buffer, 4, baseColor);
|
|
}
|
|
|
|
TEST(LEDPatternTest, ProcessMaskLayer) {
|
|
std::array<AddressableLED::LEDData, 100> buffer;
|
|
|
|
double progress = 0.0;
|
|
auto maskLayer =
|
|
LEDPattern::ProgressMaskLayer([&progress]() { return progress; });
|
|
|
|
for (double t = 0; t <= 1.0; t += 0.01) {
|
|
SCOPED_TRACE(fmt::format("Time {}", t));
|
|
progress = t;
|
|
maskLayer.ApplyTo(buffer);
|
|
|
|
int lastMaskedLED = static_cast<int>(t * 100);
|
|
for (int i = 0; i < lastMaskedLED; i++) {
|
|
SCOPED_TRACE(fmt::format("LED {}", i));
|
|
AssertIndexColor(buffer, i, wpi::util::Color::WHITE);
|
|
}
|
|
for (int i = lastMaskedLED; i < 100; i++) {
|
|
SCOPED_TRACE(fmt::format("LED {}", i));
|
|
AssertIndexColor(buffer, i, wpi::util::Color::BLACK);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST(LEDPatternTest, ZeroBrightness) {
|
|
std::array<AddressableLED::LEDData, 1> buffer;
|
|
|
|
auto base = LEDPattern::Solid(wpi::util::Color::RED);
|
|
auto pattern = base.AtBrightness(0);
|
|
pattern.ApplyTo(buffer);
|
|
AssertIndexColor(buffer, 0, wpi::util::Color::BLACK);
|
|
}
|
|
|
|
TEST(LEDPatternTest, SameBrightness) {
|
|
std::array<AddressableLED::LEDData, 1> buffer;
|
|
|
|
auto base = LEDPattern::Solid(wpi::util::Color::MAGENTA);
|
|
auto pattern = base.AtBrightness(1.0);
|
|
pattern.ApplyTo(buffer);
|
|
AssertIndexColor(buffer, 0, wpi::util::Color::MAGENTA);
|
|
}
|
|
|
|
TEST(LEDPatternTest, HigherBrightness) {
|
|
std::array<AddressableLED::LEDData, 1> buffer;
|
|
|
|
auto base = LEDPattern::Solid(wpi::util::Color::MAGENTA);
|
|
auto pattern = base.AtBrightness(4 / 3.0);
|
|
pattern.ApplyTo(buffer);
|
|
AssertIndexColor(buffer, 0, wpi::util::Color::MAGENTA);
|
|
}
|
|
|
|
TEST(LEDPatternTest, NegativeBrightness) {
|
|
std::array<AddressableLED::LEDData, 1> buffer;
|
|
|
|
auto base = LEDPattern::Solid(wpi::util::Color::WHITE);
|
|
auto pattern = base.AtBrightness(-1.0);
|
|
pattern.ApplyTo(buffer);
|
|
AssertIndexColor(buffer, 0, wpi::util::Color::BLACK);
|
|
}
|
|
|
|
TEST(LEDPatternTest, ClippingBrightness) {
|
|
std::array<AddressableLED::LEDData, 1> buffer;
|
|
auto base = LEDPattern::Solid(wpi::util::Color::MIDNIGHT_BLUE);
|
|
auto pattern = base.AtBrightness(100);
|
|
pattern.ApplyTo(buffer);
|
|
AssertIndexColor(buffer, 0, wpi::util::Color::WHITE);
|
|
}
|
|
|
|
TEST(LEDPatternTest, ReverseMask) {
|
|
std::array<AddressableLED::LEDData, 8> buffer;
|
|
|
|
std::array<std::pair<double, wpi::util::Color>, 4> colorSteps{
|
|
std::pair{0.0, wpi::util::Color::RED},
|
|
std::pair{0.25, wpi::util::Color::BLUE},
|
|
std::pair{0.5, wpi::util::Color::YELLOW},
|
|
std::pair{0.75, wpi::util::Color::GREEN}};
|
|
std::array<std::pair<double, wpi::util::Color>, 2> maskSteps{
|
|
std::pair{0, wpi::util::Color::WHITE},
|
|
std::pair{0.5, wpi::util::Color::BLACK}};
|
|
|
|
auto pattern = LEDPattern::Steps(colorSteps)
|
|
.Mask(LEDPattern::Steps(maskSteps))
|
|
.Reversed();
|
|
|
|
pattern.ApplyTo(buffer);
|
|
|
|
AssertIndexColor(buffer, 7, wpi::util::Color::RED);
|
|
AssertIndexColor(buffer, 6, wpi::util::Color::RED);
|
|
AssertIndexColor(buffer, 5, wpi::util::Color::BLUE);
|
|
AssertIndexColor(buffer, 4, wpi::util::Color::BLUE);
|
|
AssertIndexColor(buffer, 3, wpi::util::Color::BLACK);
|
|
AssertIndexColor(buffer, 2, wpi::util::Color::BLACK);
|
|
AssertIndexColor(buffer, 1, wpi::util::Color::BLACK);
|
|
AssertIndexColor(buffer, 0, wpi::util::Color::BLACK);
|
|
}
|
|
|
|
TEST(LEDPatternTest, OffsetMask) {
|
|
std::array<AddressableLED::LEDData, 8> buffer;
|
|
|
|
std::array<std::pair<double, wpi::util::Color>, 4> colorSteps{
|
|
std::pair{0.0, wpi::util::Color::RED},
|
|
std::pair{0.25, wpi::util::Color::BLUE},
|
|
std::pair{0.5, wpi::util::Color::YELLOW},
|
|
std::pair{0.75, wpi::util::Color::GREEN}};
|
|
std::array<std::pair<double, wpi::util::Color>, 2> maskSteps{
|
|
std::pair{0, wpi::util::Color::WHITE},
|
|
std::pair{0.5, wpi::util::Color::BLACK}};
|
|
|
|
auto pattern = LEDPattern::Steps(colorSteps)
|
|
.Mask(LEDPattern::Steps(maskSteps))
|
|
.OffsetBy(4);
|
|
|
|
pattern.ApplyTo(buffer);
|
|
|
|
AssertIndexColor(buffer, 0, wpi::util::Color::BLACK);
|
|
AssertIndexColor(buffer, 1, wpi::util::Color::BLACK);
|
|
AssertIndexColor(buffer, 2, wpi::util::Color::BLACK);
|
|
AssertIndexColor(buffer, 3, wpi::util::Color::BLACK);
|
|
AssertIndexColor(buffer, 4, wpi::util::Color::RED);
|
|
AssertIndexColor(buffer, 5, wpi::util::Color::RED);
|
|
AssertIndexColor(buffer, 6, wpi::util::Color::BLUE);
|
|
AssertIndexColor(buffer, 7, wpi::util::Color::BLUE);
|
|
}
|
|
|
|
TEST(LEDPatternTest, RelativeScrollingMask) {
|
|
std::array<AddressableLED::LEDData, 8> buffer;
|
|
|
|
std::array<std::pair<double, wpi::util::Color>, 4> colorSteps{
|
|
std::pair{0.0, wpi::util::Color::RED},
|
|
std::pair{0.25, wpi::util::Color::BLUE},
|
|
std::pair{0.5, wpi::util::Color::YELLOW},
|
|
std::pair{0.75, wpi::util::Color::GREEN}};
|
|
std::array<std::pair<double, wpi::util::Color>, 2> maskSteps{
|
|
std::pair{0, wpi::util::Color::WHITE},
|
|
std::pair{0.5, wpi::util::Color::BLACK}};
|
|
|
|
auto pattern = LEDPattern::Steps(colorSteps)
|
|
.Mask(LEDPattern::Steps(maskSteps))
|
|
.ScrollAtRelativeVelocity(wpi::units::hertz_t{1e6 / 8.0});
|
|
|
|
pattern.ApplyTo(buffer);
|
|
|
|
static uint64_t now = 0ull;
|
|
WPI_SetNowImpl([] { return now; });
|
|
|
|
{
|
|
now = 0ull; // start
|
|
SCOPED_TRACE(fmt::format("Time {}", now));
|
|
|
|
pattern.ApplyTo(buffer);
|
|
|
|
AssertIndexColor(buffer, 0, wpi::util::Color::RED);
|
|
AssertIndexColor(buffer, 1, wpi::util::Color::RED);
|
|
AssertIndexColor(buffer, 2, wpi::util::Color::BLUE);
|
|
AssertIndexColor(buffer, 3, wpi::util::Color::BLUE);
|
|
AssertIndexColor(buffer, 4, wpi::util::Color::BLACK);
|
|
AssertIndexColor(buffer, 5, wpi::util::Color::BLACK);
|
|
AssertIndexColor(buffer, 6, wpi::util::Color::BLACK);
|
|
AssertIndexColor(buffer, 7, wpi::util::Color::BLACK);
|
|
}
|
|
{
|
|
now = 1ull;
|
|
SCOPED_TRACE(fmt::format("Time {}", now));
|
|
|
|
pattern.ApplyTo(buffer);
|
|
|
|
AssertIndexColor(buffer, 0, wpi::util::Color::BLACK);
|
|
AssertIndexColor(buffer, 1, wpi::util::Color::RED);
|
|
AssertIndexColor(buffer, 2, wpi::util::Color::RED);
|
|
AssertIndexColor(buffer, 3, wpi::util::Color::BLUE);
|
|
AssertIndexColor(buffer, 4, wpi::util::Color::BLUE);
|
|
AssertIndexColor(buffer, 5, wpi::util::Color::BLACK);
|
|
AssertIndexColor(buffer, 6, wpi::util::Color::BLACK);
|
|
AssertIndexColor(buffer, 7, wpi::util::Color::BLACK);
|
|
}
|
|
{
|
|
now = 2ull;
|
|
SCOPED_TRACE(fmt::format("Time {}", now));
|
|
|
|
pattern.ApplyTo(buffer);
|
|
|
|
AssertIndexColor(buffer, 0, wpi::util::Color::BLACK);
|
|
AssertIndexColor(buffer, 1, wpi::util::Color::BLACK);
|
|
AssertIndexColor(buffer, 2, wpi::util::Color::RED);
|
|
AssertIndexColor(buffer, 3, wpi::util::Color::RED);
|
|
AssertIndexColor(buffer, 4, wpi::util::Color::BLUE);
|
|
AssertIndexColor(buffer, 5, wpi::util::Color::BLUE);
|
|
AssertIndexColor(buffer, 6, wpi::util::Color::BLACK);
|
|
AssertIndexColor(buffer, 7, wpi::util::Color::BLACK);
|
|
}
|
|
{
|
|
now = 3ull;
|
|
SCOPED_TRACE(fmt::format("Time {}", now));
|
|
|
|
pattern.ApplyTo(buffer);
|
|
|
|
AssertIndexColor(buffer, 0, wpi::util::Color::BLACK);
|
|
AssertIndexColor(buffer, 1, wpi::util::Color::BLACK);
|
|
AssertIndexColor(buffer, 2, wpi::util::Color::BLACK);
|
|
AssertIndexColor(buffer, 3, wpi::util::Color::RED);
|
|
AssertIndexColor(buffer, 4, wpi::util::Color::RED);
|
|
AssertIndexColor(buffer, 5, wpi::util::Color::BLUE);
|
|
AssertIndexColor(buffer, 6, wpi::util::Color::BLUE);
|
|
AssertIndexColor(buffer, 7, wpi::util::Color::BLACK);
|
|
}
|
|
|
|
WPI_SetNowImpl(nullptr); // cleanup
|
|
}
|
|
|
|
TEST(LEDPatternTest, AbsoluteScrollingMask) {
|
|
std::array<AddressableLED::LEDData, 8> buffer;
|
|
|
|
std::array<std::pair<double, wpi::util::Color>, 4> colorSteps{
|
|
std::pair{0.0, wpi::util::Color::RED},
|
|
std::pair{0.25, wpi::util::Color::BLUE},
|
|
std::pair{0.5, wpi::util::Color::YELLOW},
|
|
std::pair{0.75, wpi::util::Color::GREEN}};
|
|
std::array<std::pair<double, wpi::util::Color>, 2> maskSteps{
|
|
std::pair{0, wpi::util::Color::WHITE},
|
|
std::pair{0.5, wpi::util::Color::BLACK}};
|
|
|
|
auto pattern = LEDPattern::Steps(colorSteps)
|
|
.Mask(LEDPattern::Steps(maskSteps))
|
|
.ScrollAtAbsoluteVelocity(1_mps, 1_m);
|
|
|
|
pattern.ApplyTo(buffer);
|
|
|
|
static uint64_t now = 0ull;
|
|
WPI_SetNowImpl([] { return now; });
|
|
|
|
{
|
|
now = 0ull; // start
|
|
SCOPED_TRACE(fmt::format("Time {}", now));
|
|
|
|
pattern.ApplyTo(buffer);
|
|
|
|
AssertIndexColor(buffer, 0, wpi::util::Color::RED);
|
|
AssertIndexColor(buffer, 1, wpi::util::Color::RED);
|
|
AssertIndexColor(buffer, 2, wpi::util::Color::BLUE);
|
|
AssertIndexColor(buffer, 3, wpi::util::Color::BLUE);
|
|
AssertIndexColor(buffer, 4, wpi::util::Color::BLACK);
|
|
AssertIndexColor(buffer, 5, wpi::util::Color::BLACK);
|
|
AssertIndexColor(buffer, 6, wpi::util::Color::BLACK);
|
|
AssertIndexColor(buffer, 7, wpi::util::Color::BLACK);
|
|
}
|
|
{
|
|
now = 1000000ull;
|
|
SCOPED_TRACE(fmt::format("Time {}", now));
|
|
|
|
pattern.ApplyTo(buffer);
|
|
|
|
AssertIndexColor(buffer, 0, wpi::util::Color::BLACK);
|
|
AssertIndexColor(buffer, 1, wpi::util::Color::RED);
|
|
AssertIndexColor(buffer, 2, wpi::util::Color::RED);
|
|
AssertIndexColor(buffer, 3, wpi::util::Color::BLUE);
|
|
AssertIndexColor(buffer, 4, wpi::util::Color::BLUE);
|
|
AssertIndexColor(buffer, 5, wpi::util::Color::BLACK);
|
|
AssertIndexColor(buffer, 6, wpi::util::Color::BLACK);
|
|
AssertIndexColor(buffer, 7, wpi::util::Color::BLACK);
|
|
}
|
|
{
|
|
now = 2000000ull;
|
|
SCOPED_TRACE(fmt::format("Time {}", now));
|
|
|
|
pattern.ApplyTo(buffer);
|
|
|
|
AssertIndexColor(buffer, 0, wpi::util::Color::BLACK);
|
|
AssertIndexColor(buffer, 1, wpi::util::Color::BLACK);
|
|
AssertIndexColor(buffer, 2, wpi::util::Color::RED);
|
|
AssertIndexColor(buffer, 3, wpi::util::Color::RED);
|
|
AssertIndexColor(buffer, 4, wpi::util::Color::BLUE);
|
|
AssertIndexColor(buffer, 5, wpi::util::Color::BLUE);
|
|
AssertIndexColor(buffer, 6, wpi::util::Color::BLACK);
|
|
AssertIndexColor(buffer, 7, wpi::util::Color::BLACK);
|
|
}
|
|
{
|
|
now = 3000000ull;
|
|
SCOPED_TRACE(fmt::format("Time {}", now));
|
|
|
|
pattern.ApplyTo(buffer);
|
|
|
|
AssertIndexColor(buffer, 0, wpi::util::Color::BLACK);
|
|
AssertIndexColor(buffer, 1, wpi::util::Color::BLACK);
|
|
AssertIndexColor(buffer, 2, wpi::util::Color::BLACK);
|
|
AssertIndexColor(buffer, 3, wpi::util::Color::RED);
|
|
AssertIndexColor(buffer, 4, wpi::util::Color::RED);
|
|
AssertIndexColor(buffer, 5, wpi::util::Color::BLUE);
|
|
AssertIndexColor(buffer, 6, wpi::util::Color::BLUE);
|
|
AssertIndexColor(buffer, 7, wpi::util::Color::BLACK);
|
|
}
|
|
|
|
WPI_SetNowImpl(nullptr); // cleanup
|
|
}
|
|
|
|
void AssertIndexColor(std::span<AddressableLED::LEDData> data, int index,
|
|
wpi::util::Color color) {
|
|
wpi::util::Color8Bit color8bit{color};
|
|
|
|
EXPECT_EQ(color8bit.red, data[index].r & 0xFF);
|
|
EXPECT_EQ(color8bit.green, data[index].g & 0xFF);
|
|
EXPECT_EQ(color8bit.blue, data[index].b & 0xFF);
|
|
}
|
|
|
|
wpi::util::Color LerpColors(wpi::util::Color a, wpi::util::Color b, double t) {
|
|
return wpi::util::Color{wpi::util::Lerp(a.red, b.red, t),
|
|
wpi::util::Lerp(a.green, b.green, t),
|
|
wpi::util::Lerp(a.blue, b.blue, t)};
|
|
}
|
|
} // namespace wpi
|