SCPattern moved out of GLucose
[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 BasicParameter periodParameter = new BasicParameter("PERIOD", 4000.0, 200.0, 10000.0);
7 private final SawLFO lfo = new SawLFO(0, 1, 10000);
8 private final SinLFO sinLfo = new SinLFO(0, 1, periodParameter);
9 private final float centerX, centerY, centerZ;
10
11 class Sphere {
12 float x, y, z;
13 float radius;
14 float hue;
15 }
16
17 private final Sphere[] spheres;
18
19 public TimSpheres(LX lx) {
20 super(lx);
21 addParameter(hueParameter);
22 addParameter(periodParameter);
23 addModulator(lfo).trigger();
24 addModulator(sinLfo).trigger();
25 centerX = (model.xMax + model.xMin) / 2;
26 centerY = (model.yMax + model.yMin) / 2;
27 centerZ = (model.zMax + model.zMin) / 2;
28
29 spheres = new Sphere[2];
30
31 spheres[0] = new Sphere();
32 spheres[0].x = model.xMin;
33 spheres[0].y = centerY;
34 spheres[0].z = centerZ;
35 spheres[0].hue = 0;
36 spheres[0].radius = 50;
37
38 spheres[1] = new Sphere();
39 spheres[1].x = model.xMax;
40 spheres[1].y = centerY;
41 spheres[1].z = centerZ;
42 spheres[1].hue = 0.33;
43 spheres[1].radius = 50;
44 }
45
46 public void run(double deltaMs) {
47 // Access the core master hue via this method call
48 float hv = hueParameter.getValuef();
49 float lfoValue = lfo.getValuef();
50 float sinLfoValue = sinLfo.getValuef();
51
52 spheres[0].x = model.xMin + sinLfoValue * model.xMax;
53 spheres[1].x = model.xMax - sinLfoValue * model.xMax;
54
55 spheres[0].radius = 100 * hueParameter.getValuef();
56 spheres[1].radius = 100 * hueParameter.getValuef();
57
58 for (LXPoint p : model.points) {
59 float value = 0;
60
61 color c = lx.hsb(0, 0, 0);
62 for (Sphere s : spheres) {
63 float d = sqrt(pow(p.x - s.x, 2) + pow(p.y - s.y, 2) + pow(p.z - s.z, 2));
64 float r = (s.radius); // * (sinLfoValue + 0.5));
65 value = max(0, 1 - max(0, d - r) / 10);
66
67 c = blendColor(c, lx.hsb(((s.hue + lfoValue) % 1) * 360, 100, min(1, value) * 100), ADD);
68 }
69
70 colors[p.index] = c;
71 }
72 }
73 }
74
75 class Vector2 {
76 float x, y;
77
78 Vector2() {
79 this(0, 0);
80 }
81
82 Vector2(float x, float y) {
83 this.x = x;
84 this.y = y;
85 }
86
87 float distanceTo(float x, float y) {
88 return sqrt(pow(x - this.x, 2) + pow(y - this.y, 2));
89 }
90
91 float distanceTo(Vector2 v) {
92 return distanceTo(v.x, v.y);
93 }
94
95 Vector2 plus(float x, float y) {
96 return new Vector2(this.x + x, this.y + y);
97 }
98
99 Vector2 plus(Vector2 v) {
100 return plus(v.x, v.y);
101 }
102
103 Vector2 minus(Vector2 v) {
104 return plus(-1 * v.x, -1 * v.y);
105 }
106 }
107
108 class Vector3 {
109 float x, y, z;
110
111 Vector3() {
112 this(0, 0, 0);
113 }
114
115 Vector3(float x, float y, float z) {
116 this.x = x;
117 this.y = y;
118 this.z = z;
119 }
120
121 float distanceTo(float x, float y, float z) {
122 return sqrt(pow(x - this.x, 2) + pow(y - this.y, 2) + pow(z - this.z, 2));
123 }
124
125 float distanceTo(Vector3 v) {
126 return distanceTo(v.x, v.y, v.z);
127 }
128
129 float distanceTo(LXPoint p) {
130 return distanceTo(p.x, p.y, p.z);
131 }
132
133 void add(Vector3 other, float multiplier) {
134 this.add(other.x * multiplier, other.y * multiplier, other.z * multiplier);
135 }
136
137 void add(float x, float y, float z) {
138 this.x += x;
139 this.y += y;
140 this.z += z;
141 }
142
143 void divide(float factor) {
144 this.x /= factor;
145 this.y /= factor;
146 this.z /= factor;
147 }
148 }
149
150 class Rotation {
151 private float a, b, c, d, e, f, g, h, i;
152
153 Rotation(float yaw, float pitch, float roll) {
154 float cosYaw = cos(yaw);
155 float sinYaw = sin(yaw);
156 float cosPitch = cos(pitch);
157 float sinPitch = sin(pitch);
158 float cosRoll = cos(roll);
159 float sinRoll = sin(roll);
160
161 a = cosYaw * cosPitch;
162 b = cosYaw * sinPitch * sinRoll - sinYaw * cosRoll;
163 c = cosYaw * sinPitch * cosRoll + sinYaw * sinRoll;
164 d = sinYaw * cosPitch;
165 e = sinYaw * sinPitch * sinRoll + cosYaw * cosRoll;
166 f = sinYaw * sinPitch * cosRoll - cosYaw * sinRoll;
167 g = -1 * sinPitch;
168 h = cosPitch * sinRoll;
169 i = cosPitch * cosRoll;
170 }
171
172 Vector3 rotated(Vector3 v) {
173 return new Vector3(
174 rotatedX(v),
175 rotatedY(v),
176 rotatedZ(v));
177
178 }
179
180 float rotatedX(Vector3 v) {
181 return a * v.x + b * v.y + c * v.z;
182 }
183
184 float rotatedY(Vector3 v) {
185 return d * v.x + e * v.y + f * v.z;
186 }
187
188 float rotatedZ(Vector3 v) {
189 return g * v.x + h * v.y + i * v.z;
190 }
191 }
192
193 /**
194 * Very literal rain effect. Not that great as-is but some tweaking could make it nice.
195 * A couple ideas:
196 * - changing hue and direction of "rain" could make a nice fire effect
197 * - knobs to change frequency and size of rain drops
198 * - sync somehow to tempo but maybe less frequently than every beat?
199 */
200 class TimRaindrops extends SCPattern {
201 Vector3 randomVector3() {
202 return new Vector3(
203 random(model.xMax - model.xMin) + model.xMin,
204 random(model.yMax - model.yMin) + model.yMin,
205 random(model.zMax - model.zMin) + model.zMin);
206 }
207
208 class Raindrop {
209 Vector3 p;
210 Vector3 v;
211 float radius;
212 float hue;
213
214 Raindrop() {
215 this.radius = 30;
216 this.p = new Vector3(
217 random(model.xMax - model.xMin) + model.xMin,
218 model.yMax + this.radius,
219 random(model.zMax - model.zMin) + model.zMin);
220 float velMagnitude = 120;
221 this.v = new Vector3(
222 0,
223 -3 * model.yMax,
224 0);
225 this.hue = random(40) + 200;
226 }
227
228 // returns TRUE when this should die
229 boolean age(double ms) {
230 p.add(v, (float) (ms / 1000.0));
231 return this.p.y < (0 - this.radius);
232 }
233 }
234
235 private float leftoverMs = 0;
236 private float msPerRaindrop = 40;
237 private List<Raindrop> raindrops;
238
239 public TimRaindrops(LX lx) {
240 super(lx);
241 raindrops = new LinkedList<Raindrop>();
242 }
243
244 public void run(double deltaMs) {
245 leftoverMs += deltaMs;
246 while (leftoverMs > msPerRaindrop) {
247 leftoverMs -= msPerRaindrop;
248 raindrops.add(new Raindrop());
249 }
250
251 for (LXPoint p : model.points) {
252 color c =
253 blendColor(
254 lx.hsb(210, 20, (float)Math.max(0, 1 - Math.pow((model.yMax - p.y) / 10, 2)) * 50),
255 lx.hsb(220, 60, (float)Math.max(0, 1 - Math.pow((p.y - model.yMin) / 10, 2)) * 100),
256 ADD);
257 for (Raindrop raindrop : raindrops) {
258 if (p.x >= (raindrop.p.x - raindrop.radius) && p.x <= (raindrop.p.x + raindrop.radius) &&
259 p.y >= (raindrop.p.y - raindrop.radius) && p.y <= (raindrop.p.y + raindrop.radius)) {
260 float d = raindrop.p.distanceTo(p) / raindrop.radius;
261 // float value = (float)Math.max(0, 1 - Math.pow(Math.min(0, d - raindrop.radius) / 5, 2));
262 if (d < 1) {
263 c = blendColor(c, lx.hsb(raindrop.hue, 80, (float)Math.pow(1 - d, 0.01) * 100), ADD);
264 }
265 }
266 }
267 colors[p.index] = c;
268 }
269
270 Iterator<Raindrop> i = raindrops.iterator();
271 while (i.hasNext()) {
272 Raindrop raindrop = i.next();
273 boolean dead = raindrop.age(deltaMs);
274 if (dead) {
275 i.remove();
276 }
277 }
278 }
279 }
280
281
282 class TimCubes extends SCPattern {
283 private BasicParameter rateParameter = new BasicParameter("RATE", 0.125);
284 private BasicParameter attackParameter = new BasicParameter("ATTK", 0.5);
285 private BasicParameter decayParameter = new BasicParameter("DECAY", 0.5);
286 private BasicParameter hueParameter = new BasicParameter("HUE", 0.5);
287 private BasicParameter hueVarianceParameter = new BasicParameter("H.V.", 0.25);
288 private BasicParameter saturationParameter = new BasicParameter("SAT", 0.5);
289
290 class CubeFlash {
291 Cube c;
292 float value;
293 float hue;
294 boolean hasPeaked;
295
296 CubeFlash() {
297 c = model.cubes.get(floor(random(model.cubes.size())));
298 hue = random(1);
299 boolean infiniteAttack = (attackParameter.getValuef() > 0.999);
300 hasPeaked = infiniteAttack;
301 value = (infiniteAttack ? 1 : 0);
302 }
303
304 // returns TRUE if this should die
305 boolean age(double ms) {
306 if (!hasPeaked) {
307 value = value + (float) (ms / 1000.0f * ((attackParameter.getValuef() + 0.01) * 5));
308 if (value >= 1.0) {
309 value = 1.0;
310 hasPeaked = true;
311 }
312 return false;
313 } else {
314 value = value - (float) (ms / 1000.0f * ((decayParameter.getValuef() + 0.01) * 10));
315 return value <= 0;
316 }
317 }
318 }
319
320 private float leftoverMs = 0;
321 private List<CubeFlash> flashes;
322
323 public TimCubes(LX lx) {
324 super(lx);
325 addParameter(rateParameter);
326 addParameter(attackParameter);
327 addParameter(decayParameter);
328 addParameter(hueParameter);
329 addParameter(hueVarianceParameter);
330 addParameter(saturationParameter);
331 flashes = new LinkedList<CubeFlash>();
332 }
333
334 public void run(double deltaMs) {
335 leftoverMs += deltaMs;
336 float msPerFlash = 1000 / ((rateParameter.getValuef() + .01) * 100);
337 while (leftoverMs > msPerFlash) {
338 leftoverMs -= msPerFlash;
339 flashes.add(new CubeFlash());
340 }
341
342 for (LXPoint p : model.points) {
343 colors[p.index] = 0;
344 }
345
346 for (CubeFlash flash : flashes) {
347 float hue = (hueParameter.getValuef() + (hueVarianceParameter.getValuef() * flash.hue)) % 1.0;
348 color c = lx.hsb(hue * 360, saturationParameter.getValuef() * 100, (flash.value) * 100);
349 for (LXPoint p : flash.c.points) {
350 colors[p.index] = c;
351 }
352 }
353
354 Iterator<CubeFlash> i = flashes.iterator();
355 while (i.hasNext()) {
356 CubeFlash flash = i.next();
357 boolean dead = flash.age(deltaMs);
358 if (dead) {
359 i.remove();
360 }
361 }
362 }
363 }
364
365 /**
366 * This one is the best but you need to play with all the knobs. It's synced to
367 * the tempo, with the WSpd knob letting you pick 4 discrete multipliers for
368 * the tempo.
369 *
370 * Basically it's just 3 planes all rotating to the beat, but also rotated relative
371 * to one another. The intersection of the planes and the cubes over time makes
372 * for a nice abstract effect.
373 */
374 class TimPlanes extends SCPattern {
375 private BasicParameter wobbleParameter = new BasicParameter("Wob", 0.166);
376 private BasicParameter wobbleSpreadParameter = new BasicParameter("WSpr", 0.25);
377 private BasicParameter wobbleSpeedParameter = new BasicParameter("WSpd", 0.375);
378 private BasicParameter wobbleOffsetParameter = new BasicParameter("WOff", 0);
379 private BasicParameter derezParameter = new BasicParameter("Drez", 0.5);
380 private BasicParameter thicknessParameter = new BasicParameter("Thick", 0.4);
381 private BasicParameter ySpreadParameter = new BasicParameter("ySpr", 0.2);
382 private BasicParameter hueParameter = new BasicParameter("Hue", 0.75);
383 private BasicParameter hueSpreadParameter = new BasicParameter("HSpr", 0.68);
384
385 final float centerX, centerY, centerZ;
386 float phase;
387
388 class Plane {
389 Vector3 center;
390 Rotation rotation;
391 float hue;
392
393 Plane(Vector3 center, Rotation rotation, float hue) {
394 this.center = center;
395 this.rotation = rotation;
396 this.hue = hue;
397 }
398 }
399
400 TimPlanes(LX lx) {
401 super(lx);
402 centerX = (model.xMin + model.xMax) / 2;
403 centerY = (model.yMin + model.yMax) / 2;
404 centerZ = (model.zMin + model.zMax) / 2;
405 phase = 0;
406 addParameter(wobbleParameter);
407 addParameter(wobbleSpreadParameter);
408 addParameter(wobbleSpeedParameter);
409 // addParameter(wobbleOffsetParameter);
410 addParameter(derezParameter);
411 addParameter(thicknessParameter);
412 addParameter(ySpreadParameter);
413 addParameter(hueParameter);
414 addParameter(hueSpreadParameter);
415 }
416
417 int beat = 0;
418 float prevRamp = 0;
419 float[] wobbleSpeeds = { 1.0/8, 1.0/4, 1.0/2, 1.0 };
420
421 public void run(double deltaMs) {
422 float ramp = (float)lx.tempo.ramp();
423 if (ramp < prevRamp) {
424 beat = (beat + 1) % 32;
425 }
426 prevRamp = ramp;
427
428 float wobbleSpeed = wobbleSpeeds[floor(wobbleSpeedParameter.getValuef() * wobbleSpeeds.length * 0.9999)];
429
430 phase = (((beat + ramp) * wobbleSpeed + wobbleOffsetParameter.getValuef()) % 1) * 2 * PI;
431
432 float ySpread = ySpreadParameter.getValuef() * 50;
433 float wobble = wobbleParameter.getValuef() * PI;
434 float wobbleSpread = wobbleSpreadParameter.getValuef() * PI;
435 float hue = hueParameter.getValuef() * 360;
436 float hueSpread = (hueSpreadParameter.getValuef() - 0.5) * 360;
437
438 float saturation = 10 + 60.0 * pow(ramp, 0.25);
439
440 float derez = derezParameter.getValuef();
441
442 Plane[] planes = {
443 new Plane(
444 new Vector3(centerX, centerY + ySpread, centerZ),
445 new Rotation(wobble - wobbleSpread, phase, 0),
446 (hue + 360 - hueSpread) % 360),
447 new Plane(
448 new Vector3(centerX, centerY, centerZ),
449 new Rotation(wobble, phase, 0),
450 hue),
451 new Plane(
452 new Vector3(centerX, centerY - ySpread, centerZ),
453 new Rotation(wobble + wobbleSpread, phase, 0),
454 (hue + 360 + hueSpread) % 360)
455 };
456
457 float thickness = (thicknessParameter.getValuef() * 25 + 1);
458
459 Vector3 normalizedPoint = new Vector3();
460
461 for (LXPoint p : model.points) {
462 if (random(1.0) < derez) {
463 continue;
464 }
465
466 color c = 0;
467
468 for (Plane plane : planes) {
469 normalizedPoint.x = p.x - plane.center.x;
470 normalizedPoint.y = p.y - plane.center.y;
471 normalizedPoint.z = p.z - plane.center.z;
472
473 float v = plane.rotation.rotatedY(normalizedPoint);
474 float d = abs(v);
475
476 final color planeColor;
477 if (d <= thickness) {
478 planeColor = lx.hsb(plane.hue, saturation, 100);
479 } else if (d <= thickness * 2) {
480 float value = 1 - ((d - thickness) / thickness);
481 planeColor = lx.hsb(plane.hue, saturation, value * 100);
482 } else {
483 planeColor = 0;
484 }
485
486 if (planeColor != 0) {
487 if (c == 0) {
488 c = planeColor;
489 } else {
490 c = blendColor(c, planeColor, ADD);
491 }
492 }
493 }
494
495 colors[p.index] = c;
496 }
497 }
498 }
499
500 /**
501 * Two spinning wheels, basically XORed together, with a color palette that should
502 * be pretty easy to switch around. Timed to the beat; also introduces "clickiness"
503 * which makes the movement non-linear throughout a given beat, giving it a nice
504 * dance feel. I'm not 100% sure that it's actually going to look like it's _on_
505 * the beat, but that should be easy enough to adjust.
506 *
507 * It's particularly nice to turn down the clickiness and turn up derez during
508 * slow/beatless parts of the music and then revert them at the drop :) But maybe
509 * I shouldn't be listening to so much shitty dubstep while making these...
510 */
511 class TimPinwheels extends SCPattern {
512 private BasicParameter horizSpreadParameter = new BasicParameter("HSpr", 0.75);
513 private BasicParameter vertSpreadParameter = new BasicParameter("VSpr", 0.5);
514 private BasicParameter vertOffsetParameter = new BasicParameter("VOff", 1.0);
515 private BasicParameter zSlopeParameter = new BasicParameter("ZSlp", 0.6);
516 private BasicParameter sharpnessParameter = new BasicParameter("Shrp", 0.25);
517 private BasicParameter derezParameter = new BasicParameter("Drez", 0.25);
518 private BasicParameter clickinessParameter = new BasicParameter("Clic", 0.5);
519 private BasicParameter hueParameter = new BasicParameter("Hue", 0.667);
520 private BasicParameter hueSpreadParameter = new BasicParameter("HSpd", 0.667);
521
522 float phase = 0;
523 private final int NUM_BLADES = 12;
524
525 class Pinwheel {
526 Vector2 center;
527 int numBlades;
528 float realPhase;
529 float phase;
530 float speed;
531
532 Pinwheel(float xCenter, float yCenter, int numBlades, float speed) {
533 this.center = new Vector2(xCenter, yCenter);
534 this.numBlades = numBlades;
535 this.speed = speed;
536 }
537
538 void age(float numBeats) {
539 int numSteps = numBlades;
540
541 realPhase = (realPhase + numBeats / numSteps) % 2.0;
542
543 float phaseStep = floor(realPhase * numSteps);
544 float phaseRamp = (realPhase * numSteps) % 1.0;
545 phase = (phaseStep + pow(phaseRamp, (clickinessParameter.getValuef() * 10) + 1)) / (numSteps * 2);
546 // phase = (phase + deltaMs / 1000.0 * speed) % 1.0;
547 }
548
549 boolean isOnBlade(float x, float y) {
550 x = x - center.x;
551 y = y - center.y;
552
553 float normalizedAngle = (atan2(x, y) / (2 * PI) + 1 + phase) % 1;
554 float v = (normalizedAngle * 4 * numBlades);
555 int blade_num = floor((v + 2) / 4);
556 return (blade_num % 2) == 0;
557 }
558 }
559
560 private final List<Pinwheel> pinwheels;
561 private final float[] values;
562
563 TimPinwheels(LX lx) {
564 super(lx);
565
566 addParameter(horizSpreadParameter);
567 // addParameter(vertSpreadParameter);
568 addParameter(vertOffsetParameter);
569 addParameter(zSlopeParameter);
570 addParameter(sharpnessParameter);
571 addParameter(derezParameter);
572 addParameter(clickinessParameter);
573 addParameter(hueParameter);
574 addParameter(hueSpreadParameter);
575
576 pinwheels = new ArrayList();
577 pinwheels.add(new Pinwheel(0, 0, NUM_BLADES, 0.1));
578 pinwheels.add(new Pinwheel(0, 0, NUM_BLADES, -0.1));
579
580 this.updateHorizSpread();
581 this.updateVertPositions();
582
583 values = new float[model.points.size()];
584 }
585
586 public void onParameterChanged(LXParameter parameter) {
587 if (parameter == horizSpreadParameter) {
588 updateHorizSpread();
589 } else if (parameter == vertSpreadParameter || parameter == vertOffsetParameter) {
590 updateVertPositions();
591 }
592 }
593
594 private void updateHorizSpread() {
595 float xDist = model.xMax - model.xMin;
596 float xCenter = (model.xMin + model.xMax) / 2;
597
598 float spread = horizSpreadParameter.getValuef() - 0.5;
599 pinwheels.get(0).center.x = xCenter - xDist * spread;
600 pinwheels.get(1).center.x = xCenter + xDist * spread;
601 }
602
603 private void updateVertPositions() {
604 float yDist = model.yMax - model.yMin;
605 float yCenter = model.yMin + yDist * vertOffsetParameter.getValuef();
606
607 float spread = vertSpreadParameter.getValuef() - 0.5;
608 pinwheels.get(0).center.y = yCenter - yDist * spread;
609 pinwheels.get(1).center.y = yCenter + yDist * spread;
610 }
611
612 private float prevRamp = 0;
613
614 public void run(double deltaMs) {
615 float ramp = lx.tempo.rampf();
616 float numBeats = (1 + ramp - prevRamp) % 1;
617 prevRamp = ramp;
618
619 float hue = hueParameter.getValuef() * 360;
620 // 0 -> -180
621 // 0.5 -> 0
622 // 1 -> 180
623 float hueSpread = (hueSpreadParameter.getValuef() - 0.5) * 360;
624
625 float fadeAmount = (float) (deltaMs / 1000.0) * pow(sharpnessParameter.getValuef() * 10, 1);
626
627 for (Pinwheel pw : pinwheels) {
628 pw.age(numBeats);
629 }
630
631 float derez = derezParameter.getValuef();
632
633 float zSlope = (zSlopeParameter.getValuef() - 0.5) * 2;
634
635 int i = -1;
636 for (LXPoint p : model.points) {
637 ++i;
638
639 int value = 0;
640 for (Pinwheel pw : pinwheels) {
641 value += (pw.isOnBlade(p.x, p.y - p.z * zSlope) ? 1 : 0);
642 }
643 if (value == 1) {
644 values[i] = 1;
645 // colors[p.index] = lx.hsb(120, 0, 100);
646 } else {
647 values[i] = max(0, values[i] - fadeAmount);
648 //color c = colors[p.index];
649 //colors[p.index] = lx.hsb(max(0, lx.h(c) - 10), min(100, lx.s(c) + 10), lx.b(c) - 5 );
650 }
651
652 if (random(1.0) >= derez) {
653 float v = values[i];
654 colors[p.index] = lx.hsb((360 + hue + pow(v, 2) * hueSpread) % 360, 30 + pow(1 - v, 0.25) * 60, v * 100);
655 }
656 }
657 }
658 }
659
660 /**
661 * This tries to figure out neighboring pixels from one cube to another to
662 * let you have a bunch of moving points tracing all over the structure.
663 * Adds a couple seconds of startup time to do the calculation, and in the
664 * end just comes out looking a lot like a screensaver. Probably not worth
665 * it but there may be useful code here.
666 */
667 class TimTrace extends SCPattern {
668 private Map<LXPoint, List<LXPoint>> pointToNeighbors;
669 private Map<LXPoint, Strip> pointToStrip;
670 // private final Map<Strip, List<Strip>> stripToNearbyStrips;
671
672 int extraMs;
673
674 class MovingPoint {
675 LXPoint currentPoint;
676 float hue;
677 private Strip currentStrip;
678 private int currentStripIndex;
679 private int direction; // +1 or -1
680
681 MovingPoint(LXPoint p) {
682 this.setPointOnNewStrip(p);
683 hue = random(360);
684 }
685
686 private void setPointOnNewStrip(LXPoint p) {
687 this.currentPoint = p;
688 this.currentStrip = pointToStrip.get(p);
689 for (int i = 0; i < this.currentStrip.points.size(); ++i) {
690 if (this.currentStrip.points.get(i) == p) {
691 this.currentStripIndex = i;
692 break;
693 }
694 }
695 if (this.currentStripIndex == 0) {
696 // we are at the beginning of the strip; go forwards
697 this.direction = 1;
698 } else if (this.currentStripIndex == this.currentStrip.points.size()) {
699 // we are at the end of the strip; go backwards
700 this.direction = -1;
701 } else {
702 // we are in the middle of a strip; randomly go one way or another
703 this.direction = ((random(1.0) < 0.5) ? -1 : 1);
704 }
705 }
706
707 void step() {
708 List<LXPoint> neighborsOnOtherStrips = pointToNeighbors.get(this.currentPoint);
709
710 LXPoint nextPointOnCurrentStrip = null;
711 this.currentStripIndex += this.direction;
712 if (this.currentStripIndex >= 0 && this.currentStripIndex < this.currentStrip.points.size()) {
713 nextPointOnCurrentStrip = this.currentStrip.points.get(this.currentStripIndex);
714 }
715
716 // pick which option to take; if we can keep going on the current strip then
717 // add that as another option
718 int option = floor(random(neighborsOnOtherStrips.size() + (nextPointOnCurrentStrip == null ? 0 : 100)));
719
720 if (option < neighborsOnOtherStrips.size()) {
721 this.setPointOnNewStrip(neighborsOnOtherStrips.get(option));
722 } else {
723 this.currentPoint = nextPointOnCurrentStrip;
724 }
725 }
726 }
727
728 List<MovingPoint> movingPoints;
729
730 TimTrace(LX lx) {
731 super(lx);
732
733 extraMs = 0;
734
735 pointToNeighbors = this.buildPointToNeighborsMap();
736 pointToStrip = this.buildPointToStripMap();
737
738 int numMovingPoints = 1000;
739 movingPoints = new ArrayList();
740 for (int i = 0; i < numMovingPoints; ++i) {
741 movingPoints.add(new MovingPoint(model.points.get(floor(random(model.points.size())))));
742 }
743
744 }
745
746 private Map<Strip, List<Strip>> buildStripToNearbyStripsMap() {
747 Map<Strip, Vector3> stripToCenter = new HashMap();
748 for (Strip s : model.strips) {
749 Vector3 v = new Vector3();
750 for (LXPoint p : s.points) {
751 v.add(p.x, p.y, p.z);
752 }
753 v.divide(s.points.size());
754 stripToCenter.put(s, v);
755 }
756
757 Map<Strip, List<Strip>> stripToNeighbors = new HashMap();
758 for (Strip s : model.strips) {
759 List<Strip> neighbors = new ArrayList();
760 Vector3 sCenter = stripToCenter.get(s);
761 for (Strip potentialNeighbor : model.strips) {
762 if (s != potentialNeighbor) {
763 float distance = sCenter.distanceTo(stripToCenter.get(potentialNeighbor));
764 if (distance < 25) {
765 neighbors.add(potentialNeighbor);
766 }
767 }
768 }
769 stripToNeighbors.put(s, neighbors);
770 }
771
772 return stripToNeighbors;
773 }
774
775 private Map<LXPoint, List<LXPoint>> buildPointToNeighborsMap() {
776 Map<LXPoint, List<LXPoint>> m = new HashMap();
777 Map<Strip, List<Strip>> stripToNearbyStrips = this.buildStripToNearbyStripsMap();
778
779 for (Strip s : model.strips) {
780 List<Strip> nearbyStrips = stripToNearbyStrips.get(s);
781
782 for (LXPoint p : s.points) {
783 Vector3 v = new Vector3(p.x, p.y, p.z);
784
785 List<LXPoint> neighbors = new ArrayList();
786
787 for (Strip nearbyStrip : nearbyStrips) {
788 LXPoint closestPoint = null;
789 float closestPointDistance = 100000;
790
791 for (LXPoint nsp : nearbyStrip.points) {
792 float distance = v.distanceTo(nsp.x, nsp.y, nsp.z);
793 if (closestPoint == null || distance < closestPointDistance) {
794 closestPoint = nsp;
795 closestPointDistance = distance;
796 }
797 }
798
799 if (closestPointDistance < 15) {
800 neighbors.add(closestPoint);
801 }
802 }
803
804 m.put(p, neighbors);
805 }
806 }
807
808 return m;
809 }
810
811 private Map<LXPoint, Strip> buildPointToStripMap() {
812 Map<LXPoint, Strip> m = new HashMap();
813 for (Strip s : model.strips) {
814 for (LXPoint p : s.points) {
815 m.put(p, s);
816 }
817 }
818 return m;
819 }
820
821 public void run(double deltaMs) {
822 for (LXPoint p : model.points) {
823 color c = colors[p.index];
824 colors[p.index] = lx.hsb(lx.h(c), lx.s(c), lx.b(c) - 3);
825 }
826
827 for (MovingPoint mp : movingPoints) {
828 mp.step();
829 colors[mp.currentPoint.index] = blendColor(colors[mp.currentPoint.index], lx.hsb(mp.hue, 10, 100), ADD);
830 }
831 }
832 }
833
834 class TimMetronome extends SCPattern {
835 private BasicParameter clickyParameter = new BasicParameter("CLICK", 0, 0, 10.0);
836 private BasicParameter derezParameter = new BasicParameter("DREZ", 0.5, 0, 1.0);
837 private BasicParameter driftParameter = new BasicParameter("DRIFT", 0, 0, 1.0);
838 private BasicParameter fadeParameter = new BasicParameter("FADE", 0.05, 0, 0.2);
839 private float modelWidth;
840 private int beatNum;
841 private float prevTempoRamp;
842 private LXProjection projection;
843 private float[] values;
844 private float[] hues;
845
846 TimMetronome(LX lx) {
847 super(lx);
848 addParameter(clickyParameter);
849 addParameter(derezParameter);
850 addParameter(driftParameter);
851 addParameter(fadeParameter);
852 modelWidth = model.xMax - model.xMin;
853 projection = new LXProjection(model);
854 beatNum = 0;
855 prevTempoRamp = 0;
856 values = new float[model.points.size()];
857 hues = new float[model.points.size()];
858 }
859
860 public void run(double deltaMs) {
861 float tempoRamp = lx.tempo.rampf();
862 if (tempoRamp < prevTempoRamp) {
863 beatNum = (beatNum + 1) % 1000;
864 }
865 prevTempoRamp = tempoRamp;
866
867 float phase = beatNum + pow(tempoRamp, 1.0 + clickyParameter.getValuef());
868
869 projection.reset();
870 projection.translateCenter(model.xMin, model.yMin, model.cz);
871 projection.rotate(phase * 0.5 * PI, 0, 0, 1);
872
873 projection.translate(driftParameter.getValuef() * tempoRamp * modelWidth * 0.5, 0, 0);
874
875 float derezCutoff = derezParameter.getValuef();
876
877 float fadeMultiplier = (1.0 - fadeParameter.getValuef());
878
879 float armRadius = modelWidth * 0.1;
880 for (LXVector p : projection) {
881 boolean onArm = false;
882 if (abs(p.x) < armRadius) {
883 onArm = (p.y > 0) || (sqrt(pow(p.x, 2) + pow(p.y, 2)) < armRadius);
884 }
885 if (onArm) {
886 values[p.index] = 1.0;
887 hues[p.index] = (floor(phase / 4) * 90) % 360;
888 } else {
889 values[p.index] *= fadeMultiplier;
890 }
891
892 float saturation = pow(1 - values[p.index], 0.5) * 0.7 + 0.3;
893 float brightness = values[p.index];
894
895 if (random(1.0) > derezCutoff) {
896 colors[p.index] = lx.hsb(hues[p.index], saturation * 100, brightness * 100);
897 }
898 }
899 }
900 }
901