FastLED 3.9.3
Loading...
Searching...
No Matches
ripple.h
1/*
2 A dot animation that travels along rails
3 (C) Voidstar Lab LLC 2021
4*/
5
6#ifndef RIPPLE_H_
7#define RIPPLE_H_
8
9// WARNING: These slow things down enough to affect performance. Don't turn on unless you need them!
10//#define DEBUG_ADVANCEMENT // Print debug messages about ripples' movement
11//#define DEBUG_RENDERING // Print debug messages about translating logical to actual position
12
13#include "FastLED.h"
14#include "mapping.h"
15
16enum rippleState {
17 dead,
18 withinNode, // Ripple isn't drawn as it passes through a node to keep the speed consistent
19 travelingUpwards,
20 travelingDownwards
21};
22
23enum rippleBehavior {
24 weaksauce = 0,
25 feisty = 1,
26 angry = 2,
27 alwaysTurnsRight = 3,
28 alwaysTurnsLeft = 4
29};
30
31float fmap(float x, float in_min, float in_max, float out_min, float out_max) {
32 return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
33}
34
35class Ripple {
36 public:
37 Ripple(int id) : rippleId(id) {
38 Serial.print("Instanced ripple #");
39 Serial.println(rippleId);
40 }
41
42 rippleState state = dead;
43 unsigned long color;
44
45 /*
46 If within a node: 0 is node, 1 is direction
47 If traveling, 0 is segment, 1 is LED position from bottom
48 */
49 int position[2];
50
51 // Place the Ripple in a node
52 void start(byte n, byte d, unsigned long c, float s, unsigned long l, byte b) {
53 color = c;
54 speed = s;
55 lifespan = l;
56 behavior = b;
57
58 birthday = millis();
59 pressure = 0;
60 state = withinNode;
61
62 position[0] = n;
63 position[1] = d;
64
65 justStarted = true;
66
67 Serial.print("Ripple ");
68 Serial.print(rippleId);
69 Serial.print(" starting at node ");
70 Serial.print(position[0]);
71 Serial.print(" direction ");
72 Serial.println(position[1]);
73 }
74
75 void advance(byte ledColors[40][14][3]) {
76 unsigned long age = millis() - birthday;
77
78 if (state == dead)
79 return;
80
81 pressure += fmap(float(age), 0.0, float(lifespan), speed, 0.0); // Ripple slows down as it ages
82 // TODO: Motion of ripple is severely affected by loop speed. Make it time invariant
83
84 if (pressure < 1 && (state == travelingUpwards || state == travelingDownwards)) {
85 // Ripple is visible but hasn't moved - render it to avoid flickering
86 renderLed(ledColors, age);
87 }
88
89 while (pressure >= 1) {
90#ifdef DEBUG_ADVANCEMENT
91 Serial.print("Ripple ");
92 Serial.print(rippleId);
93 Serial.println(" advancing:");
94#endif
95
96 switch (state) {
97 case withinNode: {
98 if (justStarted) {
99 justStarted = false;
100 }
101 else {
102#ifdef DEBUG_ADVANCEMENT
103 Serial.print(" Picking direction out of node ");
104 Serial.print(position[0]);
105 Serial.print(" with agr. ");
106 Serial.println(behavior);
107#endif
108
109 int newDirection = -1;
110
111 int sharpLeft = (position[1] + 1) % 6;
112 int wideLeft = (position[1] + 2) % 6;
113 int forward = (position[1] + 3) % 6;
114 int wideRight = (position[1] + 4) % 6;
115 int sharpRight = (position[1] + 5) % 6;
116
117 if (behavior <= 2) { // Semi-random aggressive turn mode
118 // The more aggressive a ripple, the tighter turns it wants to make.
119 // If there aren't any segments it can turn to, we need to adjust its behavior.
120 byte anger = behavior;
121
122 while (newDirection < 0) {
123 if (anger == 0) {
124 int forwardConnection = nodeConnections[position[0]][forward];
125
126 if (forwardConnection < 0) {
127 // We can't go straight ahead - we need to take a more aggressive angle
128#ifdef DEBUG_ADVANCEMENT
129 Serial.println(" Can't go straight - picking more agr. path");
130#endif
131 anger++;
132 }
133 else {
134#ifdef DEBUG_ADVANCEMENT
135 Serial.println(" Going forward");
136#endif
137 newDirection = forward;
138 }
139 }
140
141 if (anger == 1) {
142 int leftConnection = nodeConnections[position[0]][wideLeft];
143 int rightConnection = nodeConnections[position[0]][wideRight];
144
145 if (leftConnection >= 0 && rightConnection >= 0) {
146#ifdef DEBUG_ADVANCEMENT
147 Serial.println(" Turning left or right at random");
148#endif
149 newDirection = random(2) ? wideLeft : wideRight;
150 }
151 else if (leftConnection >= 0) {
152#ifdef DEBUG_ADVANCEMENT
153 Serial.println(" Can only turn left");
154#endif
155 newDirection = wideLeft;
156 }
157 else if (rightConnection >= 0) {
158#ifdef DEBUG_ADVANCEMENT
159 Serial.println(" Can only turn right");
160#endif
161 newDirection = wideRight;
162 }
163 else {
164#ifdef DEBUG_ADVANCEMENT
165 Serial.println(" Can't make wide turn - picking more agr. path");
166#endif
167 anger++; // Can't take shallow turn - must become more aggressive
168 }
169 }
170
171 if (anger == 2) {
172 int leftConnection = nodeConnections[position[0]][sharpLeft];
173 int rightConnection = nodeConnections[position[0]][sharpRight];
174
175 if (leftConnection >= 0 && rightConnection >= 0) {
176#ifdef DEBUG_ADVANCEMENT
177 Serial.println(" Turning left or right at random");
178#endif
179 newDirection = random(2) ? sharpLeft : sharpRight;
180 }
181 else if (leftConnection >= 0) {
182#ifdef DEBUG_ADVANCEMENT
183 Serial.println(" Can only turn left");
184#endif
185 newDirection = sharpLeft;
186 }
187 else if (rightConnection >= 0) {
188#ifdef DEBUG_ADVANCEMENT
189 Serial.println(" Can only turn right");
190#endif
191 newDirection = sharpRight;
192 }
193 else {
194#ifdef DEBUG_ADVANCEMENT
195 Serial.println(" Can't make tight turn - picking less agr. path");
196#endif
197 anger--; // Can't take tight turn - must become less aggressive
198 }
199 }
200
201 // Note that this can't handle some circumstances,
202 // like a node with segments in nothing but the 0 and 3 positions.
203 // Good thing we don't have any of those!
204 }
205 }
206 else if (behavior == alwaysTurnsRight) {
207 for (int i = 1; i < 6; i++) {
208 int possibleDirection = (position[1] + i) % 6;
209
210 if (nodeConnections[position[0]][possibleDirection] >= 0) {
211 newDirection = possibleDirection;
212 break;
213 }
214 }
215
216#ifdef DEBUG_ADVANCEMENT
217 Serial.println(" Turning as rightward as possible");
218#endif
219 }
220 else if (behavior == alwaysTurnsLeft) {
221 for (int i = 5; i >= 1; i--) {
222 int possibleDirection = (position[1] + i) % 6;
223
224 if (nodeConnections[position[0]][possibleDirection] >= 0) {
225 newDirection = possibleDirection;
226 break;
227 }
228 }
229
230#ifdef DEBUG_ADVANCEMENT
231 Serial.println(" Turning as leftward as possible");
232#endif
233 }
234
235#ifdef DEBUG_ADVANCEMENT
236 Serial.print(" Leaving node ");
237 Serial.print(position[0]);
238 Serial.print(" in direction ");
239 Serial.println(newDirection);
240#endif
241
242 position[1] = newDirection;
243 }
244
245 position[0] = nodeConnections[position[0]][position[1]]; // Look up which segment we're on
246
247#ifdef DEBUG_ADVANCEMENT
248 Serial.print(" and entering segment ");
249 Serial.println(position[0]);
250#endif
251
252 if (position[1] == 5 || position[1] == 0 || position[1] == 1) { // Top half of the node
253#ifdef DEBUG_ADVANCEMENT
254 Serial.println(" (starting at bottom)");
255#endif
256 state = travelingUpwards;
257 position[1] = 0; // Starting at bottom of segment
258 }
259 else {
260#ifdef DEBUG_ADVANCEMENT
261 Serial.println(" (starting at top)");
262#endif
263 state = travelingDownwards;
264 position[1] = 13; // Starting at top of 14-LED-long strip
265 }
266 break;
267 }
268
269 case travelingUpwards: {
270 position[1]++;
271
272 if (position[1] >= 14) {
273 // We've reached the top!
274#ifdef DEBUG_ADVANCEMENT
275 Serial.print(" Reached top of seg. ");
276 Serial.println(position[0]);
277#endif
278 // Enter the new node.
279 int segment = position[0];
280 position[0] = segmentConnections[position[0]][0];
281 for (int i = 0; i < 6; i++) {
282 // Figure out from which direction the ripple is entering the node.
283 // Allows us to exit in an appropriately aggressive direction.
284 int incomingConnection = nodeConnections[position[0]][i];
285 if (incomingConnection == segment)
286 position[1] = i;
287 }
288#ifdef DEBUG_ADVANCEMENT
289 Serial.print(" Entering node ");
290 Serial.print(position[0]);
291 Serial.print(" from direction ");
292 Serial.println(position[1]);
293#endif
294 state = withinNode;
295 }
296 else {
297#ifdef DEBUG_ADVANCEMENT
298 Serial.print(" Moved up to seg. ");
299 Serial.print(position[0]);
300 Serial.print(" LED ");
301 Serial.println(position[1]);
302#endif
303 }
304 break;
305 }
306
307 case travelingDownwards: {
308 position[1]--;
309 if (position[1] < 0) {
310 // We've reached the bottom!
311#ifdef DEBUG_ADVANCEMENT
312 Serial.print(" Reached bottom of seg. ");
313 Serial.println(position[0]);
314#endif
315 // Enter the new node.
316 int segment = position[0];
317 position[0] = segmentConnections[position[0]][1];
318 for (int i = 0; i < 6; i++) {
319 // Figure out from which direction the ripple is entering the node.
320 // Allows us to exit in an appropriately aggressive direction.
321 int incomingConnection = nodeConnections[position[0]][i];
322 if (incomingConnection == segment)
323 position[1] = i;
324 }
325#ifdef DEBUG_ADVANCEMENT
326 Serial.print(" Entering node ");
327 Serial.print(position[0]);
328 Serial.print(" from direction ");
329 Serial.println(position[1]);
330#endif
331 state = withinNode;
332 }
333 else {
334#ifdef DEBUG_ADVANCEMENT
335 Serial.print(" Moved down to seg. ");
336 Serial.print(position[0]);
337 Serial.print(" LED ");
338 Serial.println(position[1]);
339#endif
340 }
341 break;
342 }
343
344 default:
345 break;
346 }
347
348 pressure -= 1;
349
350 if (state == travelingUpwards || state == travelingDownwards) {
351 // Ripple is visible - render it
352 renderLed(ledColors, age);
353 }
354 }
355
356#ifdef DEBUG_ADVANCEMENT
357 Serial.print(" Age is now ");
358 Serial.print(age);
359 Serial.print('/');
360 Serial.println(lifespan);
361#endif
362
363 if (lifespan && age >= lifespan) {
364 // We dead
365#ifdef DEBUG_ADVANCEMENT
366 Serial.println(" Lifespan is up! Ripple is dead.");
367#endif
368 state = dead;
369 position[0] = position[1] = pressure = age = 0;
370 }
371 }
372
373 private:
374 float speed; // Each loop, ripples move this many LED's.
375 unsigned long lifespan; // The ripple stops after this many milliseconds
376
377 /*
378 0: Always goes straight ahead if possible
379 1: Can take 60-degree turns
380 2: Can take 120-degree turns
381 */
382 byte behavior;
383
384 bool justStarted = false;
385
386 float pressure; // When Pressure reaches 1, ripple will move
387 unsigned long birthday; // Used to track age of ripple
388
389 static byte rippleCount; // Used to give them unique ID's
390 byte rippleId; // Used to identify this ripple in debug output
391
392 void renderLed(byte ledColors[40][14][3], unsigned long age) {
393 int strip = ledAssignments[position[0]][0];
394 int led = ledAssignments[position[0]][2] + position[1];
395
396 int red = ledColors[position[0]][position[1]][0];
397 int green = ledColors[position[0]][position[1]][1];
398 int blue = ledColors[position[0]][position[1]][2];
399
400 ledColors[position[0]][position[1]][0] = byte(min(255, max(0, int(fmap(float(age), 0.0, float(lifespan), (color >> 8) & 0xFF, 0.0)) + red)));
401 ledColors[position[0]][position[1]][1] = byte(min(255, max(0, int(fmap(float(age), 0.0, float(lifespan), (color >> 16) & 0xFF, 0.0)) + green)));
402 ledColors[position[0]][position[1]][2] = byte(min(255, max(0, int(fmap(float(age), 0.0, float(lifespan), color & 0xFF, 0.0)) + blue)));
403
404#ifdef DEBUG_RENDERING
405 Serial.print("Rendering ripple position (");
406 Serial.print(position[0]);
407 Serial.print(',');
408 Serial.print(position[1]);
409 Serial.print(") at Strip ");
410 Serial.print(strip);
411 Serial.print(", LED ");
412 Serial.print(led);
413 Serial.print(", color 0x");
414 for (int i = 0; i < 3; i++) {
415 if (ledColors[position[0]][position[1]][i] <= 0x0F)
416 Serial.print('0');
417 Serial.print(ledColors[position[0]][position[1]][i], HEX);
418 }
419 Serial.println();
420#endif
421 }
422};
423
424#endif
central include file for FastLED, defines the CFastLED class/object