FastLED 3.9.15
Loading...
Searching...
No Matches
screenmap.cpp
Go to the documentation of this file.
1/* Screenmap maps strip indexes to x,y coordinates. This is used for FastLED Web
2 * to map the 1D strip to a 2D grid. Note that the strip can have arbitrary
3 * size. this was first motivated during the (attempted? Oct. 19th 2024) port of
4 * the Chromancer project to FastLED Web.
5 */
6
7#include "fl/screenmap.h"
8
9#include "fl/json.h"
10#include "fl/map.h"
11#include "fl/math.h"
12#include "fl/math_macros.h"
13#include "fl/namespace.h"
14#include "fl/screenmap.h"
15#include "fl/str.h"
16#include "fl/vector.h"
17#include "fl/warn.h"
18
19
20namespace fl {
21
22// Helper function to extract a vector of floats from a JSON array
25
26 if (!jsonArray.has_value() || !jsonArray.is_array()) {
27 return result;
28 }
29 auto begin_float = jsonArray.begin_array<float>();
30 auto end_float = jsonArray.end_array<float>();
31
32 using T = decltype(*begin_float);
33 static_assert(fl::is_same<T, fl::ParseResult<float>>::value, "Value type must be ParseResult<float>");
34
35 // Use explicit array iterator style as demonstrated in FEATURE.md
36 // DO NOT CHANGE THIS CODE. FIX THE IMPLIMENTATION IF NECESSARY.
37 for (auto it = begin_float; it != end_float; ++it) {
38 // assert that the value type is ParseResult<float>
39
40 // get the name of the type
41 auto parseResult = *it;
42 if (!parseResult.has_error()) {
43 result.push_back(parseResult.get_value());
44 } else {
45 FL_WARN("jsonArrayToFloatVector: ParseResult<float> has error: " << parseResult.get_error().message);
46 }
47 }
48
49 return result;
50}
51
52ScreenMap ScreenMap::Circle(int numLeds, float cm_between_leds,
53 float cm_led_diameter, float completion) {
54 ScreenMap screenMap(numLeds);
55
56 // radius from LED spacing
57 float circumference = numLeds * cm_between_leds;
58 float radius = circumference / (2 * PI);
59
60 // how big an arc we light vs leave dark
61 float totalAngle = completion * 2 * PI;
62 float gapAngle = 2 * PI - totalAngle;
63
64 // shift so the dark gap is centered at the bottom (–π/2)
65 float startAngle = -PI / 2 + gapAngle / 2.0f;
66
67 // if partial, land last LED exactly at startAngle+totalAngle
68 float divisor =
69 (completion < 1.0f && numLeds > 1) ? (numLeds - 1) : numLeds;
70
71 for (int i = 0; i < numLeds; ++i) {
72 float angle = startAngle + (i * totalAngle) / divisor;
73 float x = radius * cos(angle) * 2;
74 float y = radius * sin(angle) * 2;
75 screenMap[i] = {x, y};
76 }
77
78 screenMap.setDiameter(cm_led_diameter);
79 return screenMap;
80}
81
82bool ScreenMap::ParseJson(const char *jsonStrScreenMap,
83 fl::fl_map<string, ScreenMap> *segmentMaps, string *err) {
84
85#if FASTLED_NO_JSON
86 FL_UNUSED(jsonStrScreenMap);
87 FL_UNUSED(segmentMaps);
88 FL_UNUSED(err);
89 FL_WARN("ScreenMap::ParseJson called with FASTLED_NO_JSON");
90 if (err) {
91 *err = "JSON is not supported in this build";
92 }
93 return false;
94#else
95 //FL_WARN_SCREENMAP("ParseJson called with JSON: " << jsonStrScreenMap);
96
97 string _err;
98 if (!err) {
99 err = &_err;
100 }
101
102 auto jsonDoc = fl::Json::parse(jsonStrScreenMap);
103 if (!jsonDoc.has_value()) {
104 *err = "Failed to parse JSON";
105 FL_WARN("Failed to parse JSON");
106 return false;
107 }
108
109 if (!jsonDoc.is_object()) {
110 *err = "JSON root is not an object";
111 FL_WARN("JSON root is not an object");
112 return false;
113 }
114
115 // Check if "map" key exists and is an object
116 if (!jsonDoc.contains("map")) {
117 *err = "Missing 'map' key in JSON";
118 FL_WARN("Missing 'map' key in JSON");
119 return false;
120 }
121
122 // Get the map object
123 auto mapObj = jsonDoc["map"];
124 if (!mapObj.has_value() || !mapObj.is_object()) {
125 *err = "Invalid 'map' object in JSON";
126 FL_WARN("Invalid 'map' object in JSON");
127 return false;
128 }
129
130 auto jsonMapOpt = mapObj.as_object();
131 if (!jsonMapOpt || jsonMapOpt->empty()) {
132 *err = "Failed to parse map from JSON or map is empty";
133 FL_WARN("Failed to parse map from JSON or map is empty");
134 return false;
135 }
136
137 auto& jsonMap = *jsonMapOpt;
138
139
140 for (const auto& kv : jsonMap) {
141 auto name = kv.first;
142
143
144 // Check that the value is not null before creating Json object
145 if (!kv.second) {
146 *err = "Null value for segment " + name;
147 return false;
148 }
149
150 // Create Json object directly from shared_ptr
151 fl::Json val(kv.second);
152 if (!val.has_value()) {
153 *err = "Invalid value for segment " + name;
154 return false;
155 }
156
157 if (!val.is_object()) {
158 *err = "Segment value for " + name + " is not an object";
159 return false;
160 }
161
162 // Check if x array exists and is actually an array
163 if (!val.contains("x")) {
164 *err = "Missing x array for " + name;
165 return false;
166 }
167
168 if (!val["x"].has_value() || !val["x"].is_array()) {
169 *err = "Invalid x array for " + name;
170 return false;
171 }
172
173 // Extract x array using our helper function
174 fl::vector<float> x_array = jsonArrayToFloatVector(val["x"]);
175
176 // Check if y array exists and is actually an array
177 if (!val.contains("y")) {
178 *err = "Missing y array for " + name;
179 return false;
180 }
181
182 if (!val["y"].has_value() || !val["y"].is_array()) {
183 *err = "Invalid y array for " + name;
184 return false;
185 }
186
187 // Extract y array using our helper function
188 fl::vector<float> y_array = jsonArrayToFloatVector(val["y"]);
189
190 // Get diameter (optional) with default value
191 float diameter = -1.0f; // default value
192 if (val.contains("diameter") && val["diameter"].has_value()) {
193 auto diameterOpt = val["diameter"].as_float();
194 if (diameterOpt) {
195 diameter = static_cast<float>(*diameterOpt);
196 }
197 }
198
199 auto n = MIN(x_array.size(), y_array.size());
200 if (n != x_array.size() || n != y_array.size()) {
201 if (n != x_array.size()) {
202 }
203 if (n != y_array.size()) {
204 }
205 }
206
207 ScreenMap segment_map(n, diameter);
208 for (size_t i = 0; i < n; i++) {
209 segment_map.set(i, vec2f{x_array[i], y_array[i]});
210 }
211 (*segmentMaps)[name] = fl::move(segment_map);
212 }
213 return true;
214#endif
215}
216
217bool ScreenMap::ParseJson(const char *jsonStrScreenMap,
218 const char *screenMapName, ScreenMap *screenmap,
219 string *err) {
220
222 bool ok = ParseJson(jsonStrScreenMap, &segmentMaps, err);
223 if (!ok) {
224 return false;
225 }
226 if (segmentMaps.size() == 0) {
227 return false;
228 }
229 if (segmentMaps.contains(screenMapName)) {
230 *screenmap = segmentMaps[screenMapName];
231 return true;
232 }
233 string _err = "ScreenMap not found: ";
234 _err.append(screenMapName);
235 if (err) {
236 *err = _err;
237 }
238
239 return false;
240}
241
243 fl::Json *doc) {
244
245#if FASTLED_NO_JSON
246 FL_WARN("ScreenMap::toJson called with FASTLED_NO_JSON");
247 return;
248#else
249 if (!doc) {
250 FL_WARN("ScreenMap::toJson called with nullptr doc");
251 return;
252 }
253
254 // Create the root object
255 *doc = fl::Json::object();
256
257 // Create the map object
258 fl::Json mapObj = fl::Json::object();
259
260 // Populate the map object with segments
261 for (const auto& kv : segmentMaps) {
262 if (kv.second.getLength() == 0) {
263 FL_WARN("ScreenMap::toJson called with empty segment: " << fl::string(kv.first));
264 continue;
265 }
266
267 auto& name = kv.first;
268 auto& segment = kv.second;
269 float diameter = segment.getDiameter();
270
271 // Create x array
272 fl::Json xArray = fl::Json::array();
273 for (u16 i = 0; i < segment.getLength(); i++) {
274 xArray.push_back(fl::Json(static_cast<double>(segment[i].x)));
275 }
276
277 // Create y array
278 fl::Json yArray = fl::Json::array();
279 for (u16 i = 0; i < segment.getLength(); i++) {
280 yArray.push_back(fl::Json(static_cast<double>(segment[i].y)));
281 }
282
283 // Create segment object
284 fl::Json segmentObj = fl::Json::object();
285 // Add arrays and diameter to segment object
286 segmentObj.set("x", xArray);
287 segmentObj.set("y", yArray);
288 segmentObj.set("diameter", fl::Json(static_cast<double>(diameter)));
289
290 // Add segment to map object
291 mapObj.set(name, segmentObj);
292 }
293
294 // Add map object to root
295 doc->set("map", mapObj);
296
297 // Debug output
298 fl::string debugStr = doc->to_string();
299 FL_WARN("ScreenMap::toJson generated JSON: " << debugStr);
300#endif
301}
302
304 string *jsonBuffer) {
305 fl::Json doc;
306 toJson(segmentMaps, &doc);
307 *jsonBuffer = doc.to_string();
308}
309
312 if (length > 0) {
314 LUTXYFLOAT &lut = *mLookUpTable.get();
315 vec2f *data = lut.getDataMutable();
316 for (u32 x = 0; x < length; x++) {
317 data[x] = {0, 0};
318 }
319 }
320}
321
322ScreenMap::ScreenMap(const vec2f *lut, u32 length, float diameter)
323 : length(length), mDiameter(diameter) {
325 LUTXYFLOAT &lut16xy = *mLookUpTable.get();
326 vec2f *data = lut16xy.getDataMutable();
327 for (u32 x = 0; x < length; x++) {
328 data[x] = lut[x];
329 }
330}
331
333 mDiameter = other.mDiameter;
334 length = other.length;
336}
337
339 mDiameter = other.mDiameter;
340 length = other.length;
341 fl::swap(mLookUpTable, other.mLookUpTable);
342 other.mLookUpTable.reset();
343}
344
345void ScreenMap::set(u16 index, const vec2f &p) {
346 if (mLookUpTable) {
347 LUTXYFLOAT &lut = *mLookUpTable.get();
348 auto *data = lut.getDataMutable();
349 data[index] = p;
350 }
351}
352
353void ScreenMap::setDiameter(float diameter) { mDiameter = diameter; }
354
356 if (x >= length || !mLookUpTable) {
357 return {0, 0};
358 }
359 LUTXYFLOAT &lut = *mLookUpTable.get();
360 vec2f screen_coords = lut[x];
361 return screen_coords;
362}
363
364u32 ScreenMap::getLength() const { return length; }
365
366float ScreenMap::getDiameter() const { return mDiameter; }
367
369
370 if (length == 0 || !mLookUpTable) {
371 return {0, 0};
372 }
373
374 LUTXYFLOAT &lut = *mLookUpTable.get();
375
376 fl::vec2f *data = lut.getDataMutable();
377 // float minX = lut[0].x;
378 // float maxX = lut[0].x;
379 // float minY = lut[0].y;
380 // float maxY = lut[0].y;
381 float minX = data[0].x;
382 float maxX = data[0].x;
383 float minY = data[0].y;
384 float maxY = data[0].y;
385
386 for (u32 i = 1; i < length; i++) {
387 const vec2f &p = lut[i];
388 minX = MIN(minX, p.x);
389 maxX = MAX(maxX, p.x);
390 minY = MIN(minY, p.y);
391 maxY = MAX(maxY, p.y);
392 }
393
394 return {maxX - minX, maxY - minY};
395}
396
398 static const vec2f s_empty = vec2f(0, 0);
399 return s_empty;
400}
401
402const vec2f &ScreenMap::operator[](u32 x) const {
403 if (x >= length || !mLookUpTable) {
404 return empty(); // better than crashing.
405 }
406 LUTXYFLOAT &lut = *mLookUpTable.get();
407 return lut[x];
408}
409
411 if (x >= length || !mLookUpTable) {
412 return const_cast<vec2f &>(empty()); // better than crashing.
413 }
414 LUTXYFLOAT &lut = *mLookUpTable.get();
415 auto *data = lut.getDataMutable();
416 return data[x];
417}
418
420 if (this != &other) {
421 mDiameter = other.mDiameter;
422 length = other.length;
424 }
425 return *this;
426}
427
429 if (this != &other) {
430 mDiameter = other.mDiameter;
431 length = other.length;
432 mLookUpTable = fl::move(other.mLookUpTable);
433 other.length = 0;
434 other.mDiameter = -1.0f;
435 }
436 return *this;
437}
438
440 vec2f *data = mLookUpTable->getDataMutable();
441 for (u32 i = 0; i < length; i++) {
442 vec2f &curr = data[i];
443 curr.x += p.x;
444 curr.y += p.y;
445 }
446}
447
448void ScreenMap::addOffsetX(float x) { addOffset({x, 0}); }
449void ScreenMap::addOffsetY(float y) { addOffset({0, y}); }
450
451} // namespace fl
int y
Definition simple.h:93
int x
Definition simple.h:92
fl::size size() const
Definition vector.h:545
bool is_array() const
Definition json.h:1736
fl::string to_string() const
Definition json.h:2120
bool has_value() const
Definition json.h:2107
JsonValue::template array_iterator< T > begin_array()
Definition json.h:1947
JsonValue::template array_iterator< T > end_array()
Definition json.h:1953
bool contains(size_t idx) const
Definition json.h:2078
static Json object()
Definition json.h:2141
void set(const fl::string &key, const Json &value)
Definition json.h:2149
void push_back(const Json &value)
Definition json.h:2172
fl::optional< float > as_float() const
Definition json.h:1756
static Json array()
Definition json.h:2137
static Json parse(const fl::string &txt)
Definition json.h:2126
bool is_object() const
Definition json.h:1738
T * getDataMutable()
Definition lut.h:53
fl::size size() const
Definition rbtree.h:787
bool contains(const Key &key) const
Definition rbtree.h:849
static void toJson(const fl::fl_map< string, ScreenMap > &, fl::Json *doc)
static bool ParseJson(const char *jsonStrScreenMap, fl::fl_map< string, ScreenMap > *segmentMaps, string *err=nullptr)
Definition screenmap.cpp:82
ScreenMap & operator=(const ScreenMap &other)
void setDiameter(float diameter)
float mDiameter
Definition screenmap.h:102
void addOffsetY(float y)
static ScreenMap Circle(int numLeds, float cm_between_leds=1.5f, float cm_led_diameter=0.5f, float completion=1.0f)
Definition screenmap.cpp:52
void set(u16 index, const vec2f &p)
void addOffsetX(float x)
void addOffset(const vec2f &p)
const vec2f & operator[](u32 x) const
static void toJsonStr(const fl::fl_map< string, ScreenMap > &, string *jsonBuffer)
ScreenMap()=default
static const vec2f & empty()
vec2f mapToIndex(u32 x) const
LUTXYFLOATPtr mLookUpTable
Definition screenmap.h:103
vec2f getBounds() const
u32 getLength() const
float getDiameter() const
Result type for promise operations.
string & append(const BitsetFixed< N > &bs)
Definition str.h:675
fl::ScreenMap screenMap
Definition Corkscrew.h:103
FastLED's Elegant JSON Library: fl::Json
#define MIN(a, b)
Definition math_macros.h:41
#define PI
Definition math_macros.h:89
#define MAX(a, b)
Definition math_macros.h:37
Implements the FastLED namespace macros.
constexpr remove_reference< T >::type && move(T &&t) noexcept
Definition move.h:27
void swap(array< T, N > &lhs, array< T, N > &rhs) noexcept(noexcept(lhs.swap(rhs)))
Definition array.h:156
LUT< vec2f > LUTXYFLOAT
Definition lut.h:26
vec2< float > vec2f
Definition geometry.h:333
shared_ptr< T > make_shared(Args &&... args)
Definition shared_ptr.h:348
MapRedBlackTree< Key, T, Compare, fl::allocator_slab< char > > fl_map
Definition map.h:540
HeapVector< T, Allocator > vector
Definition vector.h:1214
fl::vector< float > jsonArrayToFloatVector(const fl::Json &jsonArray)
Definition screenmap.cpp:23
IMPORTANT!
Definition crgb.h:20
value_type y
Definition geometry.h:191
value_type x
Definition geometry.h:190
#define FL_UNUSED(x)
Definition unused.h:8
#define FL_WARN
Definition warn.h:12