Commit | Line | Data |
---|---|---|
d564b8db | 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); | |
9f73bc0e | 6 | private BasicParameter periodParameter = new BasicParameter("PERIOD", 4000.0, 200.0, 10000.0); |
d564b8db | 7 | private final SawLFO lfo = new SawLFO(0, 1, 10000); |
9f73bc0e | 8 | private final SinLFO sinLfo = new SinLFO(0, 1, periodParameter); |
d564b8db | 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(GLucose glucose) { | |
20 | super(glucose); | |
21 | addParameter(hueParameter); | |
9f73bc0e | 22 | addParameter(periodParameter); |
d564b8db | 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 | ||
34327c96 | 46 | public void run(double deltaMs) { |
d564b8db | 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 | ||
2bb56822 | 58 | for (LXPoint p : model.points) { |
d564b8db | 59 | float value = 0; |
60 | ||
a41f334c | 61 | color c = lx.hsb(0, 0, 0); |
d564b8db | 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 | ||
a41f334c | 67 | c = blendColor(c, lx.hsb(((s.hue + lfoValue) % 1) * 360, 100, min(1, value) * 100), ADD); |
d564b8db | 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 | ||
2bb56822 | 129 | float distanceTo(LXPoint p) { |
190d91c2 | 130 | return distanceTo(p.x, p.y, p.z); |
d564b8db | 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 | |
34327c96 MS |
229 | boolean age(double ms) { |
230 | p.add(v, (float) (ms / 1000.0)); | |
d564b8db | 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(GLucose glucose) { | |
240 | super(glucose); | |
241 | raindrops = new LinkedList<Raindrop>(); | |
242 | } | |
243 | ||
34327c96 | 244 | public void run(double deltaMs) { |
d564b8db | 245 | leftoverMs += deltaMs; |
246 | while (leftoverMs > msPerRaindrop) { | |
247 | leftoverMs -= msPerRaindrop; | |
248 | raindrops.add(new Raindrop()); | |
249 | } | |
250 | ||
2bb56822 | 251 | for (LXPoint p : model.points) { |
d564b8db | 252 | color c = |
253 | blendColor( | |
a41f334c MS |
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), | |
d564b8db | 256 | ADD); |
257 | for (Raindrop raindrop : raindrops) { | |
190d91c2 MS |
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)) { | |
d564b8db | 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) { | |
a41f334c | 263 | c = blendColor(c, lx.hsb(raindrop.hue, 80, (float)Math.pow(1 - d, 0.01) * 100), ADD); |
d564b8db | 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 | |
34327c96 | 305 | boolean age(double ms) { |
d564b8db | 306 | if (!hasPeaked) { |
34327c96 | 307 | value = value + (float) (ms / 1000.0f * ((attackParameter.getValuef() + 0.01) * 5)); |
d564b8db | 308 | if (value >= 1.0) { |
309 | value = 1.0; | |
310 | hasPeaked = true; | |
311 | } | |
312 | return false; | |
313 | } else { | |
34327c96 | 314 | value = value - (float) (ms / 1000.0f * ((decayParameter.getValuef() + 0.01) * 10)); |
d564b8db | 315 | return value <= 0; |
316 | } | |
317 | } | |
318 | } | |
319 | ||
320 | private float leftoverMs = 0; | |
321 | private List<CubeFlash> flashes; | |
322 | ||
323 | public TimCubes(GLucose glucose) { | |
324 | super(glucose); | |
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 | ||
34327c96 | 334 | public void run(double deltaMs) { |
d564b8db | 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 | ||
2bb56822 | 342 | for (LXPoint p : model.points) { |
d564b8db | 343 | colors[p.index] = 0; |
344 | } | |
345 | ||
346 | for (CubeFlash flash : flashes) { | |
347 | float hue = (hueParameter.getValuef() + (hueVarianceParameter.getValuef() * flash.hue)) % 1.0; | |
a41f334c | 348 | color c = lx.hsb(hue * 360, saturationParameter.getValuef() * 100, (flash.value) * 100); |
2bb56822 | 349 | for (LXPoint p : flash.c.points) { |
d564b8db | 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 { | |
eecf5d12 | 375 | private BasicParameter wobbleParameter = new BasicParameter("Wob", 0.166); |
d564b8db | 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(GLucose glucose) { | |
401 | super(glucose); | |
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 | ||
d564b8db | 417 | int beat = 0; |
418 | float prevRamp = 0; | |
419 | float[] wobbleSpeeds = { 1.0/8, 1.0/4, 1.0/2, 1.0 }; | |
420 | ||
34327c96 | 421 | public void run(double deltaMs) { |
d564b8db | 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 | }; | |
eecf5d12 | 456 | |
457 | float thickness = (thicknessParameter.getValuef() * 25 + 1); | |
d564b8db | 458 | |
eecf5d12 | 459 | Vector3 normalizedPoint = new Vector3(); |
460 | ||
2bb56822 | 461 | for (LXPoint p : model.points) { |
d564b8db | 462 | if (random(1.0) < derez) { |
463 | continue; | |
464 | } | |
465 | ||
466 | color c = 0; | |
467 | ||
468 | for (Plane plane : planes) { | |
190d91c2 MS |
469 | normalizedPoint.x = p.x - plane.center.x; |
470 | normalizedPoint.y = p.y - plane.center.y; | |
471 | normalizedPoint.z = p.z - plane.center.z; | |
eecf5d12 | 472 | |
473 | float v = plane.rotation.rotatedY(normalizedPoint); | |
474 | float d = abs(v); | |
475 | ||
476 | final color planeColor; | |
477 | if (d <= thickness) { | |
a41f334c | 478 | planeColor = lx.hsb(plane.hue, saturation, 100); |
eecf5d12 | 479 | } else if (d <= thickness * 2) { |
480 | float value = 1 - ((d - thickness) / thickness); | |
a41f334c | 481 | planeColor = lx.hsb(plane.hue, saturation, value * 100); |
eecf5d12 | 482 | } else { |
483 | planeColor = 0; | |
484 | } | |
485 | ||
d564b8db | 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 | /** | |
be8ca2ec | 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... | |
d564b8db | 510 | */ |
511 | class TimPinwheels extends SCPattern { | |
f5540dab | 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); | |
be8ca2ec | 520 | private BasicParameter hueSpreadParameter = new BasicParameter("HSpd", 0.667); |
f5540dab | 521 | |
d564b8db | 522 | float phase = 0; |
f5540dab | 523 | private final int NUM_BLADES = 12; |
d564b8db | 524 | |
525 | class Pinwheel { | |
526 | Vector2 center; | |
527 | int numBlades; | |
f5540dab | 528 | float realPhase; |
d564b8db | 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 | ||
f5540dab | 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; | |
d564b8db | 547 | } |
548 | ||
549 | boolean isOnBlade(float x, float y) { | |
550 | x = x - center.x; | |
551 | y = y - center.y; | |
552 | ||
f5540dab | 553 | float normalizedAngle = (atan2(x, y) / (2 * PI) + 1 + phase) % 1; |
d564b8db | 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; | |
f5540dab | 561 | private final float[] values; |
d564b8db | 562 | |
563 | TimPinwheels(GLucose glucose) { | |
564 | super(glucose); | |
565 | ||
f5540dab | 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() { | |
d564b8db | 595 | float xDist = model.xMax - model.xMin; |
596 | float xCenter = (model.xMin + model.xMax) / 2; | |
f5540dab | 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(); | |
d564b8db | 606 | |
f5540dab | 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; | |
d564b8db | 610 | } |
611 | ||
f5540dab | 612 | private float prevRamp = 0; |
613 | ||
34327c96 | 614 | public void run(double deltaMs) { |
f5540dab | 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 | ||
34327c96 | 625 | float fadeAmount = (float) (deltaMs / 1000.0) * pow(sharpnessParameter.getValuef() * 10, 1); |
f5540dab | 626 | |
d564b8db | 627 | for (Pinwheel pw : pinwheels) { |
f5540dab | 628 | pw.age(numBeats); |
d564b8db | 629 | } |
630 | ||
f5540dab | 631 | float derez = derezParameter.getValuef(); |
632 | ||
633 | float zSlope = (zSlopeParameter.getValuef() - 0.5) * 2; | |
634 | ||
635 | int i = -1; | |
2bb56822 | 636 | for (LXPoint p : model.points) { |
f5540dab | 637 | ++i; |
638 | ||
d564b8db | 639 | int value = 0; |
640 | for (Pinwheel pw : pinwheels) { | |
190d91c2 | 641 | value += (pw.isOnBlade(p.x, p.y - p.z * zSlope) ? 1 : 0); |
d564b8db | 642 | } |
643 | if (value == 1) { | |
f5540dab | 644 | values[i] = 1; |
a41f334c | 645 | // colors[p.index] = lx.hsb(120, 0, 100); |
d564b8db | 646 | } else { |
f5540dab | 647 | values[i] = max(0, values[i] - fadeAmount); |
648 | //color c = colors[p.index]; | |
a41f334c | 649 | //colors[p.index] = lx.hsb(max(0, lx.h(c) - 10), min(100, lx.s(c) + 10), lx.b(c) - 5 ); |
d564b8db | 650 | } |
f5540dab | 651 | |
652 | if (random(1.0) >= derez) { | |
653 | float v = values[i]; | |
a41f334c | 654 | colors[p.index] = lx.hsb((360 + hue + pow(v, 2) * hueSpread) % 360, 30 + pow(1 - v, 0.25) * 60, v * 100); |
f5540dab | 655 | } |
d564b8db | 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 { | |
2bb56822 MS |
668 | private Map<LXPoint, List<LXPoint>> pointToNeighbors; |
669 | private Map<LXPoint, Strip> pointToStrip; | |
d564b8db | 670 | // private final Map<Strip, List<Strip>> stripToNearbyStrips; |
671 | ||
672 | int extraMs; | |
673 | ||
674 | class MovingPoint { | |
2bb56822 | 675 | LXPoint currentPoint; |
d564b8db | 676 | float hue; |
677 | private Strip currentStrip; | |
678 | private int currentStripIndex; | |
679 | private int direction; // +1 or -1 | |
680 | ||
2bb56822 | 681 | MovingPoint(LXPoint p) { |
d564b8db | 682 | this.setPointOnNewStrip(p); |
683 | hue = random(360); | |
684 | } | |
685 | ||
2bb56822 | 686 | private void setPointOnNewStrip(LXPoint p) { |
d564b8db | 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() { | |
2bb56822 | 708 | List<LXPoint> neighborsOnOtherStrips = pointToNeighbors.get(this.currentPoint); |
d564b8db | 709 | |
2bb56822 | 710 | LXPoint nextPointOnCurrentStrip = null; |
d564b8db | 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(GLucose glucose) { | |
731 | super(glucose); | |
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(); | |
2bb56822 | 750 | for (LXPoint p : s.points) { |
190d91c2 | 751 | v.add(p.x, p.y, p.z); |
d564b8db | 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 | ||
2bb56822 MS |
775 | private Map<LXPoint, List<LXPoint>> buildPointToNeighborsMap() { |
776 | Map<LXPoint, List<LXPoint>> m = new HashMap(); | |
d564b8db | 777 | Map<Strip, List<Strip>> stripToNearbyStrips = this.buildStripToNearbyStripsMap(); |
778 | ||
779 | for (Strip s : model.strips) { | |
780 | List<Strip> nearbyStrips = stripToNearbyStrips.get(s); | |
781 | ||
2bb56822 | 782 | for (LXPoint p : s.points) { |
190d91c2 | 783 | Vector3 v = new Vector3(p.x, p.y, p.z); |
d564b8db | 784 | |
2bb56822 | 785 | List<LXPoint> neighbors = new ArrayList(); |
d564b8db | 786 | |
787 | for (Strip nearbyStrip : nearbyStrips) { | |
2bb56822 | 788 | LXPoint closestPoint = null; |
d564b8db | 789 | float closestPointDistance = 100000; |
790 | ||
2bb56822 | 791 | for (LXPoint nsp : nearbyStrip.points) { |
190d91c2 | 792 | float distance = v.distanceTo(nsp.x, nsp.y, nsp.z); |
d564b8db | 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 | ||
2bb56822 MS |
811 | private Map<LXPoint, Strip> buildPointToStripMap() { |
812 | Map<LXPoint, Strip> m = new HashMap(); | |
d564b8db | 813 | for (Strip s : model.strips) { |
2bb56822 | 814 | for (LXPoint p : s.points) { |
d564b8db | 815 | m.put(p, s); |
816 | } | |
817 | } | |
818 | return m; | |
819 | } | |
820 | ||
34327c96 | 821 | public void run(double deltaMs) { |
2bb56822 | 822 | for (LXPoint p : model.points) { |
d564b8db | 823 | color c = colors[p.index]; |
a41f334c | 824 | colors[p.index] = lx.hsb(lx.h(c), lx.s(c), lx.b(c) - 3); |
d564b8db | 825 | } |
826 | ||
827 | for (MovingPoint mp : movingPoints) { | |
828 | mp.step(); | |
a41f334c | 829 | colors[mp.currentPoint.index] = blendColor(colors[mp.currentPoint.index], lx.hsb(mp.hue, 10, 100), ADD); |
d564b8db | 830 | } |
831 | } | |
832 | } | |
9f73bc0e | 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(GLucose glucose) { | |
847 | super(glucose); | |
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 |