| 1 | import java.util.Map; |
| 2 | import java.util.TreeMap; |
| 3 | color BLACK = #000000; |
| 4 | |
| 5 | class Gimbal extends SCPattern { |
| 6 | |
| 7 | private final PerfTimer perf = new PerfTimer(); |
| 8 | |
| 9 | private final boolean DEBUG_MANUAL_ABG = false; |
| 10 | private final int MAXIMUM_BEATS_PER_REVOLUTION = 100; |
| 11 | |
| 12 | private boolean first_run = true; |
| 13 | private final LXProjection projection; |
| 14 | private final BasicParameter beatsPerRevolutionParam = new BasicParameter("SLOW", 25./MAXIMUM_BEATS_PER_REVOLUTION); |
| 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 | |
| 26 | Gimbal(LX lx) { |
| 27 | super(lx); |
| 28 | projection = new LXProjection(model); |
| 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 | |
| 46 | public void run(double deltaMs) { |
| 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 | |
| 82 | projection.reset() |
| 83 | // Translate so the center of the car is the origin |
| 84 | .center(); |
| 85 | |
| 86 | for (LXVector c : projection) { |
| 87 | //if (first_run) println(c.x + "," + c.y + "," + c.z); |
| 88 | |
| 89 | rotate3dA(c, a); |
| 90 | rotate3dPiOver4(c); |
| 91 | color color1 = ring1.colorFor(c); |
| 92 | |
| 93 | rotate3dB(c, b); |
| 94 | color color2 = ring2.colorFor(c); |
| 95 | |
| 96 | rotate3dG(c, g); |
| 97 | color color3 = ring3.colorFor(c); |
| 98 | |
| 99 | colors[c.index] = specialBlend(color1, color2, color3); |
| 100 | } |
| 101 | |
| 102 | first_run = false; |
| 103 | } |
| 104 | |
| 105 | class Ring { |
| 106 | |
| 107 | float hue; |
| 108 | float radius, girth; |
| 109 | float _multiplier; |
| 110 | |
| 111 | public Ring(float hue, float radius, float girth) { |
| 112 | this.hue = hue; |
| 113 | this.radius = radius; |
| 114 | this.girth = girth; |
| 115 | this._multiplier = 100. / girth * fadeFromCoreParam.getValuef(); |
| 116 | } |
| 117 | |
| 118 | public color colorFor(LXVector c) { |
| 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 |
| 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); |
| 140 | return lx.hsb(this.hue, 100, (1 - distance_to_circle / girth * fadeFromCoreParam.getValuef()) * 100); |
| 141 | */ |
| 142 | } |
| 143 | |
| 144 | } |
| 145 | |
| 146 | } |
| 147 | |
| 148 | |
| 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 | |
| 195 | |
| 196 | |
| 197 | |
| 198 | |
| 199 | class Zebra extends SCPattern { |
| 200 | |
| 201 | private final LXProjection projection; |
| 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 | |
| 210 | Zebra(LX lx) { |
| 211 | super(lx); |
| 212 | projection = new LXProjection(model); |
| 213 | |
| 214 | addModulator(angleM).trigger(); |
| 215 | } |
| 216 | |
| 217 | color colorFor(LXVector c) { |
| 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) { |
| 232 | return lx.hsb(hue, 100, 100); |
| 233 | } else { |
| 234 | return lx.hsb((hue + 90) % 360, 100, 100); |
| 235 | } |
| 236 | |
| 237 | |
| 238 | /* OCTANTS |
| 239 | |
| 240 | if ((isPositiveBit(c.x) + isPositiveBit(c.y) + isPositiveBit(c.z)) % 2 == 0) { |
| 241 | return lx.hsb(lx.getBaseHuef(), 100, 100); |
| 242 | } else { |
| 243 | return lx.hsb(0, 0, 0); |
| 244 | } |
| 245 | */ |
| 246 | } |
| 247 | |
| 248 | int isPositiveBit(float f) { |
| 249 | return f > 0 ? 1 : 0; |
| 250 | } |
| 251 | |
| 252 | public void run(double deltaMs) { |
| 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 | |
| 257 | projection.reset() |
| 258 | // Translate so the center of the car is the origin |
| 259 | .center(); |
| 260 | |
| 261 | for (LXVector c : projection) { |
| 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 | |
| 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 | |
| 317 | void rotate3d(LXVector c, float a /* roll */, float b /* pitch */, float g /* yaw */) { |
| 318 | float ox = c.x, oy = c.y, oz = c.z; |
| 319 | |
| 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; |
| 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; |
| 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); |
| 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; |
| 374 | |
| 375 | return lx.hsb( |
| 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 | |