Files
klubhaus-doorbell/libraries/FastLED/tests/test_corkscrew.cpp
2026-02-12 00:45:31 -08:00

659 lines
25 KiB
C++

// g++ --std=c++11 test.cpp
#include "fl/math_macros.h"
#include "test.h"
#include "fl/algorithm.h"
#include "fl/sstream.h"
#include "fl/corkscrew.h"
#include "fl/grid.h"
#include "fl/screenmap.h"
#include "fl/tile2x2.h" // Ensure this header is included for Tile2x2_u8
#define NUM_LEDS 288
using namespace fl;
TEST_CASE("Corkscrew Circle10 test") {
// Test basic dimensional calculations using Corkscrew objects directly
// Test simple case: 1 turn, 10 LEDs
Corkscrew corkscrew_simple(1.0f, 10);
REQUIRE_EQ(corkscrew_simple.cylinderWidth(), 10); // ceil(10 LEDs / 1 turn) = 10
REQUIRE_EQ(corkscrew_simple.cylinderHeight(), 1); // ceil(1 turn) = 1
// Test 2 turns with 20 LEDs (10 LEDs per turn)
Corkscrew corkscrew_example(2.0f, 20);
REQUIRE_EQ(corkscrew_example.cylinderWidth(), 10); // LEDs per turn
REQUIRE_EQ(corkscrew_example.cylinderHeight(), 2); // Number of turns
// Test default case: 19 turns, 144 LEDs
Corkscrew corkscrew_default(19.0f, 144);
REQUIRE_EQ(corkscrew_default.cylinderWidth(), 8); // ceil(144/19) = ceil(7.58) = 8
REQUIRE_EQ(corkscrew_default.cylinderHeight(), 18); // Optimized: ceil(144/8) = ceil(18) = 18
// Test FestivalStick case: 19 turns, 288 LEDs
Corkscrew corkscrew_festival(19.0f, 288);
REQUIRE_EQ(corkscrew_festival.cylinderWidth(), 16); // ceil(288/19) = ceil(15.16) = 16
REQUIRE_EQ(corkscrew_festival.cylinderHeight(), 18); // ceil(288/16) = ceil(18) = 18 (optimized!)
// Verify grid size matches LED count
REQUIRE_EQ(corkscrew_festival.cylinderWidth() * corkscrew_festival.cylinderHeight(), 288);
// Check LED distribution - find max height position actually used
float max_height = 0.0f;
float min_height = 999.0f;
for (uint16_t i = 0; i < corkscrew_festival.size(); ++i) {
vec2f pos = corkscrew_festival.at_no_wrap(i);
max_height = MAX(max_height, pos.y);
min_height = MIN(min_height, pos.y);
}
// LEDs should span from 0 to height-1
REQUIRE(min_height >= 0.0f);
REQUIRE(max_height <= 18.0f); // height-1 = 18-1 = 17
}
TEST_CASE("Corkscrew LED distribution test") {
// Test if LEDs actually reach the top row
Corkscrew corkscrew(19.0f, 288); // FestivalStick case
// Count how many LEDs map to each row
fl::vector<int> row_counts(corkscrew.cylinderHeight(), 0);
for (uint16_t i = 0; i < corkscrew.size(); ++i) {
vec2f pos = corkscrew.at_no_wrap(i);
int row = static_cast<int>(pos.y);
if (row >= 0 && row < corkscrew.cylinderHeight()) {
row_counts[row]++;
}
}
// Check if top row (now row 17) has LEDs
REQUIRE(row_counts[corkscrew.cylinderHeight() - 1] > 0); // Top row should have LEDs
REQUIRE(row_counts[0] > 0); // Bottom row should have LEDs
}
TEST_CASE("Corkscrew two turns test") {
// Test 2 turns with 2 LEDs per turn (4 LEDs total)
Corkscrew corkscrew_two_turns(2.0f, 4); // 2 turns, 4 LEDs, defaults
// Verify: 4 LEDs / 2 turns = 2 LEDs per turn
REQUIRE_EQ(corkscrew_two_turns.cylinderWidth(), 2); // LEDs per turn
REQUIRE_EQ(corkscrew_two_turns.cylinderHeight(), 2); // Number of turns
// Verify grid size matches LED count
REQUIRE_EQ(corkscrew_two_turns.cylinderWidth() * corkscrew_two_turns.cylinderHeight(), 4);
// Test LED positioning across both turns
REQUIRE_EQ(corkscrew_two_turns.size(), 4);
// Check that LEDs are distributed across both rows
fl::vector<int> row_counts(corkscrew_two_turns.cylinderHeight(), 0);
// Unrolled loop for 4 LEDs
vec2f pos0 = corkscrew_two_turns.at_no_wrap(0);
vec2f pos1 = corkscrew_two_turns.at_no_wrap(1);
vec2f pos2 = corkscrew_two_turns.at_no_wrap(2);
vec2f pos3 = corkscrew_two_turns.at_no_wrap(3);
FL_WARN("pos0: " << pos0);
FL_WARN("pos1: " << pos1);
FL_WARN("pos2: " << pos2);
FL_WARN("pos3: " << pos3);
int row0 = static_cast<int>(pos0.y);
if (row0 >= 0 && row0 < corkscrew_two_turns.cylinderHeight()) {
row_counts[row0]++;
}
int row1 = static_cast<int>(pos1.y);
if (row1 >= 0 && row1 < corkscrew_two_turns.cylinderHeight()) {
row_counts[row1]++;
}
int row2 = static_cast<int>(pos2.y);
if (row2 >= 0 && row2 < corkscrew_two_turns.cylinderHeight()) {
row_counts[row2]++;
}
int row3 = static_cast<int>(pos3.y);
if (row3 >= 0 && row3 < corkscrew_two_turns.cylinderHeight()) {
row_counts[row3]++;
}
// Both rows should have LEDs
REQUIRE(row_counts[0] > 0); // First turn should have LEDs
REQUIRE(row_counts[1] > 0); // Second turn should have LEDs
}
TEST_CASE("Constexpr corkscrew dimension calculation") {
// Test constexpr functions at compile time
// FestivalStick case: 19 turns, 288 LEDs
constexpr uint16_t festival_width = fl::calculateCorkscrewWidth(19.0f, 288);
constexpr uint16_t festival_height = fl::calculateCorkscrewHeight(19.0f, 288);
static_assert(festival_width == 16, "FestivalStick width should be 16");
static_assert(festival_height == 18, "FestivalStick height should be 18");
// Default case: 19 turns, 144 LEDs
constexpr uint16_t default_width = fl::calculateCorkscrewWidth(19.0f, 144);
constexpr uint16_t default_height = fl::calculateCorkscrewHeight(19.0f, 144);
static_assert(default_width == 8, "Default width should be 8");
static_assert(default_height == 18, "Default height should be 18");
// Verify runtime and compile-time versions match
Corkscrew runtime_corkscrew(19.0f, 288);
REQUIRE_EQ(festival_width, runtime_corkscrew.cylinderWidth());
REQUIRE_EQ(festival_height, runtime_corkscrew.cylinderHeight());
// Test simple perfect case: 100 LEDs, 10 turns = 10x10 grid
constexpr uint16_t simple_width = fl::calculateCorkscrewWidth(10.0f, 100);
constexpr uint16_t simple_height = fl::calculateCorkscrewHeight(10.0f, 100);
static_assert(simple_width == 10, "Simple width should be 10");
static_assert(simple_height == 10, "Simple height should be 10");
}
TEST_CASE("TestCorkscrewBufferFunctionality") {
// Create a Corkscrew with 16 LEDs and 4 turns for simple testing
// Create a buffer for testing
fl::vector<CRGB> led_buffer(16);
fl::span<CRGB> led_span(led_buffer);
fl::Corkscrew corkscrew(4.0f, led_span, false);
// Get the rectangular buffer dimensions
uint16_t width = corkscrew.cylinderWidth();
uint16_t height = corkscrew.cylinderHeight();
// Get the surface and verify it's properly initialized
fl::Grid<CRGB>& surface = corkscrew.surface();
REQUIRE(surface.size() == width * height);
// Fill the buffer with a simple pattern
corkscrew.fillInputSurface(CRGB::Red);
for (size_t i = 0; i < surface.size(); ++i) {
REQUIRE(surface.data()[i] == CRGB::Red);
}
// Clear the buffer
corkscrew.clear();
for (size_t i = 0; i < surface.size(); ++i) {
REQUIRE(surface.data()[i] == CRGB::Black);
}
// Create a source fl::Grid<CRGB> object with a checkerboard pattern
fl::Grid<CRGB> source_grid(width, height);
// Fill source with checkerboard pattern
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
if ((x + y) % 2 == 0) {
source_grid(x, y) = CRGB::Blue;
} else {
source_grid(x, y) = CRGB::Green;
}
}
}
// First, copy the source grid to the corkscrew's surface, then draw
auto& corkscrew_surface = corkscrew.surface();
for (u32 y = 0; y < height; ++y) {
for (u32 x = 0; x < width; ++x) {
if (x < corkscrew_surface.width() && y < corkscrew_surface.height()) {
corkscrew_surface(x, y) = source_grid(x, y);
}
}
}
corkscrew.draw();
// Verify that the LED data has been populated with colors from the surface
// Note: Not every LED may be written to by the corkscrew mapping
bool found_blue = false;
bool found_green = false;
int non_black_count = 0;
CRGB* led_data = corkscrew.rawData();
for (size_t i = 0; i < corkscrew.size(); ++i) {
if (led_data[i] == CRGB::Blue) found_blue = true;
if (led_data[i] == CRGB::Green) found_green = true;
if (led_data[i] != CRGB::Black) non_black_count++;
}
// Should have found both colors in our checkerboard pattern
REQUIRE(found_blue);
REQUIRE(found_green);
// Should have some non-black pixels since we read from a filled source
REQUIRE(non_black_count > 0);
}
TEST_CASE("Corkscrew readFrom with bilinear interpolation") {
// Create a small corkscrew for testing with LED buffer
fl::vector<CRGB> led_buffer(12);
fl::span<CRGB> led_span(led_buffer);
Corkscrew corkscrew(1.0f, led_span, false);
// Create a source grid - simple 3x4 pattern
const uint16_t width = 3;
const uint16_t height = 4;
fl::Grid<CRGB> source_grid(width, height);
// Grid initializes to black by default, but let's be explicit
source_grid.clear();
// Set up a simple pattern: red in corners, blue in center
source_grid(0, 0) = CRGB::Red; // Bottom-left
source_grid(2, 0) = CRGB::Red; // Bottom-right
source_grid(0, 3) = CRGB::Red; // Top-left
source_grid(2, 3) = CRGB::Red; // Top-right
source_grid(1, 1) = CRGB::Blue; // Center-ish
source_grid(1, 2) = CRGB::Blue; // Center-ish
// Copy source to corkscrew surface and draw
auto& corkscrew_surface2 = corkscrew.surface();
for (u32 y = 0; y < height; ++y) {
for (u32 x = 0; x < width; ++x) {
if (x < corkscrew_surface2.width() && y < corkscrew_surface2.height()) {
corkscrew_surface2(x, y) = source_grid(x, y);
}
}
}
corkscrew.draw();
// Verify LED count
REQUIRE_EQ(corkscrew.size(), 12);
// Check that some colors were captured in the LED data
bool found_red_component = false;
bool found_blue_component = false;
int non_black_count = 0;
CRGB* led_data = corkscrew.rawData();
for (size_t i = 0; i < corkscrew.size(); ++i) {
const CRGB& color = led_data[i];
if (color.r > 0 || color.g > 0 || color.b > 0) {
non_black_count++;
}
if (color.r > 0) found_red_component = true;
if (color.b > 0) found_blue_component = true;
}
// We should find some non-black LEDs and some red components
REQUIRE(non_black_count > 0);
REQUIRE(found_red_component);
// Note: Blue components might not be found depending on the mapping, but we check anyway
FL_UNUSED(found_blue_component); // Suppress warning if not used in assertions
// Test that coordinates mapping makes sense by checking a specific LED
vec2f pos0 = corkscrew.at_no_wrap(0);
vec2f pos5 = corkscrew.at_no_wrap(5);
// Positions should be different
bool positions_different = (pos0.x != pos5.x) || (pos0.y != pos5.y);
REQUIRE(positions_different);
// Positions should be within reasonable bounds
REQUIRE(pos0.x >= 0.0f);
REQUIRE(pos0.y >= 0.0f);
REQUIRE(pos5.x >= 0.0f);
REQUIRE(pos5.y >= 0.0f);
}
TEST_CASE("Corkscrew CRGB* data access") {
// Create a corkscrew with LED buffer
fl::vector<CRGB> led_buffer(6);
fl::span<CRGB> led_span(led_buffer);
Corkscrew corkscrew(1.0f, led_span, false, Gap());
// Get raw CRGB* access - this should trigger lazy allocation
CRGB* data_ptr = corkscrew.rawData();
REQUIRE(data_ptr != nullptr);
// Verify buffer was allocated with correct size
size_t expected_size = static_cast<size_t>(corkscrew.cylinderWidth()) * static_cast<size_t>(corkscrew.cylinderHeight());
// All pixels should be initialized to black
for (size_t i = 0; i < expected_size; ++i) {
REQUIRE_EQ(data_ptr[i].r, 0);
REQUIRE_EQ(data_ptr[i].g, 0);
REQUIRE_EQ(data_ptr[i].b, 0);
}
// Note: Removed const access test since we simplified the API to be non-const
// Modify a pixel via the raw pointer
if (expected_size > 0) {
data_ptr[0] = CRGB::Red;
// Verify the change is reflected
REQUIRE_EQ(data_ptr[0].r, 255);
REQUIRE_EQ(data_ptr[0].g, 0);
REQUIRE_EQ(data_ptr[0].b, 0);
// Surface should remain separate from LED data
const auto& surface = corkscrew.surface();
REQUIRE_EQ(surface.data()[0].r, 0); // Surface should still be black
REQUIRE_EQ(surface.data()[0].g, 0);
REQUIRE_EQ(surface.data()[0].b, 0);
}
}
TEST_CASE("Corkscrew ScreenMap functionality") {
// Create a simple corkscrew for testing
fl::Corkscrew corkscrew(2.0f, 8, false, Gap()); // 2 turns, 8 LEDs
// Test default diameter
fl::ScreenMap screenMap = corkscrew.toScreenMap();
// Verify the ScreenMap has the correct number of LEDs
REQUIRE_EQ(screenMap.getLength(), 8);
// Verify default diameter
REQUIRE_EQ(screenMap.getDiameter(), 0.5f);
// Test custom diameter
fl::ScreenMap screenMapCustom = corkscrew.toScreenMap(1.2f);
REQUIRE_EQ(screenMapCustom.getDiameter(), 1.2f);
// Verify that each LED index maps to the same position as at_exact() (wrapped)
for (uint16_t i = 0; i < 8; ++i) {
vec2f corkscrewPos = corkscrew.at_exact(i);
vec2f screenMapPos = screenMap[i];
// Positions should match exactly (both are wrapped)
REQUIRE(ALMOST_EQUAL_FLOAT(corkscrewPos.x, screenMapPos.x));
REQUIRE(ALMOST_EQUAL_FLOAT(corkscrewPos.y, screenMapPos.y));
}
// Test that different LED indices have different positions (at least some of them)
bool positions_differ = false;
for (uint16_t i = 1; i < 8; ++i) {
vec2f pos0 = screenMap[0];
vec2f posI = screenMap[i];
if (!ALMOST_EQUAL_FLOAT(pos0.x, posI.x) || !ALMOST_EQUAL_FLOAT(pos0.y, posI.y)) {
positions_differ = true;
break;
}
}
REQUIRE(positions_differ); // At least some positions should be different
// Test ScreenMap bounds
vec2f bounds = screenMap.getBounds();
REQUIRE(bounds.x > 0.0f); // Should have some width
REQUIRE(bounds.y >= 0.0f); // Should have some height (or 0 for single row)
// Test a larger corkscrew to ensure it works with more complex cases
fl::Corkscrew corkscrew_large(19.0f, 288, false, Gap()); // FestivalStick case
fl::ScreenMap screenMap_large = corkscrew_large.toScreenMap(0.8f);
REQUIRE_EQ(screenMap_large.getLength(), 288);
REQUIRE_EQ(screenMap_large.getDiameter(), 0.8f);
// Verify all positions are valid (non-negative)
for (uint16_t i = 0; i < 288; ++i) {
vec2f pos = screenMap_large[i];
REQUIRE(pos.x >= 0.0f);
REQUIRE(pos.y >= 0.0f);
}
}
TEST_CASE("Corkscrew Gap struct functionality") {
// Test default Gap construction
Gap defaultGap;
REQUIRE_EQ(defaultGap.gap, 0.0f);
// Test Gap construction with value
Gap halfGap(0.5f);
REQUIRE_EQ(halfGap.gap, 0.5f);
Gap fullGap(1.0f);
REQUIRE_EQ(fullGap.gap, 1.0f);
// Test Corkscrew construction with Gap struct
Gap customGap(0.3f);
Corkscrew corkscrewWithGap(19.0f, 144, false, customGap);
REQUIRE_EQ(corkscrewWithGap.size(), 144);
// Test that different gap values still produce valid corkscrews
Gap noGap(0.0f);
Gap smallGap(0.1f);
Gap largeGap(0.9f);
Corkscrew corkscrewNoGap(2.0f, 8, false, noGap);
Corkscrew corkscrewSmallGap(2.0f, 8, false, smallGap);
Corkscrew corkscrewLargeGap(2.0f, 8, false, largeGap);
// All should have the same basic properties since gap computation is not implemented yet
REQUIRE_EQ(corkscrewNoGap.size(), 8);
REQUIRE_EQ(corkscrewSmallGap.size(), 8);
REQUIRE_EQ(corkscrewLargeGap.size(), 8);
// Test that Gap struct supports copy construction and assignment
Gap originalGap(0.7f);
Gap copiedGap(originalGap);
REQUIRE_EQ(copiedGap.gap, 0.7f);
Gap assignedGap;
assignedGap = originalGap;
REQUIRE_EQ(assignedGap.gap, 0.7f);
// Test Gap struct in corkscrew construction
Corkscrew corkscrewWithGap2(19.0f, 144, false, customGap);
REQUIRE(corkscrewWithGap2.cylinderWidth() > 0);
REQUIRE(corkscrewWithGap2.cylinderHeight() > 0);
}
TEST_CASE("Corkscrew Enhanced Gap - Specific user test: 2 LEDs, 1 turn, 1.0f gap every 1 LED") {
// Test case: 2 LEDs, 1 turn, gap of 1.0f every 1 LED
Gap gapEvery1(1, 1.0f); // Gap after every 1 LED, adds full 1.0 width unit
Corkscrew corkscrew(1.0f, 2, false, gapEvery1); // 1 turn, 2 LEDs
// Get dimensions - accept whatever height the algorithm produces
uint16_t width = corkscrew.cylinderWidth();
FL_WARN("User test dimensions: width=" << width << " height=" << corkscrew.cylinderHeight());
// Total turns should still be exactly 1
REQUIRE_EQ(1.0f, 1.0f);
// Get positions for both LEDs (unwrapped and wrapped)
vec2f pos0_unwrapped = corkscrew.at_no_wrap(0); // First LED, no gap yet
vec2f pos1_unwrapped = corkscrew.at_no_wrap(1); // Second LED, after gap trigger
vec2f pos0_wrapped = corkscrew.at_exact(0); // First LED, wrapped
vec2f pos1_wrapped = corkscrew.at_exact(1); // Second LED, wrapped
FL_WARN("LED0 unwrapped: " << pos0_unwrapped << ", wrapped: " << pos0_wrapped);
FL_WARN("LED1 unwrapped: " << pos1_unwrapped << ", wrapped: " << pos1_wrapped);
// Both unwrapped positions should be valid
REQUIRE(pos0_unwrapped.x >= 0.0f);
REQUIRE(pos0_unwrapped.y >= 0.0f);
REQUIRE(pos1_unwrapped.x >= 0.0f);
REQUIRE(pos1_unwrapped.y >= 0.0f);
// Both wrapped positions should be valid
REQUIRE(pos0_wrapped.x >= 0.0f);
REQUIRE(pos0_wrapped.y >= 0.0f);
REQUIRE(pos1_wrapped.x >= 0.0f);
REQUIRE(pos1_wrapped.y >= 0.0f);
// First LED should be at or near starting position
REQUIRE(pos0_unwrapped.x <= static_cast<float>(width));
REQUIRE(pos0_unwrapped.y <= 1.0f);
// Wrapped positions should be within the cylinder width
REQUIRE(pos0_wrapped.x < static_cast<float>(width));
REQUIRE(pos1_wrapped.x < static_cast<float>(width));
// The key test: total height should not exceed the specified turns
float maxHeight = MAX(pos0_unwrapped.y, pos1_unwrapped.y);
REQUIRE(maxHeight <= 1.0f + 0.1f); // Small tolerance for floating point
// LEDs should have different unwrapped positions due to gap
bool unwrapped_different = (pos0_unwrapped.x != pos1_unwrapped.x) || (pos0_unwrapped.y != pos1_unwrapped.y);
REQUIRE(unwrapped_different);
// Test the expected gap behavior: LED1 should be "back at starting position" when wrapped
// This means LED1 wrapped x-coordinate should be close to LED0's x-coordinate
float x_diff = (pos1_wrapped.x > pos0_wrapped.x) ?
(pos1_wrapped.x - pos0_wrapped.x) :
(pos0_wrapped.x - pos1_wrapped.x);
REQUIRE(x_diff < 0.1f); // LED1 should wrap back to near the starting x position
}
TEST_CASE("Corkscrew gap test with 3 LEDs") {
// User's specific test case:
// 3 LEDs, gap of 0.5 every 1 LED, one turn
// Expected: LED0 at w=0, LED1 at w=3.0, LED2 at w=0 (back to start)
Gap gapParams(1, 0.5f); // gap of 0.5 every 1 LED
Corkscrew corkscrew_gap(1.0f, 3, false, gapParams);
REQUIRE_EQ(corkscrew_gap.size(), 3);
// Get LED positions
vec2f pos0 = corkscrew_gap.at_exact(0); // wrapped position
vec2f pos1 = corkscrew_gap.at_exact(1); // wrapped position
vec2f pos2 = corkscrew_gap.at_exact(2); // wrapped position
FL_WARN("LED0 wrapped pos: (" << pos0.x << "," << pos0.y << ")");
FL_WARN("LED1 wrapped pos: (" << pos1.x << "," << pos1.y << ")");
FL_WARN("LED2 wrapped pos: (" << pos2.x << "," << pos2.y << ")");
// Also get unwrapped positions to understand the math
vec2f pos0_unwrap = corkscrew_gap.at_no_wrap(0);
vec2f pos1_unwrap = corkscrew_gap.at_no_wrap(1);
vec2f pos2_unwrap = corkscrew_gap.at_no_wrap(2);
FL_WARN("LED0 unwrapped pos: (" << pos0_unwrap.x << "," << pos0_unwrap.y << ")");
FL_WARN("LED1 unwrapped pos: (" << pos1_unwrap.x << "," << pos1_unwrap.y << ")");
FL_WARN("LED2 unwrapped pos: (" << pos2_unwrap.x << "," << pos2_unwrap.y << ")");
FL_WARN("Calculated width: " << corkscrew_gap.cylinderWidth());
// Verify the corrected gap calculation produces expected results:
// - LED0: unwrapped (0,0), wrapped (0,0)
// - LED1: unwrapped (3,1), wrapped (0,1) - 3.0 exactly as user wanted!
// - LED2: unwrapped (6,2), wrapped (0,2) - wraps back to 0 as expected
// Test unwrapped positions (what the user specified)
REQUIRE(ALMOST_EQUAL_FLOAT(pos0_unwrap.x, 0.0f)); // LED0 at unwrapped w=0
REQUIRE(ALMOST_EQUAL_FLOAT(pos1_unwrap.x, 3.0f)); // LED1 at unwrapped w=3.0 ✓
REQUIRE(ALMOST_EQUAL_FLOAT(pos2_unwrap.x, 6.0f)); // LED2 at unwrapped w=6.0
// Test wrapped positions - all LEDs wrap back to w=0 due to width=3
REQUIRE(ALMOST_EQUAL_FLOAT(pos0.x, 0.0f)); // LED0 wrapped w=0
REQUIRE(ALMOST_EQUAL_FLOAT(pos1.x, 0.0f)); // LED1 wrapped w=0 (3.0 % 3 = 0)
REQUIRE(ALMOST_EQUAL_FLOAT(pos2.x, 0.0f)); // LED2 wrapped w=0 (6.0 % 3 = 0)
// Height positions should increase by 1 for each LED (one turn per 3 width units)
REQUIRE(ALMOST_EQUAL_FLOAT(pos0_unwrap.y, 0.0f)); // LED0 height
REQUIRE(ALMOST_EQUAL_FLOAT(pos1_unwrap.y, 1.0f)); // LED1 height
REQUIRE(ALMOST_EQUAL_FLOAT(pos2_unwrap.y, 2.0f)); // LED2 height
}
TEST_CASE("Corkscrew caching functionality") {
// Create a small corkscrew for testing
Corkscrew corkscrew(2.0f, 10); // 2 turns, 10 LEDs
// Test that caching is enabled by default
Tile2x2_u8_wrap tile1 = corkscrew.at_wrap(1.0f);
Tile2x2_u8_wrap tile1_again = corkscrew.at_wrap(1.0f);
// Values should be identical (from cache)
for (int x = 0; x < 2; x++) {
for (int y = 0; y < 2; y++) {
auto data1 = tile1.at(x, y);
auto data1_again = tile1_again.at(x, y);
REQUIRE_EQ(data1.first.x, data1_again.first.x);
REQUIRE_EQ(data1.first.y, data1_again.first.y);
REQUIRE_EQ(data1.second, data1_again.second);
}
}
}
TEST_CASE("Corkscrew caching disable functionality") {
// Create a small corkscrew for testing
Corkscrew corkscrew(2.0f, 10); // 2 turns, 10 LEDs
// Get a tile with caching enabled
Tile2x2_u8_wrap tile_cached = corkscrew.at_wrap(1.0f);
// Disable caching
corkscrew.setCachingEnabled(false);
// Get the same tile with caching disabled
Tile2x2_u8_wrap tile_uncached = corkscrew.at_wrap(1.0f);
// Values should still be identical (same calculation)
for (int x = 0; x < 2; x++) {
for (int y = 0; y < 2; y++) {
auto data_cached = tile_cached.at(x, y);
auto data_uncached = tile_uncached.at(x, y);
REQUIRE_EQ(data_cached.first.x, data_uncached.first.x);
REQUIRE_EQ(data_cached.first.y, data_uncached.first.y);
REQUIRE_EQ(data_cached.second, data_uncached.second);
}
}
// Re-enable caching
corkscrew.setCachingEnabled(true);
// Get a tile again - should work with caching re-enabled
Tile2x2_u8_wrap tile_recached = corkscrew.at_wrap(1.0f);
// Values should still be identical
for (int x = 0; x < 2; x++) {
for (int y = 0; y < 2; y++) {
auto data_cached = tile_cached.at(x, y);
auto data_recached = tile_recached.at(x, y);
REQUIRE_EQ(data_cached.first.x, data_recached.first.x);
REQUIRE_EQ(data_cached.first.y, data_recached.first.y);
REQUIRE_EQ(data_cached.second, data_recached.second);
}
}
}
TEST_CASE("Corkscrew caching with edge cases") {
// Create a small corkscrew for testing
Corkscrew corkscrew(1.5f, 5); // 1.5 turns, 5 LEDs
// Test caching with various float indices
Tile2x2_u8_wrap tile0 = corkscrew.at_wrap(0.0f);
Tile2x2_u8_wrap tile4 = corkscrew.at_wrap(4.0f);
// Test that different indices produce different tiles
bool tiles_different = false;
for (int x = 0; x < 2 && !tiles_different; x++) {
for (int y = 0; y < 2 && !tiles_different; y++) {
auto data0 = tile0.at(x, y);
auto data4 = tile4.at(x, y);
if (data0.first.x != data4.first.x ||
data0.first.y != data4.first.y ||
data0.second != data4.second) {
tiles_different = true;
}
}
}
REQUIRE(tiles_different); // Tiles at different positions should be different
// Test that same index produces same tile (cache consistency)
Tile2x2_u8_wrap tile0_again = corkscrew.at_wrap(0.0f);
for (int x = 0; x < 2; x++) {
for (int y = 0; y < 2; y++) {
auto data0 = tile0.at(x, y);
auto data0_again = tile0_again.at(x, y);
REQUIRE_EQ(data0.first.x, data0_again.first.x);
REQUIRE_EQ(data0.first.y, data0_again.first.y);
REQUIRE_EQ(data0.second, data0_again.second);
}
}
}