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