merged slee's lx changes
[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(double 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 (LXPoint p : model.points) {
57 float value = 0;
58
59 color c = lx.hsb(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, lx.hsb(((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(LXPoint p) {
128 return distanceTo(p.x, p.y, p.z);
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(double ms) {
228 p.add(v, (float) (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(double deltaMs) {
243 leftoverMs += deltaMs;
244 while (leftoverMs > msPerRaindrop) {
245 leftoverMs -= msPerRaindrop;
246 raindrops.add(new Raindrop());
247 }
248
249 for (LXPoint p : model.points) {
250 color c =
251 blendColor(
252 lx.hsb(210, 20, (float)Math.max(0, 1 - Math.pow((model.yMax - p.y) / 10, 2)) * 50),
253 lx.hsb(220, 60, (float)Math.max(0, 1 - Math.pow((p.y - model.yMin) / 10, 2)) * 100),
254 ADD);
255 for (Raindrop raindrop : raindrops) {
256 if (p.x >= (raindrop.p.x - raindrop.radius) && p.x <= (raindrop.p.x + raindrop.radius) &&
257 p.y >= (raindrop.p.y - raindrop.radius) && p.y <= (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, lx.hsb(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(double ms) {
304 if (!hasPeaked) {
305 value = value + (float) (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 - (float) (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(double 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 (LXPoint 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 = lx.hsb(hue * 360, saturationParameter.getValuef() * 100, (flash.value) * 100);
347 for (LXPoint 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(double 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 (LXPoint 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.x - plane.center.x;
468 normalizedPoint.y = p.y - plane.center.y;
469 normalizedPoint.z = p.z - 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 = lx.hsb(plane.hue, saturation, 100);
477 } else if (d <= thickness * 2) {
478 float value = 1 - ((d - thickness) / thickness);
479 planeColor = lx.hsb(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 * Two spinning wheels, basically XORed together, with a color palette that should
500 * be pretty easy to switch around. Timed to the beat; also introduces "clickiness"
501 * which makes the movement non-linear throughout a given beat, giving it a nice
502 * dance feel. I'm not 100% sure that it's actually going to look like it's _on_
503 * the beat, but that should be easy enough to adjust.
504 *
505 * It's particularly nice to turn down the clickiness and turn up derez during
506 * slow/beatless parts of the music and then revert them at the drop :) But maybe
507 * I shouldn't be listening to so much shitty dubstep while making these...
508 */
509 class TimPinwheels extends SCPattern {
510 private BasicParameter horizSpreadParameter = new BasicParameter("HSpr", 0.75);
511 private BasicParameter vertSpreadParameter = new BasicParameter("VSpr", 0.5);
512 private BasicParameter vertOffsetParameter = new BasicParameter("VOff", 1.0);
513 private BasicParameter zSlopeParameter = new BasicParameter("ZSlp", 0.6);
514 private BasicParameter sharpnessParameter = new BasicParameter("Shrp", 0.25);
515 private BasicParameter derezParameter = new BasicParameter("Drez", 0.25);
516 private BasicParameter clickinessParameter = new BasicParameter("Clic", 0.5);
517 private BasicParameter hueParameter = new BasicParameter("Hue", 0.667);
518 private BasicParameter hueSpreadParameter = new BasicParameter("HSpd", 0.667);
519
520 float phase = 0;
521 private final int NUM_BLADES = 12;
522
523 class Pinwheel {
524 Vector2 center;
525 int numBlades;
526 float realPhase;
527 float phase;
528 float speed;
529
530 Pinwheel(float xCenter, float yCenter, int numBlades, float speed) {
531 this.center = new Vector2(xCenter, yCenter);
532 this.numBlades = numBlades;
533 this.speed = speed;
534 }
535
536 void age(float numBeats) {
537 int numSteps = numBlades;
538
539 realPhase = (realPhase + numBeats / numSteps) % 2.0;
540
541 float phaseStep = floor(realPhase * numSteps);
542 float phaseRamp = (realPhase * numSteps) % 1.0;
543 phase = (phaseStep + pow(phaseRamp, (clickinessParameter.getValuef() * 10) + 1)) / (numSteps * 2);
544 // phase = (phase + deltaMs / 1000.0 * speed) % 1.0;
545 }
546
547 boolean isOnBlade(float x, float y) {
548 x = x - center.x;
549 y = y - center.y;
550
551 float normalizedAngle = (atan2(x, y) / (2 * PI) + 1 + phase) % 1;
552 float v = (normalizedAngle * 4 * numBlades);
553 int blade_num = floor((v + 2) / 4);
554 return (blade_num % 2) == 0;
555 }
556 }
557
558 private final List<Pinwheel> pinwheels;
559 private final float[] values;
560
561 TimPinwheels(GLucose glucose) {
562 super(glucose);
563
564 addParameter(horizSpreadParameter);
565 // addParameter(vertSpreadParameter);
566 addParameter(vertOffsetParameter);
567 addParameter(zSlopeParameter);
568 addParameter(sharpnessParameter);
569 addParameter(derezParameter);
570 addParameter(clickinessParameter);
571 addParameter(hueParameter);
572 addParameter(hueSpreadParameter);
573
574 pinwheels = new ArrayList();
575 pinwheels.add(new Pinwheel(0, 0, NUM_BLADES, 0.1));
576 pinwheels.add(new Pinwheel(0, 0, NUM_BLADES, -0.1));
577
578 this.updateHorizSpread();
579 this.updateVertPositions();
580
581 values = new float[model.points.size()];
582 }
583
584 public void onParameterChanged(LXParameter parameter) {
585 if (parameter == horizSpreadParameter) {
586 updateHorizSpread();
587 } else if (parameter == vertSpreadParameter || parameter == vertOffsetParameter) {
588 updateVertPositions();
589 }
590 }
591
592 private void updateHorizSpread() {
593 float xDist = model.xMax - model.xMin;
594 float xCenter = (model.xMin + model.xMax) / 2;
595
596 float spread = horizSpreadParameter.getValuef() - 0.5;
597 pinwheels.get(0).center.x = xCenter - xDist * spread;
598 pinwheels.get(1).center.x = xCenter + xDist * spread;
599 }
600
601 private void updateVertPositions() {
602 float yDist = model.yMax - model.yMin;
603 float yCenter = model.yMin + yDist * vertOffsetParameter.getValuef();
604
605 float spread = vertSpreadParameter.getValuef() - 0.5;
606 pinwheels.get(0).center.y = yCenter - yDist * spread;
607 pinwheels.get(1).center.y = yCenter + yDist * spread;
608 }
609
610 private float prevRamp = 0;
611
612 public void run(double deltaMs) {
613 float ramp = lx.tempo.rampf();
614 float numBeats = (1 + ramp - prevRamp) % 1;
615 prevRamp = ramp;
616
617 float hue = hueParameter.getValuef() * 360;
618 // 0 -> -180
619 // 0.5 -> 0
620 // 1 -> 180
621 float hueSpread = (hueSpreadParameter.getValuef() - 0.5) * 360;
622
623 float fadeAmount = (float) (deltaMs / 1000.0) * pow(sharpnessParameter.getValuef() * 10, 1);
624
625 for (Pinwheel pw : pinwheels) {
626 pw.age(numBeats);
627 }
628
629 float derez = derezParameter.getValuef();
630
631 float zSlope = (zSlopeParameter.getValuef() - 0.5) * 2;
632
633 int i = -1;
634 for (LXPoint p : model.points) {
635 ++i;
636
637 int value = 0;
638 for (Pinwheel pw : pinwheels) {
639 value += (pw.isOnBlade(p.x, p.y - p.z * zSlope) ? 1 : 0);
640 }
641 if (value == 1) {
642 values[i] = 1;
643 // colors[p.index] = lx.hsb(120, 0, 100);
644 } else {
645 values[i] = max(0, values[i] - fadeAmount);
646 //color c = colors[p.index];
647 //colors[p.index] = lx.hsb(max(0, lx.h(c) - 10), min(100, lx.s(c) + 10), lx.b(c) - 5 );
648 }
649
650 if (random(1.0) >= derez) {
651 float v = values[i];
652 colors[p.index] = lx.hsb((360 + hue + pow(v, 2) * hueSpread) % 360, 30 + pow(1 - v, 0.25) * 60, v * 100);
653 }
654 }
655 }
656 }
657
658 /**
659 * This tries to figure out neighboring pixels from one cube to another to
660 * let you have a bunch of moving points tracing all over the structure.
661 * Adds a couple seconds of startup time to do the calculation, and in the
662 * end just comes out looking a lot like a screensaver. Probably not worth
663 * it but there may be useful code here.
664 */
665 class TimTrace extends SCPattern {
666 private Map<LXPoint, List<LXPoint>> pointToNeighbors;
667 private Map<LXPoint, Strip> pointToStrip;
668 // private final Map<Strip, List<Strip>> stripToNearbyStrips;
669
670 int extraMs;
671
672 class MovingPoint {
673 LXPoint currentPoint;
674 float hue;
675 private Strip currentStrip;
676 private int currentStripIndex;
677 private int direction; // +1 or -1
678
679 MovingPoint(LXPoint p) {
680 this.setPointOnNewStrip(p);
681 hue = random(360);
682 }
683
684 private void setPointOnNewStrip(LXPoint p) {
685 this.currentPoint = p;
686 this.currentStrip = pointToStrip.get(p);
687 for (int i = 0; i < this.currentStrip.points.size(); ++i) {
688 if (this.currentStrip.points.get(i) == p) {
689 this.currentStripIndex = i;
690 break;
691 }
692 }
693 if (this.currentStripIndex == 0) {
694 // we are at the beginning of the strip; go forwards
695 this.direction = 1;
696 } else if (this.currentStripIndex == this.currentStrip.points.size()) {
697 // we are at the end of the strip; go backwards
698 this.direction = -1;
699 } else {
700 // we are in the middle of a strip; randomly go one way or another
701 this.direction = ((random(1.0) < 0.5) ? -1 : 1);
702 }
703 }
704
705 void step() {
706 List<LXPoint> neighborsOnOtherStrips = pointToNeighbors.get(this.currentPoint);
707
708 LXPoint nextPointOnCurrentStrip = null;
709 this.currentStripIndex += this.direction;
710 if (this.currentStripIndex >= 0 && this.currentStripIndex < this.currentStrip.points.size()) {
711 nextPointOnCurrentStrip = this.currentStrip.points.get(this.currentStripIndex);
712 }
713
714 // pick which option to take; if we can keep going on the current strip then
715 // add that as another option
716 int option = floor(random(neighborsOnOtherStrips.size() + (nextPointOnCurrentStrip == null ? 0 : 100)));
717
718 if (option < neighborsOnOtherStrips.size()) {
719 this.setPointOnNewStrip(neighborsOnOtherStrips.get(option));
720 } else {
721 this.currentPoint = nextPointOnCurrentStrip;
722 }
723 }
724 }
725
726 List<MovingPoint> movingPoints;
727
728 TimTrace(GLucose glucose) {
729 super(glucose);
730
731 extraMs = 0;
732
733 pointToNeighbors = this.buildPointToNeighborsMap();
734 pointToStrip = this.buildPointToStripMap();
735
736 int numMovingPoints = 1000;
737 movingPoints = new ArrayList();
738 for (int i = 0; i < numMovingPoints; ++i) {
739 movingPoints.add(new MovingPoint(model.points.get(floor(random(model.points.size())))));
740 }
741
742 }
743
744 private Map<Strip, List<Strip>> buildStripToNearbyStripsMap() {
745 Map<Strip, Vector3> stripToCenter = new HashMap();
746 for (Strip s : model.strips) {
747 Vector3 v = new Vector3();
748 for (LXPoint p : s.points) {
749 v.add(p.x, p.y, p.z);
750 }
751 v.divide(s.points.size());
752 stripToCenter.put(s, v);
753 }
754
755 Map<Strip, List<Strip>> stripToNeighbors = new HashMap();
756 for (Strip s : model.strips) {
757 List<Strip> neighbors = new ArrayList();
758 Vector3 sCenter = stripToCenter.get(s);
759 for (Strip potentialNeighbor : model.strips) {
760 if (s != potentialNeighbor) {
761 float distance = sCenter.distanceTo(stripToCenter.get(potentialNeighbor));
762 if (distance < 25) {
763 neighbors.add(potentialNeighbor);
764 }
765 }
766 }
767 stripToNeighbors.put(s, neighbors);
768 }
769
770 return stripToNeighbors;
771 }
772
773 private Map<LXPoint, List<LXPoint>> buildPointToNeighborsMap() {
774 Map<LXPoint, List<LXPoint>> m = new HashMap();
775 Map<Strip, List<Strip>> stripToNearbyStrips = this.buildStripToNearbyStripsMap();
776
777 for (Strip s : model.strips) {
778 List<Strip> nearbyStrips = stripToNearbyStrips.get(s);
779
780 for (LXPoint p : s.points) {
781 Vector3 v = new Vector3(p.x, p.y, p.z);
782
783 List<LXPoint> neighbors = new ArrayList();
784
785 for (Strip nearbyStrip : nearbyStrips) {
786 LXPoint closestPoint = null;
787 float closestPointDistance = 100000;
788
789 for (LXPoint nsp : nearbyStrip.points) {
790 float distance = v.distanceTo(nsp.x, nsp.y, nsp.z);
791 if (closestPoint == null || distance < closestPointDistance) {
792 closestPoint = nsp;
793 closestPointDistance = distance;
794 }
795 }
796
797 if (closestPointDistance < 15) {
798 neighbors.add(closestPoint);
799 }
800 }
801
802 m.put(p, neighbors);
803 }
804 }
805
806 return m;
807 }
808
809 private Map<LXPoint, Strip> buildPointToStripMap() {
810 Map<LXPoint, Strip> m = new HashMap();
811 for (Strip s : model.strips) {
812 for (LXPoint p : s.points) {
813 m.put(p, s);
814 }
815 }
816 return m;
817 }
818
819 public void run(double deltaMs) {
820 for (LXPoint p : model.points) {
821 color c = colors[p.index];
822 colors[p.index] = lx.hsb(lx.h(c), lx.s(c), lx.b(c) - 3);
823 }
824
825 for (MovingPoint mp : movingPoints) {
826 mp.step();
827 colors[mp.currentPoint.index] = blendColor(colors[mp.currentPoint.index], lx.hsb(mp.hue, 10, 100), ADD);
828 }
829 }
830 }