// 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 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(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 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(pos0.y); if (row0 >= 0 && row0 < corkscrew_two_turns.cylinderHeight()) { row_counts[row0]++; } int row1 = static_cast(pos1.y); if (row1 >= 0 && row1 < corkscrew_two_turns.cylinderHeight()) { row_counts[row1]++; } int row2 = static_cast(pos2.y); if (row2 >= 0 && row2 < corkscrew_two_turns.cylinderHeight()) { row_counts[row2]++; } int row3 = static_cast(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 led_buffer(16); fl::span 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& 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 object with a checkerboard pattern fl::Grid 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 led_buffer(12); fl::span 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 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 led_buffer(6); fl::span 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(corkscrew.cylinderWidth()) * static_cast(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(width)); REQUIRE(pos0_unwrapped.y <= 1.0f); // Wrapped positions should be within the cylinder width REQUIRE(pos0_wrapped.x < static_cast(width)); REQUIRE(pos1_wrapped.x < static_cast(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); } } }