Commit | Line | Data |
---|---|---|
cc9e8c8b BM |
1 | import java.util.Map; |
2 | import java.util.TreeMap; | |
a41f334c | 3 | color BLACK = #000000; |
e28f168c AG |
4 | |
5 | class Gimbal extends SCPattern { | |
6 | ||
58888c69 JR |
7 | private final PerfTimer perf = new PerfTimer(); |
8 | ||
e28f168c AG |
9 | private final boolean DEBUG_MANUAL_ABG = false; |
10 | private final int MAXIMUM_BEATS_PER_REVOLUTION = 100; | |
11 | ||
12 | private boolean first_run = true; | |
9fa29818 | 13 | private final LXProjection projection; |
58888c69 | 14 | private final BasicParameter beatsPerRevolutionParam = new BasicParameter("SLOW", 25./MAXIMUM_BEATS_PER_REVOLUTION); |
e28f168c AG |
15 | private final BasicParameter hueDeltaParam = new BasicParameter("HUED", 60./360); |
16 | private final BasicParameter fadeFromCoreParam = new BasicParameter("FADE", 1); | |
17 | private final BasicParameter girthParam = new BasicParameter("GRTH", .18); | |
18 | private final BasicParameter ringExtendParam = new BasicParameter("XTND", 1); | |
19 | private final BasicParameter relativeSpeedParam = new BasicParameter("RLSP", .83); | |
20 | private final BasicParameter sizeParam = new BasicParameter("SIZE", .9); | |
21 | ||
22 | private final BasicParameter aP = new BasicParameter("a", 0); | |
23 | private final BasicParameter bP = new BasicParameter("b", 0); | |
24 | private final BasicParameter gP = new BasicParameter("g", 0); | |
25 | ||
dde75983 MS |
26 | Gimbal(LX lx) { |
27 | super(lx); | |
9fa29818 | 28 | projection = new LXProjection(model); |
e28f168c AG |
29 | addParameter(beatsPerRevolutionParam); |
30 | addParameter(hueDeltaParam); | |
31 | addParameter(fadeFromCoreParam); | |
32 | addParameter(girthParam); | |
33 | addParameter(ringExtendParam); | |
34 | addParameter(relativeSpeedParam); | |
35 | addParameter(sizeParam); | |
36 | ||
37 | if (DEBUG_MANUAL_ABG) { | |
38 | addParameter(aP); | |
39 | addParameter(bP); | |
40 | addParameter(gP); | |
41 | } | |
42 | } | |
43 | ||
44 | float a = 0, b = 0, g = 0; | |
45 | ||
34327c96 | 46 | public void run(double deltaMs) { |
e28f168c AG |
47 | |
48 | if (DEBUG_MANUAL_ABG) { | |
49 | a = aP.getValuef() * (2 * PI); | |
50 | b = bP.getValuef() * (2 * PI); | |
51 | g = gP.getValuef() * (2 * PI); | |
52 | } else { | |
53 | float relativeSpeed = relativeSpeedParam.getValuef(); | |
54 | float time = millis() / 1000.f; | |
55 | ||
56 | int beatsPerRevolution = (int) (beatsPerRevolutionParam.getValuef() * MAXIMUM_BEATS_PER_REVOLUTION) + 1; | |
57 | float radiansPerMs = 2 * PI // radians / revolution | |
58 | / beatsPerRevolution // beats / revolution | |
59 | * lx.tempo.bpmf() // BPM beats / min | |
60 | / 60 // sec / min | |
61 | / 1000; // ms / sec | |
62 | ||
63 | a += deltaMs * radiansPerMs * pow(relativeSpeed, 0); | |
64 | b += deltaMs * radiansPerMs * pow(relativeSpeed, 1); | |
65 | g += deltaMs * radiansPerMs * pow(relativeSpeed, 2); | |
66 | a %= 2 * PI; | |
67 | b %= 2 * PI; | |
68 | g %= 2 * PI; | |
69 | } | |
70 | ||
71 | float hue = lx.getBaseHuef(); | |
72 | float hue_delta = hueDeltaParam.getValuef() * 360; | |
73 | ||
74 | float radius1 = model.xMax / 2 * sizeParam.getValuef(); | |
75 | float radius2 = ((model.xMax + model.yMax) / 2) / 2 * sizeParam.getValuef(); | |
76 | float radius3 = model.yMax / 2 * sizeParam.getValuef(); | |
77 | float girth = model.xMax * girthParam.getValuef(); | |
78 | Ring ring1 = new Ring((hue + hue_delta * 0) % 360, radius1, girth); | |
79 | Ring ring2 = new Ring((hue + hue_delta * 1) % 360, radius2, girth); | |
80 | Ring ring3 = new Ring((hue + hue_delta * 2) % 360, radius3, girth); | |
81 | ||
2bb56822 | 82 | projection.reset() |
e28f168c | 83 | // Translate so the center of the car is the origin |
2bb56822 | 84 | .center(); |
e28f168c | 85 | |
9fa29818 | 86 | for (LXVector c : projection) { |
e28f168c AG |
87 | //if (first_run) println(c.x + "," + c.y + "," + c.z); |
88 | ||
58888c69 JR |
89 | rotate3dA(c, a); |
90 | rotate3dPiOver4(c); | |
e28f168c AG |
91 | color color1 = ring1.colorFor(c); |
92 | ||
58888c69 | 93 | rotate3dB(c, b); |
e28f168c AG |
94 | color color2 = ring2.colorFor(c); |
95 | ||
58888c69 | 96 | rotate3dG(c, g); |
e28f168c AG |
97 | color color3 = ring3.colorFor(c); |
98 | ||
99 | colors[c.index] = specialBlend(color1, color2, color3); | |
100 | } | |
101 | ||
58888c69 | 102 | first_run = false; |
e28f168c AG |
103 | } |
104 | ||
105 | class Ring { | |
106 | ||
107 | float hue; | |
108 | float radius, girth; | |
58888c69 | 109 | float _multiplier; |
e28f168c AG |
110 | |
111 | public Ring(float hue, float radius, float girth) { | |
112 | this.hue = hue; | |
113 | this.radius = radius; | |
114 | this.girth = girth; | |
58888c69 | 115 | this._multiplier = 100. / girth * fadeFromCoreParam.getValuef(); |
e28f168c AG |
116 | } |
117 | ||
9fa29818 | 118 | public color colorFor(LXVector c) { |
58888c69 JR |
119 | float xy_distance_to_circle = sqrt(c.x * c.x + c.y * c.y) - radius; |
120 | float z_distance_to_circle = c.z * ringExtendParam.getValuef(); | |
121 | ||
122 | float distance_to_circle | |
123 | = sqrt(xy_distance_to_circle * xy_distance_to_circle | |
124 | + z_distance_to_circle * z_distance_to_circle); | |
125 | ||
126 | return lx.hsb(this.hue, 100, 100. - distance_to_circle * _multiplier); | |
127 | ||
128 | /* PRE-OPTIMIZED IMPLEMENTATION | |
e28f168c AG |
129 | float theta = atan2(c.y, c.x); |
130 | float nearest_circle_x = cos(theta) * radius; | |
131 | float nearest_circle_y = sin(theta) * radius; | |
132 | float nearest_circle_z = 0; | |
133 | ||
134 | float distance_to_circle | |
135 | = sqrt(pow(nearest_circle_x - c.x, 2) | |
136 | + pow(nearest_circle_y - c.y, 2) | |
137 | + pow(nearest_circle_z - c.z * ringExtendParam.getValuef(), 2)); | |
138 | ||
139 | float xy_distance = sqrt(c.x*c.x + c.y*c.y); | |
a41f334c | 140 | return lx.hsb(this.hue, 100, (1 - distance_to_circle / girth * fadeFromCoreParam.getValuef()) * 100); |
58888c69 | 141 | */ |
e28f168c | 142 | } |
58888c69 | 143 | |
e28f168c AG |
144 | } |
145 | ||
146 | } | |
147 | ||
148 | ||
58888c69 JR |
149 | class PerfTimer { |
150 | ||
151 | private final Map<String, Integer> m = new TreeMap<String, Integer>(); | |
152 | private String current_phase = null; | |
153 | private int current_phase_start_time = 0; | |
154 | private int total_time = 0; | |
155 | ||
156 | public void start(String phase_name) { | |
157 | if (current_phase != null) { | |
158 | stop(); | |
159 | } | |
160 | current_phase = phase_name; | |
161 | current_phase_start_time = millis(); | |
162 | } | |
163 | ||
164 | public void stop() { | |
165 | int current_time = millis(); | |
166 | ||
167 | assert(current_phase != null); | |
168 | assert(current_phase_start_time != 0); | |
169 | int time_ellapsed = current_time - current_phase_start_time; | |
170 | m.put(current_phase, | |
171 | (m.containsKey(current_phase) ? m.get(current_phase) : 0) | |
172 | + time_ellapsed); | |
173 | ||
174 | current_phase = null; | |
175 | current_phase_start_time = 0; | |
176 | total_time += time_ellapsed; | |
177 | } | |
178 | ||
179 | public void report() { | |
180 | if (random(0, 60 * 4) < 1) { | |
181 | println("~~~~~~~~~~~~~~~~~~"); | |
182 | for (String phase_name : m.keySet()) { | |
183 | print(phase_name); | |
184 | for (int i = phase_name.length(); i < 30; ++i) { | |
185 | print(" "); | |
186 | } | |
187 | println("" + (float) m.get(phase_name) / total_time); | |
188 | } | |
189 | } | |
190 | } | |
191 | ||
192 | } | |
193 | ||
194 | ||
e28f168c AG |
195 | |
196 | ||
197 | ||
198 | ||
199 | class Zebra extends SCPattern { | |
200 | ||
9fa29818 | 201 | private final LXProjection projection; |
e28f168c AG |
202 | SinLFO angleM = new SinLFO(0, PI * 2, 30000); |
203 | ||
204 | /* | |
205 | SinLFO x, y, z, dx, dy, dz; | |
206 | float cRad; | |
207 | _P size; | |
208 | */ | |
209 | ||
dde75983 MS |
210 | Zebra(LX lx) { |
211 | super(lx); | |
9fa29818 | 212 | projection = new LXProjection(model); |
e28f168c AG |
213 | |
214 | addModulator(angleM).trigger(); | |
215 | } | |
216 | ||
9fa29818 | 217 | color colorFor(LXVector c) { |
e28f168c AG |
218 | float hue = lx.getBaseHuef(); |
219 | ||
220 | ||
221 | ||
222 | ||
223 | /* SLIDE ALONG | |
224 | c.x = c.x + millis() / 100.f; | |
225 | */ | |
226 | ||
227 | ||
228 | ||
229 | int stripe_count = 12; | |
230 | float stripe_width = model.xMax / (float)stripe_count; | |
231 | if (Math.floor((c.x) / stripe_width) % 2 == 0) { | |
a41f334c | 232 | return lx.hsb(hue, 100, 100); |
e28f168c | 233 | } else { |
a41f334c | 234 | return lx.hsb((hue + 90) % 360, 100, 100); |
e28f168c AG |
235 | } |
236 | ||
237 | ||
238 | /* OCTANTS | |
239 | ||
240 | if ((isPositiveBit(c.x) + isPositiveBit(c.y) + isPositiveBit(c.z)) % 2 == 0) { | |
a41f334c | 241 | return lx.hsb(lx.getBaseHuef(), 100, 100); |
e28f168c | 242 | } else { |
a41f334c | 243 | return lx.hsb(0, 0, 0); |
e28f168c AG |
244 | } |
245 | */ | |
246 | } | |
247 | ||
248 | int isPositiveBit(float f) { | |
249 | return f > 0 ? 1 : 0; | |
250 | } | |
251 | ||
34327c96 | 252 | public void run(double deltaMs) { |
e28f168c AG |
253 | float a = (millis() / 1000.f) % (2 * PI); |
254 | float b = (millis() / 1200.f) % (2 * PI); | |
255 | float g = (millis() / 1600.f) % (2 * PI); | |
256 | ||
2bb56822 | 257 | projection.reset() |
e28f168c | 258 | // Translate so the center of the car is the origin |
2bb56822 | 259 | .center(); |
e28f168c | 260 | |
9fa29818 | 261 | for (LXVector c : projection) { |
e28f168c AG |
262 | // rotate3d(c, a, b, g); |
263 | colors[c.index] = colorFor(c); | |
264 | } | |
265 | ||
266 | first_run = false; | |
267 | } | |
268 | ||
269 | ||
270 | // Utility! | |
271 | boolean first_run = true; | |
272 | private void log(String s) { | |
273 | if (first_run) { | |
274 | println(s); | |
275 | } | |
276 | } | |
277 | ||
278 | ||
279 | } | |
280 | ||
58888c69 JR |
281 | float HALF_OF_ONE_OVER_SQRT2_MINUS_1 = (1./sqrt(2) - 1) / 2; |
282 | float HALF_OF_ONE_OVER_SQRT2_PLUS_1 = (1./sqrt(2) + 1) / 2; | |
283 | float SQRT2 = sqrt(2); | |
284 | ||
285 | /** Equivalent to rotate3d(c, a, 0, 0); */ | |
286 | void rotate3dA(LXVector c, float a) { | |
287 | float ox = c.x, oy = c.y; | |
288 | float cosa = cos(a); | |
289 | float sina = sin(a); | |
290 | c.x = ox * cosa - oy * sina; | |
291 | c.y = ox * sina + oy * cosa; | |
292 | } | |
293 | /** Equivalent to rotate3d(c, 0, b, 0); */ | |
294 | void rotate3dB(LXVector c, float b) { | |
295 | float ox = c.x, oz = c.z; | |
296 | float cosb = cos(b); | |
297 | float sinb = sin(b); | |
298 | c.x = ox * cosb + oz * sinb; | |
299 | c.z = oz * cosb - ox * sinb; | |
300 | } | |
301 | /** Equivalent to rotate3d(c, 0, 0, g); */ | |
302 | void rotate3dG(LXVector c, float g) { | |
303 | float oy = c.y, oz = c.z; | |
304 | float cosg = cos(g); | |
305 | float sing = sin(g); | |
306 | c.y = oy * cosg - oz * sing; | |
307 | c.z = oz * cosg + oy * sing; | |
308 | } | |
309 | /** Equivalent to rotate3d(c, PI/4, PI/4, PI/4); */ | |
310 | void rotate3dPiOver4(LXVector c) { | |
311 | float ox = c.x, oy = c.y, oz = c.z; | |
312 | c.x = ox / 2 + oy * HALF_OF_ONE_OVER_SQRT2_MINUS_1 + oz * HALF_OF_ONE_OVER_SQRT2_PLUS_1; | |
313 | c.y = ox / 2 + oy * HALF_OF_ONE_OVER_SQRT2_PLUS_1 + oz * HALF_OF_ONE_OVER_SQRT2_MINUS_1; | |
314 | c.z = - ox / SQRT2 + oy / 2 + oz / 2; | |
315 | } | |
316 | ||
9fa29818 | 317 | void rotate3d(LXVector c, float a /* roll */, float b /* pitch */, float g /* yaw */) { |
58888c69 JR |
318 | float ox = c.x, oy = c.y, oz = c.z; |
319 | ||
e28f168c AG |
320 | float cosa = cos(a); |
321 | float cosb = cos(b); | |
322 | float cosg = cos(g); | |
323 | float sina = sin(a); | |
324 | float sinb = sin(b); | |
325 | float sing = sin(g); | |
326 | ||
327 | float a1 = cosa*cosb; | |
328 | float a2 = cosa*sinb*sing - sina*cosg; | |
329 | float a3 = cosa*sinb*cosg + sina*sing; | |
330 | float b1 = sina*cosb; | |
331 | float b2 = sina*sinb*sing + cosa*cosg; | |
332 | float b3 = sina*sinb*cosg - cosa*sing; | |
333 | float c1 = -sinb; | |
334 | float c2 = cosb*sing; | |
335 | float c3 = cosb*cosg; | |
58888c69 JR |
336 | |
337 | c.x = ox * a1 + oy * a2 + oz * a3; | |
338 | c.y = ox * b1 + oy * b2 + oz * b3; | |
339 | c.z = ox * c1 + oy * c2 + oz * c3; | |
e28f168c AG |
340 | } |
341 | ||
342 | float dotProduct(float[] a, float[] b) { | |
343 | float ret = 0; | |
344 | for (int i = 0 ; i < a.length; ++i) { | |
345 | ret += a[i] * b[i]; | |
346 | } | |
347 | return ret; | |
348 | } | |
349 | ||
350 | color specialBlend(color c1, color c2, color c3) { | |
351 | float h1 = hue(c1); | |
352 | float h2 = hue(c2); | |
353 | float h3 = hue(c3); | |
354 | ||
355 | // force h1 < h2 < h3 | |
356 | while (h2 < h1) { | |
357 | h2 += 360; | |
358 | } | |
359 | while (h3 < h2) { | |
360 | h3 += 360; | |
361 | } | |
362 | ||
363 | float s1 = saturation(c1); | |
364 | float s2 = saturation(c2); | |
365 | float s3 = saturation(c3); | |
366 | ||
367 | float b1 = brightness(c1); | |
368 | float b2 = brightness(c2); | |
369 | float b3 = brightness(c3); | |
58888c69 JR |
370 | float b_denominator = b1 + b2 + b3; |
371 | float relative_b1 = b1 / b_denominator; | |
372 | float relative_b2 = b2 / b_denominator; | |
373 | float relative_b3 = b3 / b_denominator; | |
e28f168c | 374 | |
a41f334c | 375 | return lx.hsb( |
e28f168c AG |
376 | (h1 * relative_b1 + h2 * relative_b1 + h3 * relative_b3) % 360, |
377 | s1 * relative_b1 + s2 * relative_b2 + s3 * relative_b3, | |
378 | max(max(b1, b2), b3) | |
379 | ); | |
380 | } | |
381 |