FastLED 3.10.8
Loading...
Searching...
No Matches
corkscrew.cpp.hpp
Go to the documentation of this file.
1#include "fl/gfx/corkscrew.h"
2#include "fl/stl/algorithm.h"
3#include "fl/stl/assert.h"
4#include "fl/math/math.h"
5#include "fl/gfx/splat.h"
6#include "fl/log/log.h"
7#include "fl/gfx/tile2x2.h"
8#include "fl/math/math.h"
10#include "fl/math/math.h"
11#include "fl/gfx/leds.h"
12#include "fl/math/grid.h"
13#include "fl/math/screenmap.h"
14#include "fl/stl/shared_ptr.h" // For shared_ptr
15#include "fl/stl/int.h"
16#include "fl/stl/noexcept.h"
17
18
19
20
21namespace fl {
22
23namespace {
24
25// New helper function to calculate individual LED position
26vec2f calculateLedPositionExtended(fl::u16 ledIndex, fl::u16 numLeds, float totalTurns, const Gap& gapParams, fl::u16 width, fl::u16 height) FL_NO_EXCEPT {
28 FL_UNUSED(totalTurns);
29
30 // Check if gap feature is active AND will actually be triggered
31 bool gapActive = (gapParams.num_leds > 0 && gapParams.gap > 0.0f && numLeds > static_cast<fl::u16>(gapParams.num_leds));
32
33 if (!gapActive) {
34 // Original behavior when no gap or gap never triggers
35 const float ledProgress = static_cast<float>(ledIndex) / static_cast<float>(numLeds - 1);
36 const fl::u16 row = ledIndex / width;
37 const fl::u16 remainder = ledIndex % width;
38 const float alpha = static_cast<float>(remainder) / static_cast<float>(width);
39 const float width_pos = ledProgress * numLeds;
40 const float height_pos = static_cast<float>(row) + alpha;
41 return vec2f(width_pos, height_pos);
42 }
43
44 // Simplified gap calculation based on user expectation
45 // User wants: LED0=0, LED1=3, LED2=6(wraps to 0) with width=3
46 // This suggests they want regular spacing of width units per LED
47
48 // Simple spacing: each LED is separated by exactly width units
49 float width_pos = static_cast<float>(ledIndex) * static_cast<float>(width);
50
51 // For height, divide by width to get turn progress
52 float height_pos = width_pos / static_cast<float>(width);
53
54 return vec2f(width_pos, height_pos);
55}
56
57void calculateDimensions(float totalTurns, fl::u16 numLeds, const Gap& gapParams, fl::u16 *width, fl::u16 *height) FL_NO_EXCEPT {
58 FL_UNUSED(gapParams);
59
60 // Calculate optimal width and height
61 float ledsPerTurn = static_cast<float>(numLeds) / totalTurns;
62 fl::u16 calc_width = static_cast<fl::u16>(fl::ceil(ledsPerTurn));
63
64 fl::u16 height_from_turns = static_cast<fl::u16>(fl::ceil(totalTurns));
65 fl::u16 calc_height;
66
67 // If the grid would have more pixels than LEDs, adjust height to better match
68 if (calc_width * height_from_turns > numLeds) {
69 // Calculate height that better matches LED count
70 calc_height = static_cast<fl::u16>(fl::ceil(static_cast<float>(numLeds) / static_cast<float>(calc_width)));
71 } else {
72 calc_height = height_from_turns;
73 }
74
75 *width = calc_width;
76 *height = calc_height;
77}
78
79} // namespace
80
81// New primary constructor
82Corkscrew::Corkscrew(float totalTurns, fl::u16 numLeds, bool invert, const Gap& gapParams)
83 FL_NO_EXCEPT : mTotalTurns(totalTurns), mNumLeds(numLeds), mGapParams(gapParams), mInvert(invert) {
84 fl::calculateDimensions(mTotalTurns, mNumLeds, mGapParams, &mWidth, &mHeight);
85 mOwnsPixels = false;
86}
87
88// Constructor with external pixel buffer
89Corkscrew::Corkscrew(float totalTurns, fl::span<CRGB> dstPixels, bool invert, const Gap& gapParams)
90 FL_NO_EXCEPT : mTotalTurns(totalTurns), mNumLeds(static_cast<fl::u16>(dstPixels.size())),
91 mGapParams(gapParams), mInvert(invert) {
92 fl::calculateDimensions(mTotalTurns, mNumLeds, mGapParams, &mWidth, &mHeight);
93 mPixelStorage = dstPixels;
94 mOwnsPixels = false; // External span
95}
96
97
98
100 if (i >= mNumLeds) {
101 // Handle out-of-bounds access, possibly by returning a default value
102 return vec2f(0, 0);
103 }
104
105 // Compute position on-the-fly
106 vec2f position = calculateLedPositionExtended(i, mNumLeds, mTotalTurns,
108
109 // // Apply inversion if requested
110 // if (mInvert) {
111 // fl::u16 invertedIndex = mNumLeds - 1 - i;
112 // position = calculateLedPositionExtended(invertedIndex, mNumLeds, mTotalTurns,
113 // mGapParams, mState.width, mState.height);
114 // }
115
116 // now wrap the x-position
117 //position.x = fl::fmodf(position.x, static_cast<float>(mState.width));
118
119 return position;
120}
121
123 // Get the unwrapped position
124 vec2f position = at_no_wrap(i);
125
126 // Apply cylindrical wrapping to the x-position (like at_wrap does)
127 position.x = fl::fmodf(position.x, static_cast<float>(mWidth));
128
129 return position;
130}
131
132
134 if (i >= mNumLeds) {
135 // Handle out-of-bounds access, possibly by returning a default
136 // Tile2x2_u8
137 FASTLED_ASSERT(false, "Out of bounds access in Corkscrew at_splat");
138 return Tile2x2_u8();
139 }
140
141 // Use the splat function to convert the vec2f to a Tile2x2_u8
142 float i_floor = fl::floorf(i);
143 float i_ceil = fl::ceilf(i);
144 if (fl::almost_equal(i_floor, i_ceil)) {
145 // If the index is the same, just return the splat of that index
146 vec2f position = at_no_wrap(static_cast<fl::u16>(i_floor));
147 return splat(position);
148 } else {
149 // Interpolate between the two points and return the splat of the result
150 vec2f pos1 = at_no_wrap(static_cast<fl::u16>(i_floor));
151 vec2f pos2 = at_no_wrap(static_cast<fl::u16>(i_ceil));
152 float t = i - i_floor;
153 vec2f interpolated_pos = map_range(t, 0.0f, 1.0f, pos1, pos2);
154 return splat(interpolated_pos);
155 }
156}
157
158fl::size Corkscrew::size() const FL_NO_EXCEPT { return mNumLeds; }
159
160
162 if (mCachingEnabled) {
163 // Use cache if enabled
165
166 // Convert float index to integer for cache lookup
167 fl::size cache_index = static_cast<fl::size>(i);
168 if (cache_index < mTileCache.size()) {
169 return mTileCache[cache_index];
170 }
171 }
172
173 // Fall back to dynamic calculation if cache disabled or index out of bounds
174 return calculateTileAtWrap(i);
175}
176
178 // This is a splatted pixel, but wrapped around the cylinder.
179 // This is useful for rendering the corkscrew in a cylindrical way.
182 vec2<u16> origin = tile.origin();
183 for (fl::u8 x = 0; x < 2; x++) {
184 for (fl::u8 y = 0; y < 2; y++) {
185 // For each pixel in the tile, map it to the cylinder so that each subcomponent
186 // is mapped to the correct position on the cylinder.
187 vec2<u16> pos = origin + vec2<u16>(x, y);
188 // now wrap the x-position
189 pos.x = fl::fmodf(pos.x, static_cast<float>(mWidth));
190 data[x][y] = {pos, tile.at(x, y)};
191 }
192 }
193 return Tile2x2_u8_wrap(data);
194}
195
197 if (!enabled && mCachingEnabled) {
198 // Caching was enabled, now disabling - clear the cache
199 mTileCache.clear();
200 mCacheInitialized = false;
201 }
202 mCachingEnabled = enabled;
203}
204
207 // Initialize cache with tiles for each LED position
208 mTileCache.resize(mNumLeds);
209
210 // Populate cache lazily
211 for (fl::size i = 0; i < mNumLeds; ++i) {
212 mTileCache[i] = calculateTileAtWrap(static_cast<float>(i));
213 }
214
215 mCacheInitialized = true;
216 }
217}
218
220 // Use variant storage if available, otherwise fall back to input surface
221 if (!mPixelStorage.empty()) {
222 if (mPixelStorage.template is<fl::span<CRGB>>()) {
223 return mPixelStorage.template get<fl::span<CRGB>>().data();
224 } else if (mPixelStorage.template is<fl::vector_psram<CRGB>>()) {
225 return mPixelStorage.template get<fl::vector_psram<CRGB>>().data();
226 }
227 }
228
229 // Fall back to input surface data
231 return surface->span().data();
232}
233
235 // Use variant storage if available, otherwise fall back to input surface
236 if (!mPixelStorage.empty()) {
237 if (mPixelStorage.template is<fl::span<CRGB>>()) {
238 return mPixelStorage.template get<fl::span<CRGB>>();
239 } else if (mPixelStorage.template is<fl::vector_psram<CRGB>>()) {
240 auto& vec = mPixelStorage.template get<fl::vector_psram<CRGB>>();
241 return vec;
242 }
243 }
244
245 // Fall back to input surface data as span
247 return surface->span();
248}
249
250
251void Corkscrew::readFrom(const fl::Grid<CRGB>& source_grid, bool use_multi_sampling) FL_NO_EXCEPT {
252
253 if (use_multi_sampling) {
254 readFromMulti(source_grid);
255 return;
256 }
257
258 // Get or create the input surface
259 auto target_surface = getOrCreateInputSurface();
260
261 // Clear surface first
262 target_surface->clear();
263
264 // Iterate through each LED in the corkscrew
265 for (fl::size led_idx = 0; led_idx < mNumLeds; ++led_idx) {
266 // Get the rectangular coordinates for this corkscrew LED
267 vec2f rect_pos = at_no_wrap(static_cast<fl::u16>(led_idx));
268
269 // Convert to integer coordinates for indexing
270 vec2i16 coord(static_cast<fl::i16>(rect_pos.x + 0.5f),
271 static_cast<fl::i16>(rect_pos.y + 0.5f));
272
273 // Clamp coordinates to grid bounds
274 coord.x = fl::max(0, fl::min(coord.x, static_cast<fl::i16>(source_grid.width()) - 1));
275 coord.y = fl::max(0, fl::min(coord.y, static_cast<fl::i16>(source_grid.height()) - 1));
276
277 // Sample from the source fl::Grid using its at() method
278 CRGB sampled_color = source_grid.at(coord.x, coord.y);
279
280 // Store the sampled color directly in the target surface
281 if (led_idx < target_surface->size()) {
282 target_surface->span()[led_idx] = sampled_color;
283 }
284 }
285}
286
288 // Clear input surface if it exists
289 if (mInputSurface) {
290 mInputSurface->clear();
291 // Don't reset the shared_ptr - the test expects the surface to remain valid
292 // The surface is cleared but the memory is not freed
293 }
294
295 // Clear pixel storage if we own it (vector variant)
296 if (!mPixelStorage.empty()) {
297 if (mPixelStorage.template is<fl::vector_psram<CRGB>>()) {
298 auto& vec = mPixelStorage.template get<fl::vector_psram<CRGB>>();
299 vec.clear();
300 // Note: fl::vector doesn't have shrink_to_fit(), but clear() frees the memory
301 }
302 // Note: Don't clear external spans as we don't own that memory
303 }
304
305 // Clear tile cache
306 mTileCache.clear();
307 // Note: fl::vector doesn't have shrink_to_fit(), but clear() frees the memory
308 mCacheInitialized = false;
309}
310
312 auto target_surface = getOrCreateInputSurface();
313 for (fl::size i = 0; i < target_surface->size(); ++i) {
314 target_surface->span()[i] = color;
315 }
316}
317
318void Corkscrew::draw(bool use_multi_sampling) FL_NO_EXCEPT {
319 // The draw method should map from the rectangular surface to the LED pixel data
320 // This is the reverse of readFrom - we read from our surface and populate LED data
321 auto source_surface = getOrCreateInputSurface();
322
323 // Make sure we have pixel storage
324 if (mPixelStorage.empty()) {
325 // If no pixel storage is configured, there's nothing to draw to
326 return;
327 }
328
329 CRGB* led_data = rawData();
330 if (!led_data) return;
331
332 if (use_multi_sampling) {
333 // Use multi-sampling to get better accuracy
334 for (fl::size led_idx = 0; led_idx < mNumLeds; ++led_idx) {
335 // Get the wrapped tile for this LED position
336 Tile2x2_u8_wrap tile = at_wrap(static_cast<float>(led_idx));
337
338 // Accumulate color from the 4 sample points with their weights
339 fl::u32 r_accum = 0, g_accum = 0, b_accum = 0;
340 fl::u32 total_weight = 0;
341
342 // Sample from each of the 4 corners of the tile
343 for (fl::u8 x = 0; x < 2; x++) {
344 for (fl::u8 y = 0; y < 2; y++) {
345 const auto& entry = tile.at(x, y);
346 vec2<u16> pos = entry.first;
347 fl::u8 weight = entry.second;
348
349 // Bounds check for the source surface
350 if (pos.x < source_surface->width() && pos.y < source_surface->height()) {
351 // Sample from the source surface
352 CRGB sample_color = source_surface->at(pos.x, pos.y);
353
354 // Accumulate weighted color components
355 r_accum += static_cast<fl::u32>(sample_color.r) * weight;
356 g_accum += static_cast<fl::u32>(sample_color.g) * weight;
357 b_accum += static_cast<fl::u32>(sample_color.b) * weight;
358 total_weight += weight;
359 }
360 }
361 }
362
363 // Calculate final color by dividing by total weight
364 CRGB final_color = CRGB::Black;
365 if (total_weight > 0) {
366 final_color.r = static_cast<fl::u8>(r_accum / total_weight);
367 final_color.g = static_cast<fl::u8>(g_accum / total_weight);
368 final_color.b = static_cast<fl::u8>(b_accum / total_weight);
369 }
370
371 // Store the result in the LED data
372 led_data[led_idx] = final_color;
373 }
374 } else {
375 // Simple non-multi-sampling version
376 for (fl::size led_idx = 0; led_idx < mNumLeds; ++led_idx) {
377 // Get the rectangular coordinates for this corkscrew LED
378 vec2f rect_pos = at_no_wrap(static_cast<fl::u16>(led_idx));
379
380 // Convert to integer coordinates for indexing
381 vec2i16 coord(static_cast<fl::i16>(rect_pos.x + 0.5f),
382 static_cast<fl::i16>(rect_pos.y + 0.5f));
383
384 // Clamp coordinates to surface bounds
385 coord.x = fl::max(0, fl::min(coord.x, static_cast<fl::i16>(source_surface->width()) - 1));
386 coord.y = fl::max(0, fl::min(coord.y, static_cast<fl::i16>(source_surface->height()) - 1));
387
388 // Sample from the source surface
389 CRGB sampled_color = source_surface->at(coord.x, coord.y);
390
391 // Store the sampled color in the LED data
392 led_data[led_idx] = sampled_color;
393 }
394 }
395}
396
398 // Get the target surface and clear it
399 auto target_surface = const_cast<Corkscrew*>(this)->getOrCreateInputSurface();
400 target_surface->clear();
401 const u16 width = static_cast<u16>(source_grid.width());
402 const u16 height = static_cast<u16>(source_grid.height());
403
404 // Iterate through each LED in the corkscrew
405 for (fl::size led_idx = 0; led_idx < mNumLeds; ++led_idx) {
406 // Get the wrapped tile for this LED position
407 Tile2x2_u8_wrap tile = at_wrap(static_cast<float>(led_idx));
408
409 // Accumulate color from the 4 sample points with their weights
410 fl::u32 r_accum = 0, g_accum = 0, b_accum = 0;
411 fl::u32 total_weight = 0;
412
413 // Sample from each of the 4 corners of the tile
414 for (fl::u8 x = 0; x < 2; x++) {
415 for (fl::u8 y = 0; y < 2; y++) {
416 const auto& entry = tile.at(x, y);
417 vec2<u16> pos = entry.first; // position is the first element of the pair
418 fl::u8 weight = entry.second; // weight is the second element of the pair
419
420 // Bounds check for the source grid
421 if (pos.x < width && pos.y < height) {
422
423 // Sample from the source grid
424 CRGB sample_color = source_grid.at(pos.x, pos.y);
425
426 // Accumulate weighted color components
427 r_accum += static_cast<fl::u32>(sample_color.r) * weight;
428 g_accum += static_cast<fl::u32>(sample_color.g) * weight;
429 b_accum += static_cast<fl::u32>(sample_color.b) * weight;
430 total_weight += weight;
431 }
432 }
433 }
434
435 // Calculate final color by dividing by total weight
436 CRGB final_color = CRGB::Black;
437 if (total_weight > 0) {
438 final_color.r = static_cast<fl::u8>(r_accum / total_weight);
439 final_color.g = static_cast<fl::u8>(g_accum / total_weight);
440 final_color.b = static_cast<fl::u8>(b_accum / total_weight);
441 }
442
443 // Store the result in the target surface at the LED index position
444 auto target_surface = const_cast<Corkscrew*>(this)->getOrCreateInputSurface();
445 if (led_idx < target_surface->size()) {
446 target_surface->span()[led_idx] = final_color;
447 }
448 }
449}
450
451// Iterator implementation
453 return mCorkscrew->at_no_wrap(static_cast<fl::u16>(mPosition));
454}
455
457 // Create a ScreenMap with the correct number of LEDs
459
460 // For each LED index, calculate its position and set it in the ScreenMap
461 for (fl::u16 i = 0; i < mNumLeds; ++i) {
462 // Get the wrapped 2D position for this LED index in the cylindrical mapping
463 vec2f position = at_exact(i);
464
465 // Set the wrapped position in the ScreenMap
466 screenMap.set(i, position);
467 }
468
469 return screenMap;
470}
471
472// Enhanced surface handling methods
474 if (!mInputSurface) {
475 // Create a new Grid with cylinder dimensions using PSRAM allocation
477 }
478 return mInputSurface;
479}
480
484
485
487 // Use variant storage if available, otherwise fall back to legacy buffer size
488 if (!mPixelStorage.empty()) {
489 if (mPixelStorage.template is<fl::span<CRGB>>()) {
490 return mPixelStorage.template get<fl::span<CRGB>>().size();
491 } else if (mPixelStorage.template is<fl::vector_psram<CRGB>>()) {
492 return mPixelStorage.template get<fl::vector_psram<CRGB>>().size();
493 }
494 }
495
496 // Fall back to input size
497 return mNumLeds;
498}
499
500
501
502} // namespace fl
uint8_t pos
Definition Blur.ino:11
vec2f operator*() const FL_NO_EXCEPT
const Corkscrew * mCorkscrew
Definition corkscrew.h:137
Corkscrew(float totalTurns, fl::u16 numLeds, bool invert=false, const Gap &gapParams=Gap()) FL_NO_EXCEPT
void setCachingEnabled(bool enabled) FL_NO_EXCEPT
fl::ScreenMap toScreenMap(float diameter=0.5f) const FL_NO_EXCEPT
fl::u16 mWidth
Definition corkscrew.h:228
fl::u16 mNumLeds
Definition corkscrew.h:223
void draw(bool use_multi_sampling=true) FL_NO_EXCEPT
fl::span< CRGB > data() FL_NO_EXCEPT
bool mCacheInitialized
Definition corkscrew.h:240
void fillInputSurface(const CRGB &color) FL_NO_EXCEPT
void initializeCache() const FL_NO_EXCEPT
vec2f at_no_wrap(fl::u16 i) const FL_NO_EXCEPT
float mTotalTurns
Definition corkscrew.h:222
fl::shared_ptr< fl::Grid< CRGB > > & getOrCreateInputSurface() FL_NO_EXCEPT
fl::u16 mHeight
Definition corkscrew.h:229
fl::size size() const FL_NO_EXCEPT
PixelStorage mPixelStorage
Definition corkscrew.h:232
fl::shared_ptr< fl::Grid< CRGB > > mInputSurface
Definition corkscrew.h:236
fl::vector< Tile2x2_u8_wrap > mTileCache
Definition corkscrew.h:239
void clear() FL_NO_EXCEPT
CRGB * rawData() FL_NO_EXCEPT
Tile2x2_u8_wrap at_wrap(float i) const FL_NO_EXCEPT
fl::size pixelCount() const FL_NO_EXCEPT
vec2f at_exact(fl::u16 i) const FL_NO_EXCEPT
void readFromMulti(const fl::Grid< CRGB > &target_grid) const FL_NO_EXCEPT
fl::Grid< CRGB > & surface() FL_NO_EXCEPT
Tile2x2_u8_wrap calculateTileAtWrap(float i) const FL_NO_EXCEPT
Tile2x2_u8 at_splat_extrapolate(float i) const FL_NO_EXCEPT
void readFrom(const fl::Grid< CRGB > &source_grid, bool use_multi_sampling=true) FL_NO_EXCEPT
bool mCachingEnabled
Definition corkscrew.h:241
Entry & at(u16 x, u16 y) FL_NO_EXCEPT
fl::pair< vec2< u16 >, u8 > Entry
Definition tile2x2.h:103
u8 & at(int x, int y) FL_NO_EXCEPT
Definition tile2x2.h:34
vec2< u16 > origin() const FL_NO_EXCEPT
Definition tile2x2.h:51
#define FL_UNUSED(x)
fl::ScreenMap screenMap
Definition Corkscrew.h:101
Centralized logging categories for FastLED hardware interfaces and subsystems.
shared_ptr< T > make_shared(Args &&... args) FL_NO_EXCEPT
Definition shared_ptr.h:423
vec2f calculateLedPositionExtended(fl::u16 ledIndex, fl::u16 numLeds, float totalTurns, const Gap &gapParams, fl::u16 width, fl::u16 height) FL_NO_EXCEPT
void calculateDimensions(float totalTurns, fl::u16 numLeds, const Gap &gapParams, fl::u16 *width, fl::u16 *height) FL_NO_EXCEPT
unsigned char u8
Definition stdint.h:131
constexpr common_type_t< T, U > max(T a, U b) FL_NO_EXCEPT
Definition math.h:75
u8 u8 fract8 blur_amount FL_NO_EXCEPT
Definition blur.h:186
constexpr bool almost_equal(T a, T b, U tolerance) FL_NO_EXCEPT
Definition math.h:98
u8 u8 height
Definition blur.h:186
FL_DISABLE_WARNING_PUSH U constexpr common_type_t< T, U > min(T a, U b) FL_NO_EXCEPT
Definition math.h:71
vec2< float > vec2f
Definition geometry.h:333
Tile2x2_u8 splat(vec2f xy) FL_NO_EXCEPT
"Splat" as in "splat pixel rendering" takes a pixel value in float x,y coordinates and "splats" it in...
Definition splat.cpp.hpp:15
constexpr enable_if< is_fixed_point< T >::value, T >::type ceil(T x) FL_NO_EXCEPT
FASTLED_FORCE_INLINE U map_range(T value, T in_min, T in_max, U out_min, U out_max) FL_NO_EXCEPT
Definition math.h:182
u8 width
Definition blur.h:186
vec2< i16 > vec2i16
Definition geometry.h:335
pair_element< I, T1, T2 >::type & get(pair< T1, T2 > &p) FL_NO_EXCEPT
Definition pair.h:119
float fmodf(float x, float y) FL_NO_EXCEPT
Definition math.h:344
float floorf(float value) FL_NO_EXCEPT
Definition math.h:312
float ceilf(float value) FL_NO_EXCEPT
Definition math.h:318
Base definition for an LED controller.
Definition crgb.hpp:179
Corkscrew LED strip projection and rendering.
@ Black
<div style='background:#000000;width:4em;height:4em;'></div>
Definition crgb.h:517
Representation of an 8-bit RGB pixel (Red, Green, Blue)
Definition crgb.h:38
Struct representing gap parameters for corkscrew mapping.
Definition corkscrew.h:64
value_type y
Definition geometry.h:191
value_type x
Definition geometry.h:190