rename Tim.pde -> TimBavaro.pde
[SugarCubes.git] / TimBavaro.pde
1 /**
2 * Not very flushed out, but kind of fun nonetheless.
3 */
4 class 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
73 class 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
106 class 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
148 class 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 */
198 class 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
280 class 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 */
372 class TimPlanes extends SCPattern {
373 private BasicParameter wobbleParameter = new BasicParameter("Wob", 0.166);
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 int beat = 0;
416 float prevRamp = 0;
417 float[] wobbleSpeeds = { 1.0/8, 1.0/4, 1.0/2, 1.0 };
418
419 public void run(int deltaMs) {
420 float ramp = (float)lx.tempo.ramp();
421 if (ramp < prevRamp) {
422 beat = (beat + 1) % 32;
423 }
424 prevRamp = ramp;
425
426 float wobbleSpeed = wobbleSpeeds[floor(wobbleSpeedParameter.getValuef() * wobbleSpeeds.length * 0.9999)];
427
428 phase = (((beat + ramp) * wobbleSpeed + wobbleOffsetParameter.getValuef()) % 1) * 2 * PI;
429
430 float ySpread = ySpreadParameter.getValuef() * 50;
431 float wobble = wobbleParameter.getValuef() * PI;
432 float wobbleSpread = wobbleSpreadParameter.getValuef() * PI;
433 float hue = hueParameter.getValuef() * 360;
434 float hueSpread = (hueSpreadParameter.getValuef() - 0.5) * 360;
435
436 float saturation = 10 + 60.0 * pow(ramp, 0.25);
437
438 float derez = derezParameter.getValuef();
439
440 Plane[] planes = {
441 new Plane(
442 new Vector3(centerX, centerY + ySpread, centerZ),
443 new Rotation(wobble - wobbleSpread, phase, 0),
444 (hue + 360 - hueSpread) % 360),
445 new Plane(
446 new Vector3(centerX, centerY, centerZ),
447 new Rotation(wobble, phase, 0),
448 hue),
449 new Plane(
450 new Vector3(centerX, centerY - ySpread, centerZ),
451 new Rotation(wobble + wobbleSpread, phase, 0),
452 (hue + 360 + hueSpread) % 360)
453 };
454
455 float thickness = (thicknessParameter.getValuef() * 25 + 1);
456
457 Vector3 normalizedPoint = new Vector3();
458
459 for (Point p : model.points) {
460 if (random(1.0) < derez) {
461 continue;
462 }
463
464 color c = 0;
465
466 for (Plane plane : planes) {
467 normalizedPoint.x = p.fx - plane.center.x;
468 normalizedPoint.y = p.fy - plane.center.y;
469 normalizedPoint.z = p.fz - plane.center.z;
470
471 float v = plane.rotation.rotatedY(normalizedPoint);
472 float d = abs(v);
473
474 final color planeColor;
475 if (d <= thickness) {
476 planeColor = color(plane.hue, saturation, 100);
477 } else if (d <= thickness * 2) {
478 float value = 1 - ((d - thickness) / thickness);
479 planeColor = color(plane.hue, saturation, value * 100);
480 } else {
481 planeColor = 0;
482 }
483
484 if (planeColor != 0) {
485 if (c == 0) {
486 c = planeColor;
487 } else {
488 c = blendColor(c, planeColor, ADD);
489 }
490 }
491 }
492
493 colors[p.index] = c;
494 }
495 }
496 }
497
498 /**
499 * Not very flushed out but pretty.
500 */
501 class TimPinwheels extends SCPattern {
502 float phase = 0;
503 private final int NUM_BLADES = 16;
504
505 class Pinwheel {
506 Vector2 center;
507 int numBlades;
508 float phase;
509 float speed;
510
511 Pinwheel(float xCenter, float yCenter, int numBlades, float speed) {
512 this.center = new Vector2(xCenter, yCenter);
513 this.numBlades = numBlades;
514 this.speed = speed;
515 }
516
517 void age(int deltaMs) {
518 phase = (phase + deltaMs / 1000.0 * speed) % 1.0;
519 }
520
521 boolean isOnBlade(float x, float y) {
522 x = x - center.x;
523 y = y - center.y;
524
525 float normalizedAngle = (atan2(x, y) / (2 * PI) + 1.5 + phase) % 1;
526 float v = (normalizedAngle * 4 * numBlades);
527 int blade_num = floor((v + 2) / 4);
528 return (blade_num % 2) == 0;
529 }
530 }
531
532 private final List<Pinwheel> pinwheels;
533
534 TimPinwheels(GLucose glucose) {
535 super(glucose);
536
537 float xDist = model.xMax - model.xMin;
538 float xCenter = (model.xMin + model.xMax) / 2;
539 float yCenter = (model.yMin + model.yMax) / 2;
540
541 pinwheels = new ArrayList();
542 pinwheels.add(new Pinwheel(xCenter - xDist * 0.4, yCenter, NUM_BLADES, 0.1));
543 pinwheels.add(new Pinwheel(xCenter + xDist * 0.4, yCenter, NUM_BLADES, -0.1));
544 }
545
546 public void run(int deltaMs) {
547 for (Pinwheel pw : pinwheels) {
548 pw.age(deltaMs);
549 }
550
551 for (Point p : model.points) {
552 int value = 0;
553 for (Pinwheel pw : pinwheels) {
554 value += (pw.isOnBlade(p.fx, p.fy) ? 1 : 0);
555 }
556 if (value == 1) {
557 colors[p.index] = color(120, 0, 100);
558 } else {
559 color c = colors[p.index];
560 colors[p.index] = color(max(0, hue(c) - 10), min(100, saturation(c) + 10), brightness(c) - 5 );
561 }
562 }
563 }
564 }
565
566 /**
567 * This tries to figure out neighboring pixels from one cube to another to
568 * let you have a bunch of moving points tracing all over the structure.
569 * Adds a couple seconds of startup time to do the calculation, and in the
570 * end just comes out looking a lot like a screensaver. Probably not worth
571 * it but there may be useful code here.
572 */
573 class TimTrace extends SCPattern {
574 private Map<Point, List<Point>> pointToNeighbors;
575 private Map<Point, Strip> pointToStrip;
576 // private final Map<Strip, List<Strip>> stripToNearbyStrips;
577
578 int extraMs;
579
580 class MovingPoint {
581 Point currentPoint;
582 float hue;
583 private Strip currentStrip;
584 private int currentStripIndex;
585 private int direction; // +1 or -1
586
587 MovingPoint(Point p) {
588 this.setPointOnNewStrip(p);
589 hue = random(360);
590 }
591
592 private void setPointOnNewStrip(Point p) {
593 this.currentPoint = p;
594 this.currentStrip = pointToStrip.get(p);
595 for (int i = 0; i < this.currentStrip.points.size(); ++i) {
596 if (this.currentStrip.points.get(i) == p) {
597 this.currentStripIndex = i;
598 break;
599 }
600 }
601 if (this.currentStripIndex == 0) {
602 // we are at the beginning of the strip; go forwards
603 this.direction = 1;
604 } else if (this.currentStripIndex == this.currentStrip.points.size()) {
605 // we are at the end of the strip; go backwards
606 this.direction = -1;
607 } else {
608 // we are in the middle of a strip; randomly go one way or another
609 this.direction = ((random(1.0) < 0.5) ? -1 : 1);
610 }
611 }
612
613 void step() {
614 List<Point> neighborsOnOtherStrips = pointToNeighbors.get(this.currentPoint);
615
616 Point nextPointOnCurrentStrip = null;
617 this.currentStripIndex += this.direction;
618 if (this.currentStripIndex >= 0 && this.currentStripIndex < this.currentStrip.points.size()) {
619 nextPointOnCurrentStrip = this.currentStrip.points.get(this.currentStripIndex);
620 }
621
622 // pick which option to take; if we can keep going on the current strip then
623 // add that as another option
624 int option = floor(random(neighborsOnOtherStrips.size() + (nextPointOnCurrentStrip == null ? 0 : 100)));
625
626 if (option < neighborsOnOtherStrips.size()) {
627 this.setPointOnNewStrip(neighborsOnOtherStrips.get(option));
628 } else {
629 this.currentPoint = nextPointOnCurrentStrip;
630 }
631 }
632 }
633
634 List<MovingPoint> movingPoints;
635
636 TimTrace(GLucose glucose) {
637 super(glucose);
638
639 extraMs = 0;
640
641 pointToNeighbors = this.buildPointToNeighborsMap();
642 pointToStrip = this.buildPointToStripMap();
643
644 int numMovingPoints = 1000;
645 movingPoints = new ArrayList();
646 for (int i = 0; i < numMovingPoints; ++i) {
647 movingPoints.add(new MovingPoint(model.points.get(floor(random(model.points.size())))));
648 }
649
650 }
651
652 private Map<Strip, List<Strip>> buildStripToNearbyStripsMap() {
653 Map<Strip, Vector3> stripToCenter = new HashMap();
654 for (Strip s : model.strips) {
655 Vector3 v = new Vector3();
656 for (Point p : s.points) {
657 v.add(p.fx, p.fy, p.fz);
658 }
659 v.divide(s.points.size());
660 stripToCenter.put(s, v);
661 }
662
663 Map<Strip, List<Strip>> stripToNeighbors = new HashMap();
664 for (Strip s : model.strips) {
665 List<Strip> neighbors = new ArrayList();
666 Vector3 sCenter = stripToCenter.get(s);
667 for (Strip potentialNeighbor : model.strips) {
668 if (s != potentialNeighbor) {
669 float distance = sCenter.distanceTo(stripToCenter.get(potentialNeighbor));
670 if (distance < 25) {
671 neighbors.add(potentialNeighbor);
672 }
673 }
674 }
675 stripToNeighbors.put(s, neighbors);
676 }
677
678 return stripToNeighbors;
679 }
680
681 private Map<Point, List<Point>> buildPointToNeighborsMap() {
682 Map<Point, List<Point>> m = new HashMap();
683 Map<Strip, List<Strip>> stripToNearbyStrips = this.buildStripToNearbyStripsMap();
684
685 for (Strip s : model.strips) {
686 List<Strip> nearbyStrips = stripToNearbyStrips.get(s);
687
688 for (Point p : s.points) {
689 Vector3 v = new Vector3(p.fx, p.fy, p.fz);
690
691 List<Point> neighbors = new ArrayList();
692
693 for (Strip nearbyStrip : nearbyStrips) {
694 Point closestPoint = null;
695 float closestPointDistance = 100000;
696
697 for (Point nsp : nearbyStrip.points) {
698 float distance = v.distanceTo(nsp.fx, nsp.fy, nsp.fz);
699 if (closestPoint == null || distance < closestPointDistance) {
700 closestPoint = nsp;
701 closestPointDistance = distance;
702 }
703 }
704
705 if (closestPointDistance < 15) {
706 neighbors.add(closestPoint);
707 }
708 }
709
710 m.put(p, neighbors);
711 }
712 }
713
714 return m;
715 }
716
717 private Map<Point, Strip> buildPointToStripMap() {
718 Map<Point, Strip> m = new HashMap();
719 for (Strip s : model.strips) {
720 for (Point p : s.points) {
721 m.put(p, s);
722 }
723 }
724 return m;
725 }
726
727 public void run(int deltaMs) {
728 for (Point p : model.points) {
729 color c = colors[p.index];
730 colors[p.index] = color(hue(c), saturation(c), brightness(c) - 3);
731 }
732
733 for (MovingPoint mp : movingPoints) {
734 mp.step();
735 colors[mp.currentPoint.index] = blendColor(colors[mp.currentPoint.index], color(mp.hue, 10, 100), ADD);
736 }
737 }
738 }