Committing my visualizers. TimPlanes is by far the
[SugarCubes.git] / Tim.pde
CommitLineData
d564b8db 1/**
2 * Not very flushed out, but kind of fun nonetheless.
3 */
4class TimSpheres extends SCPattern {
5 private BasicParameter hueParameter = new BasicParameter("RAD", 1.0);
6 private final SawLFO lfo = new SawLFO(0, 1, 10000);
7 private final SinLFO sinLfo = new SinLFO(0, 1, 4000);
8 private final float centerX, centerY, centerZ;
9
10 class Sphere {
11 float x, y, z;
12 float radius;
13 float hue;
14 }
15
16 private final Sphere[] spheres;
17
18 public TimSpheres(GLucose glucose) {
19 super(glucose);
20 addParameter(hueParameter);
21 addModulator(lfo).trigger();
22 addModulator(sinLfo).trigger();
23 centerX = (model.xMax + model.xMin) / 2;
24 centerY = (model.yMax + model.yMin) / 2;
25 centerZ = (model.zMax + model.zMin) / 2;
26
27 spheres = new Sphere[2];
28
29 spheres[0] = new Sphere();
30 spheres[0].x = model.xMin;
31 spheres[0].y = centerY;
32 spheres[0].z = centerZ;
33 spheres[0].hue = 0;
34 spheres[0].radius = 50;
35
36 spheres[1] = new Sphere();
37 spheres[1].x = model.xMax;
38 spheres[1].y = centerY;
39 spheres[1].z = centerZ;
40 spheres[1].hue = 0.33;
41 spheres[1].radius = 50;
42 }
43
44 public void run(int deltaMs) {
45 // Access the core master hue via this method call
46 float hv = hueParameter.getValuef();
47 float lfoValue = lfo.getValuef();
48 float sinLfoValue = sinLfo.getValuef();
49
50 spheres[0].x = model.xMin + sinLfoValue * model.xMax;
51 spheres[1].x = model.xMax - sinLfoValue * model.xMax;
52
53 spheres[0].radius = 100 * hueParameter.getValuef();
54 spheres[1].radius = 100 * hueParameter.getValuef();
55
56 for (Point p : model.points) {
57 float value = 0;
58
59 color c = color(0, 0, 0);
60 for (Sphere s : spheres) {
61 float d = sqrt(pow(p.x - s.x, 2) + pow(p.y - s.y, 2) + pow(p.z - s.z, 2));
62 float r = (s.radius); // * (sinLfoValue + 0.5));
63 value = max(0, 1 - max(0, d - r) / 10);
64
65 c = blendColor(c, color(((s.hue + lfoValue) % 1) * 360, 100, min(1, value) * 100), ADD);
66 }
67
68 colors[p.index] = c;
69 }
70 }
71}
72
73class Vector2 {
74 float x, y;
75
76 Vector2() {
77 this(0, 0);
78 }
79
80 Vector2(float x, float y) {
81 this.x = x;
82 this.y = y;
83 }
84
85 float distanceTo(float x, float y) {
86 return sqrt(pow(x - this.x, 2) + pow(y - this.y, 2));
87 }
88
89 float distanceTo(Vector2 v) {
90 return distanceTo(v.x, v.y);
91 }
92
93 Vector2 plus(float x, float y) {
94 return new Vector2(this.x + x, this.y + y);
95 }
96
97 Vector2 plus(Vector2 v) {
98 return plus(v.x, v.y);
99 }
100
101 Vector2 minus(Vector2 v) {
102 return plus(-1 * v.x, -1 * v.y);
103 }
104}
105
106class Vector3 {
107 float x, y, z;
108
109 Vector3() {
110 this(0, 0, 0);
111 }
112
113 Vector3(float x, float y, float z) {
114 this.x = x;
115 this.y = y;
116 this.z = z;
117 }
118
119 float distanceTo(float x, float y, float z) {
120 return sqrt(pow(x - this.x, 2) + pow(y - this.y, 2) + pow(z - this.z, 2));
121 }
122
123 float distanceTo(Vector3 v) {
124 return distanceTo(v.x, v.y, v.z);
125 }
126
127 float distanceTo(Point p) {
128 return distanceTo(p.fx, p.fy, p.fz);
129 }
130
131 void add(Vector3 other, float multiplier) {
132 this.add(other.x * multiplier, other.y * multiplier, other.z * multiplier);
133 }
134
135 void add(float x, float y, float z) {
136 this.x += x;
137 this.y += y;
138 this.z += z;
139 }
140
141 void divide(float factor) {
142 this.x /= factor;
143 this.y /= factor;
144 this.z /= factor;
145 }
146}
147
148class Rotation {
149 private float a, b, c, d, e, f, g, h, i;
150
151 Rotation(float yaw, float pitch, float roll) {
152 float cosYaw = cos(yaw);
153 float sinYaw = sin(yaw);
154 float cosPitch = cos(pitch);
155 float sinPitch = sin(pitch);
156 float cosRoll = cos(roll);
157 float sinRoll = sin(roll);
158
159 a = cosYaw * cosPitch;
160 b = cosYaw * sinPitch * sinRoll - sinYaw * cosRoll;
161 c = cosYaw * sinPitch * cosRoll + sinYaw * sinRoll;
162 d = sinYaw * cosPitch;
163 e = sinYaw * sinPitch * sinRoll + cosYaw * cosRoll;
164 f = sinYaw * sinPitch * cosRoll - cosYaw * sinRoll;
165 g = -1 * sinPitch;
166 h = cosPitch * sinRoll;
167 i = cosPitch * cosRoll;
168 }
169
170 Vector3 rotated(Vector3 v) {
171 return new Vector3(
172 rotatedX(v),
173 rotatedY(v),
174 rotatedZ(v));
175
176 }
177
178 float rotatedX(Vector3 v) {
179 return a * v.x + b * v.y + c * v.z;
180 }
181
182 float rotatedY(Vector3 v) {
183 return d * v.x + e * v.y + f * v.z;
184 }
185
186 float rotatedZ(Vector3 v) {
187 return g * v.x + h * v.y + i * v.z;
188 }
189}
190
191/**
192 * Very literal rain effect. Not that great as-is but some tweaking could make it nice.
193 * A couple ideas:
194 * - changing hue and direction of "rain" could make a nice fire effect
195 * - knobs to change frequency and size of rain drops
196 * - sync somehow to tempo but maybe less frequently than every beat?
197 */
198class TimRaindrops extends SCPattern {
199 Vector3 randomVector3() {
200 return new Vector3(
201 random(model.xMax - model.xMin) + model.xMin,
202 random(model.yMax - model.yMin) + model.yMin,
203 random(model.zMax - model.zMin) + model.zMin);
204 }
205
206 class Raindrop {
207 Vector3 p;
208 Vector3 v;
209 float radius;
210 float hue;
211
212 Raindrop() {
213 this.radius = 30;
214 this.p = new Vector3(
215 random(model.xMax - model.xMin) + model.xMin,
216 model.yMax + this.radius,
217 random(model.zMax - model.zMin) + model.zMin);
218 float velMagnitude = 120;
219 this.v = new Vector3(
220 0,
221 -3 * model.yMax,
222 0);
223 this.hue = random(40) + 200;
224 }
225
226 // returns TRUE when this should die
227 boolean age(int ms) {
228 p.add(v, ms / 1000.0);
229 return this.p.y < (0 - this.radius);
230 }
231 }
232
233 private float leftoverMs = 0;
234 private float msPerRaindrop = 40;
235 private List<Raindrop> raindrops;
236
237 public TimRaindrops(GLucose glucose) {
238 super(glucose);
239 raindrops = new LinkedList<Raindrop>();
240 }
241
242 public void run(int deltaMs) {
243 leftoverMs += deltaMs;
244 while (leftoverMs > msPerRaindrop) {
245 leftoverMs -= msPerRaindrop;
246 raindrops.add(new Raindrop());
247 }
248
249 for (Point p : model.points) {
250 color c =
251 blendColor(
252 color(210, 20, (float)Math.max(0, 1 - Math.pow((model.yMax - p.fy) / 10, 2)) * 50),
253 color(220, 60, (float)Math.max(0, 1 - Math.pow((p.fy - model.yMin) / 10, 2)) * 100),
254 ADD);
255 for (Raindrop raindrop : raindrops) {
256 if (p.fx >= (raindrop.p.x - raindrop.radius) && p.fx <= (raindrop.p.x + raindrop.radius) &&
257 p.fy >= (raindrop.p.y - raindrop.radius) && p.fy <= (raindrop.p.y + raindrop.radius)) {
258 float d = raindrop.p.distanceTo(p) / raindrop.radius;
259 // float value = (float)Math.max(0, 1 - Math.pow(Math.min(0, d - raindrop.radius) / 5, 2));
260 if (d < 1) {
261 c = blendColor(c, color(raindrop.hue, 80, (float)Math.pow(1 - d, 0.01) * 100), ADD);
262 }
263 }
264 }
265 colors[p.index] = c;
266 }
267
268 Iterator<Raindrop> i = raindrops.iterator();
269 while (i.hasNext()) {
270 Raindrop raindrop = i.next();
271 boolean dead = raindrop.age(deltaMs);
272 if (dead) {
273 i.remove();
274 }
275 }
276 }
277}
278
279
280class TimCubes extends SCPattern {
281 private BasicParameter rateParameter = new BasicParameter("RATE", 0.125);
282 private BasicParameter attackParameter = new BasicParameter("ATTK", 0.5);
283 private BasicParameter decayParameter = new BasicParameter("DECAY", 0.5);
284 private BasicParameter hueParameter = new BasicParameter("HUE", 0.5);
285 private BasicParameter hueVarianceParameter = new BasicParameter("H.V.", 0.25);
286 private BasicParameter saturationParameter = new BasicParameter("SAT", 0.5);
287
288 class CubeFlash {
289 Cube c;
290 float value;
291 float hue;
292 boolean hasPeaked;
293
294 CubeFlash() {
295 c = model.cubes.get(floor(random(model.cubes.size())));
296 hue = random(1);
297 boolean infiniteAttack = (attackParameter.getValuef() > 0.999);
298 hasPeaked = infiniteAttack;
299 value = (infiniteAttack ? 1 : 0);
300 }
301
302 // returns TRUE if this should die
303 boolean age(int ms) {
304 if (!hasPeaked) {
305 value = value + (ms / 1000.0f * ((attackParameter.getValuef() + 0.01) * 5));
306 if (value >= 1.0) {
307 value = 1.0;
308 hasPeaked = true;
309 }
310 return false;
311 } else {
312 value = value - (ms / 1000.0f * ((decayParameter.getValuef() + 0.01) * 10));
313 return value <= 0;
314 }
315 }
316 }
317
318 private float leftoverMs = 0;
319 private List<CubeFlash> flashes;
320
321 public TimCubes(GLucose glucose) {
322 super(glucose);
323 addParameter(rateParameter);
324 addParameter(attackParameter);
325 addParameter(decayParameter);
326 addParameter(hueParameter);
327 addParameter(hueVarianceParameter);
328 addParameter(saturationParameter);
329 flashes = new LinkedList<CubeFlash>();
330 }
331
332 public void run(int deltaMs) {
333 leftoverMs += deltaMs;
334 float msPerFlash = 1000 / ((rateParameter.getValuef() + .01) * 100);
335 while (leftoverMs > msPerFlash) {
336 leftoverMs -= msPerFlash;
337 flashes.add(new CubeFlash());
338 }
339
340 for (Point p : model.points) {
341 colors[p.index] = 0;
342 }
343
344 for (CubeFlash flash : flashes) {
345 float hue = (hueParameter.getValuef() + (hueVarianceParameter.getValuef() * flash.hue)) % 1.0;
346 color c = color(hue * 360, saturationParameter.getValuef() * 100, (flash.value) * 100);
347 for (Point p : flash.c.points) {
348 colors[p.index] = c;
349 }
350 }
351
352 Iterator<CubeFlash> i = flashes.iterator();
353 while (i.hasNext()) {
354 CubeFlash flash = i.next();
355 boolean dead = flash.age(deltaMs);
356 if (dead) {
357 i.remove();
358 }
359 }
360 }
361}
362
363/**
364 * This one is the best but you need to play with all the knobs. It's synced to
365 * the tempo, with the WSpd knob letting you pick 4 discrete multipliers for
366 * the tempo.
367 *
368 * Basically it's just 3 planes all rotating to the beat, but also rotated relative
369 * to one another. The intersection of the planes and the cubes over time makes
370 * for a nice abstract effect.
371 */
372class TimPlanes extends SCPattern {
373 private BasicParameter wobbleParameter = new BasicParameter("Wob", 0.2);
374 private BasicParameter wobbleSpreadParameter = new BasicParameter("WSpr", 0.25);
375 private BasicParameter wobbleSpeedParameter = new BasicParameter("WSpd", 0.375);
376 private BasicParameter wobbleOffsetParameter = new BasicParameter("WOff", 0);
377 private BasicParameter derezParameter = new BasicParameter("Drez", 0.5);
378 private BasicParameter thicknessParameter = new BasicParameter("Thick", 0.4);
379 private BasicParameter ySpreadParameter = new BasicParameter("ySpr", 0.2);
380 private BasicParameter hueParameter = new BasicParameter("Hue", 0.75);
381 private BasicParameter hueSpreadParameter = new BasicParameter("HSpr", 0.68);
382
383 final float centerX, centerY, centerZ;
384 float phase;
385
386 class Plane {
387 Vector3 center;
388 Rotation rotation;
389 float hue;
390
391 Plane(Vector3 center, Rotation rotation, float hue) {
392 this.center = center;
393 this.rotation = rotation;
394 this.hue = hue;
395 }
396 }
397
398 TimPlanes(GLucose glucose) {
399 super(glucose);
400 centerX = (model.xMin + model.xMax) / 2;
401 centerY = (model.yMin + model.yMax) / 2;
402 centerZ = (model.zMin + model.zMax) / 2;
403 phase = 0;
404 addParameter(wobbleParameter);
405 addParameter(wobbleSpreadParameter);
406 addParameter(wobbleSpeedParameter);
407// addParameter(wobbleOffsetParameter);
408 addParameter(derezParameter);
409 addParameter(thicknessParameter);
410 addParameter(ySpreadParameter);
411 addParameter(hueParameter);
412 addParameter(hueSpreadParameter);
413 }
414
415 color getColor(Vector3 normalizedPoint, float hue, Rotation rotation, float saturation) {
416 float t = (thicknessParameter.getValuef() * 25 + 1);
417
418 float v = rotation.rotatedY(normalizedPoint);
419 float d = abs(v);
420
421 if (d <= t) {
422 return color(hue, saturation, 100);
423 } else if (d <= t * 2) {
424 float value = 1 - ((d - t) / t);
425 return color(hue, saturation, value * 100);
426 } else {
427 return 0;
428 }
429 }
430
431 int beat = 0;
432 float prevRamp = 0;
433 float[] wobbleSpeeds = { 1.0/8, 1.0/4, 1.0/2, 1.0 };
434
435 public void run(int deltaMs) {
436 float ramp = (float)lx.tempo.ramp();
437 if (ramp < prevRamp) {
438 beat = (beat + 1) % 32;
439 }
440 prevRamp = ramp;
441
442 float wobbleSpeed = wobbleSpeeds[floor(wobbleSpeedParameter.getValuef() * wobbleSpeeds.length * 0.9999)];
443
444 phase = (((beat + ramp) * wobbleSpeed + wobbleOffsetParameter.getValuef()) % 1) * 2 * PI;
445
446 float ySpread = ySpreadParameter.getValuef() * 50;
447 float wobble = wobbleParameter.getValuef() * PI;
448 float wobbleSpread = wobbleSpreadParameter.getValuef() * PI;
449 float hue = hueParameter.getValuef() * 360;
450 float hueSpread = (hueSpreadParameter.getValuef() - 0.5) * 360;
451
452 float saturation = 10 + 60.0 * pow(ramp, 0.25);
453
454 float derez = derezParameter.getValuef();
455
456 Plane[] planes = {
457 new Plane(
458 new Vector3(centerX, centerY + ySpread, centerZ),
459 new Rotation(wobble - wobbleSpread, phase, 0),
460 (hue + 360 - hueSpread) % 360),
461 new Plane(
462 new Vector3(centerX, centerY, centerZ),
463 new Rotation(wobble, phase, 0),
464 hue),
465 new Plane(
466 new Vector3(centerX, centerY - ySpread, centerZ),
467 new Rotation(wobble + wobbleSpread, phase, 0),
468 (hue + 360 + hueSpread) % 360)
469 };
470
471 for (Point p : model.points) {
472 if (random(1.0) < derez) {
473 continue;
474 }
475
476 color c = 0;
477
478 for (Plane plane : planes) {
479 Vector3 normalizedPoint = new Vector3(p.fx - plane.center.x, p.fy - plane.center.y, p.fz - plane.center.z);
480 color planeColor = getColor(normalizedPoint, plane.hue, plane.rotation, saturation);
481 if (planeColor != 0) {
482 if (c == 0) {
483 c = planeColor;
484 } else {
485 c = blendColor(c, planeColor, ADD);
486 }
487 }
488 }
489
490 colors[p.index] = c;
491 }
492 }
493}
494
495/**
496 * Not very flushed out but pretty.
497 */
498class TimPinwheels extends SCPattern {
499 float phase = 0;
500 private final int NUM_BLADES = 16;
501
502 class Pinwheel {
503 Vector2 center;
504 int numBlades;
505 float phase;
506 float speed;
507
508 Pinwheel(float xCenter, float yCenter, int numBlades, float speed) {
509 this.center = new Vector2(xCenter, yCenter);
510 this.numBlades = numBlades;
511 this.speed = speed;
512 }
513
514 void age(int deltaMs) {
515 phase = (phase + deltaMs / 1000.0 * speed) % 1.0;
516 }
517
518 boolean isOnBlade(float x, float y) {
519 x = x - center.x;
520 y = y - center.y;
521
522 float normalizedAngle = (atan2(x, y) / (2 * PI) + 1.5 + phase) % 1;
523 float v = (normalizedAngle * 4 * numBlades);
524 int blade_num = floor((v + 2) / 4);
525 return (blade_num % 2) == 0;
526 }
527 }
528
529 private final List<Pinwheel> pinwheels;
530
531 TimPinwheels(GLucose glucose) {
532 super(glucose);
533
534 float xDist = model.xMax - model.xMin;
535 float xCenter = (model.xMin + model.xMax) / 2;
536 float yCenter = (model.yMin + model.yMax) / 2;
537
538 pinwheels = new ArrayList();
539 pinwheels.add(new Pinwheel(xCenter - xDist * 0.4, yCenter, NUM_BLADES, 0.1));
540 pinwheels.add(new Pinwheel(xCenter + xDist * 0.4, yCenter, NUM_BLADES, -0.1));
541 }
542
543 public void run(int deltaMs) {
544 for (Pinwheel pw : pinwheels) {
545 pw.age(deltaMs);
546 }
547
548 for (Point p : model.points) {
549 int value = 0;
550 for (Pinwheel pw : pinwheels) {
551 value += (pw.isOnBlade(p.fx, p.fy) ? 1 : 0);
552 }
553 if (value == 1) {
554 colors[p.index] = color(120, 0, 100);
555 } else {
556 color c = colors[p.index];
557 colors[p.index] = color(max(0, hue(c) - 10), min(100, saturation(c) + 10), brightness(c) - 5 );
558 }
559 }
560 }
561}
562
563/**
564 * This tries to figure out neighboring pixels from one cube to another to
565 * let you have a bunch of moving points tracing all over the structure.
566 * Adds a couple seconds of startup time to do the calculation, and in the
567 * end just comes out looking a lot like a screensaver. Probably not worth
568 * it but there may be useful code here.
569 */
570class TimTrace extends SCPattern {
571 private Map<Point, List<Point>> pointToNeighbors;
572 private Map<Point, Strip> pointToStrip;
573 // private final Map<Strip, List<Strip>> stripToNearbyStrips;
574
575 int extraMs;
576
577 class MovingPoint {
578 Point currentPoint;
579 float hue;
580 private Strip currentStrip;
581 private int currentStripIndex;
582 private int direction; // +1 or -1
583
584 MovingPoint(Point p) {
585 this.setPointOnNewStrip(p);
586 hue = random(360);
587 }
588
589 private void setPointOnNewStrip(Point p) {
590 this.currentPoint = p;
591 this.currentStrip = pointToStrip.get(p);
592 for (int i = 0; i < this.currentStrip.points.size(); ++i) {
593 if (this.currentStrip.points.get(i) == p) {
594 this.currentStripIndex = i;
595 break;
596 }
597 }
598 if (this.currentStripIndex == 0) {
599 // we are at the beginning of the strip; go forwards
600 this.direction = 1;
601 } else if (this.currentStripIndex == this.currentStrip.points.size()) {
602 // we are at the end of the strip; go backwards
603 this.direction = -1;
604 } else {
605 // we are in the middle of a strip; randomly go one way or another
606 this.direction = ((random(1.0) < 0.5) ? -1 : 1);
607 }
608 }
609
610 void step() {
611 List<Point> neighborsOnOtherStrips = pointToNeighbors.get(this.currentPoint);
612
613 Point nextPointOnCurrentStrip = null;
614 this.currentStripIndex += this.direction;
615 if (this.currentStripIndex >= 0 && this.currentStripIndex < this.currentStrip.points.size()) {
616 nextPointOnCurrentStrip = this.currentStrip.points.get(this.currentStripIndex);
617 }
618
619 // pick which option to take; if we can keep going on the current strip then
620 // add that as another option
621 int option = floor(random(neighborsOnOtherStrips.size() + (nextPointOnCurrentStrip == null ? 0 : 100)));
622
623 if (option < neighborsOnOtherStrips.size()) {
624 this.setPointOnNewStrip(neighborsOnOtherStrips.get(option));
625 } else {
626 this.currentPoint = nextPointOnCurrentStrip;
627 }
628 }
629 }
630
631 List<MovingPoint> movingPoints;
632
633 TimTrace(GLucose glucose) {
634 super(glucose);
635
636 extraMs = 0;
637
638 pointToNeighbors = this.buildPointToNeighborsMap();
639 pointToStrip = this.buildPointToStripMap();
640
641 int numMovingPoints = 1000;
642 movingPoints = new ArrayList();
643 for (int i = 0; i < numMovingPoints; ++i) {
644 movingPoints.add(new MovingPoint(model.points.get(floor(random(model.points.size())))));
645 }
646
647 }
648
649 private Map<Strip, List<Strip>> buildStripToNearbyStripsMap() {
650 Map<Strip, Vector3> stripToCenter = new HashMap();
651 for (Strip s : model.strips) {
652 Vector3 v = new Vector3();
653 for (Point p : s.points) {
654 v.add(p.fx, p.fy, p.fz);
655 }
656 v.divide(s.points.size());
657 stripToCenter.put(s, v);
658 }
659
660 Map<Strip, List<Strip>> stripToNeighbors = new HashMap();
661 for (Strip s : model.strips) {
662 List<Strip> neighbors = new ArrayList();
663 Vector3 sCenter = stripToCenter.get(s);
664 for (Strip potentialNeighbor : model.strips) {
665 if (s != potentialNeighbor) {
666 float distance = sCenter.distanceTo(stripToCenter.get(potentialNeighbor));
667 if (distance < 25) {
668 neighbors.add(potentialNeighbor);
669 }
670 }
671 }
672 stripToNeighbors.put(s, neighbors);
673 }
674
675 return stripToNeighbors;
676 }
677
678 private Map<Point, List<Point>> buildPointToNeighborsMap() {
679 Map<Point, List<Point>> m = new HashMap();
680 Map<Strip, List<Strip>> stripToNearbyStrips = this.buildStripToNearbyStripsMap();
681
682 for (Strip s : model.strips) {
683 List<Strip> nearbyStrips = stripToNearbyStrips.get(s);
684
685 for (Point p : s.points) {
686 Vector3 v = new Vector3(p.fx, p.fy, p.fz);
687
688 List<Point> neighbors = new ArrayList();
689
690 for (Strip nearbyStrip : nearbyStrips) {
691 Point closestPoint = null;
692 float closestPointDistance = 100000;
693
694 for (Point nsp : nearbyStrip.points) {
695 float distance = v.distanceTo(nsp.fx, nsp.fy, nsp.fz);
696 if (closestPoint == null || distance < closestPointDistance) {
697 closestPoint = nsp;
698 closestPointDistance = distance;
699 }
700 }
701
702 if (closestPointDistance < 15) {
703 neighbors.add(closestPoint);
704 }
705 }
706
707 m.put(p, neighbors);
708 }
709 }
710
711 return m;
712 }
713
714 private Map<Point, Strip> buildPointToStripMap() {
715 Map<Point, Strip> m = new HashMap();
716 for (Strip s : model.strips) {
717 for (Point p : s.points) {
718 m.put(p, s);
719 }
720 }
721 return m;
722 }
723
724 public void run(int deltaMs) {
725 for (Point p : model.points) {
726 color c = colors[p.index];
727 colors[p.index] = color(hue(c), saturation(c), brightness(c) - 3);
728 }
729
730 for (MovingPoint mp : movingPoints) {
731 mp.step();
732 colors[mp.currentPoint.index] = blendColor(colors[mp.currentPoint.index], color(mp.hue, 10, 100), ADD);
733 }
734 }
735}