FastLED 3.9.15
Loading...
Searching...
No Matches
corkscrew.cpp
Go to the documentation of this file.
1#include "fl/corkscrew.h"
2#include "fl/algorithm.h"
3#include "fl/assert.h"
4#include "fl/math.h"
5#include "fl/splat.h"
6
7#include "fl/math_macros.h"
8
9#define TWO_PI (PI * 2.0)
10
11namespace fl {
12
13void generateState(const Corkscrew::Input &input, CorkscrewState *output);
14
15void generateState(const Corkscrew::Input &input, CorkscrewState *output) {
16
17 // Calculate vertical segments based on number of turns
18 // For a single turn (2π), we want exactly 1 vertical segment
19 // For two turns (4π), we want exactly 2 vertical segments
20 // uint16_t verticalSegments = ceil(input.totalTurns);
21
22 // Calculate width based on LED density per turn
23 // float ledsPerTurn = static_cast<float>(input.numLeds) / verticalSegments;
24
25 output->mapping.clear();
26 output->width = 0; // we will change this below.
27 output->height = 0;
28
29 // If numLeds is specified, use that for mapping size instead of grid
30 output->mapping.reserve(input.numLeds);
31 // Generate LED mapping based on numLeds
32 // Note that width_step should be 1.0f/float(input.numLeds) so last led in a
33 // turn does not wrap around.
34 const float width_step =
35 1.0f / float(input.numLeds); // Corkscrew reaches max width on last led.
36 const float height_step =
37 1.0f /
38 float(input.numLeds - 1); // Corkscrew reaches max height on last led.
39 // const float led_width_factor = circumferencePerTurn / TWO_PI;
40 const float length_per_turn = input.numLeds / input.totalTurns;
41
42 for (uint16_t i = 0; i < input.numLeds; ++i) {
43 // Calculate position along the corkscrew (0.0 to 1.0)
44 const float i_f = static_cast<float>(i);
45 const float alpha_width = i_f * width_step;
46 const float alpha_height = i_f * height_step;
47 const float width_before_mod = alpha_width * input.totalLength;
48 const float height = alpha_height * input.totalHeight;
49 const float width = fmodf(width_before_mod, length_per_turn);
50 output->mapping.push_back({width, height});
51 }
52
53 if (!output->mapping.empty()) {
54 float max_width = 0.0f;
55 float max_height = 0.0f;
56 for (const auto &point : output->mapping) {
57 max_width = MAX(max_width, point.x);
58 max_height = MAX(max_height, point.y);
59 }
60 output->width = static_cast<uint16_t>(ceilf(max_width)) + 1;
61 output->height = static_cast<uint16_t>(ceilf(max_height)) + 1;
62 }
63
64 // Apply inversion if requested
65 if (input.invert) {
66 fl::reverse(output->mapping.begin(), output->mapping.end());
67 }
68}
69
73
74vec2f Corkscrew::at_exact(uint16_t i) const {
75 if (i >= mState.mapping.size()) {
76 // Handle out-of-bounds access, possibly by returning a default value
77 return vec2f(0, 0);
78 }
79 // Convert the float position to integer
80 const vec2f &position = mState.mapping[i];
81 return position;
82}
83
84
86 // To finish this, we need to handle wrap around.
87 // To accomplish this we need a different data structure than the the
88 // Tile2x2_u8.
89 // 1. It will be called CorkscrewTile2x2_u8.
90 // 2. The four alpha values will each contain the index the LED is at,
91 // uint16_t.
92 // 3. There will be no origin, each pixel in the tile will contain a
93 // uint16_t origin. This is not supposed to be a storage format, but a
94 // convenient pre-computed value for rendering.
95 if (i >= mState.mapping.size()) {
96 // Handle out-of-bounds access, possibly by returning a default
97 // Tile2x2_u8
98 FASTLED_ASSERT(false, "Out of bounds access in Corkscrew at_splat: "
99 << i << " size: " << mState.mapping.size());
100 return Tile2x2_u8();
101 }
102 // Use the splat function to convert the vec2f to a Tile2x2_u8
103 float i_floor = floorf(i);
104 float i_ceil = ceilf(i);
105 if (ALMOST_EQUAL_FLOAT(i_floor, i_ceil)) {
106 // If the index is the same, just return the splat of that index
107 return splat(mState.mapping[static_cast<uint16_t>(i_floor)]);
108 } else {
109 // Interpolate between the two points and return the splat of the result
110 vec2f pos1 = mState.mapping[static_cast<uint16_t>(i_floor)];
111 vec2f pos2 = mState.mapping[static_cast<uint16_t>(i_ceil)];
112
113 if (pos2.x < pos1.x) {
114 // If the next point is on the other side of the cylinder, we need
115 // to wrap it around and bring it back into the positive direction so we can construct a Tile2x2_u8 wrap with it.
116 pos2.x += mState.width;
117 }
118
119 vec2f interpolated_pos =
120 pos1 * (1.0f - (i - i_floor)) + pos2 * (i - i_floor);
121 return splat(interpolated_pos);
122 }
123}
124
125size_t Corkscrew::size() const { return mState.mapping.size(); }
126
128 CorkscrewState output;
129 fl::generateState(input, &output);
130 return output;
131}
132
133
134
136 // This is a splatted pixel, but wrapped around the cylinder.
137 // This is useful for rendering the corkscrew in a cylindrical way.
139 return Tile2x2_u8_wrap(tile, mState.width, mState.height);
140}
141
142} // namespace fl
size_t size() const
CorkscrewState State
Definition corkscrew.h:136
Tile2x2_u8 at_splat_extrapolate(float i) const
Definition corkscrew.cpp:85
vec2f at_exact(uint16_t i) const
Definition corkscrew.cpp:74
Tile2x2_u8_wrap at_wrap(float i) const
CorkscrewInput Input
Definition corkscrew.h:135
Corkscrew(const Input &input)
Definition corkscrew.cpp:70
static State generateState(const Input &input)
For testing.
#define ALMOST_EQUAL_FLOAT(a, b)
Definition math_macros.h:37
#define MAX(a, b)
Definition math_macros.h:11
Tile2x2_u8 splat(vec2f xy)
"Splat" as in "splat pixel rendering" takes a pixel value in float x,y coordinates and "splats" it in...
Definition splat.cpp:14
void generateState(const Corkscrew::Input &input, CorkscrewState *output)
Definition corkscrew.cpp:15
vec2< float > vec2f
Definition geometry.h:318
void reverse(Iterator first, Iterator last)
Definition algorithm.h:8
Implements a simple red square effect for 2D LED grids.
Definition crgb.h:16
Corkscrew projection utilities.
fl::vector< fl::vec2f, fl::allocator_psram< fl::vec2f > > mapping
Definition corkscrew.h:71