sinesphere
[SugarCubes.git] / _Internals.pde
CommitLineData
49815cc0 1/**
1ecdb44a
MS
2 * DOUBLE BLACK DIAMOND DOUBLE BLACK DIAMOND
3 *
4 * //\\ //\\ //\\ //\\
5 * ///\\\ ///\\\ ///\\\ ///\\\
6 * \\\/// \\\/// \\\/// \\\///
7 * \\// \\// \\// \\//
8 *
9 * EXPERTS ONLY!! EXPERTS ONLY!!
10 *
49815cc0
MS
11 * If you are an artist, you may ignore this file! It just sets
12 * up the framework to run the patterns. Should not need modification
13 * for general animation work.
14 */
15
16import glucose.*;
17import glucose.control.*;
3f8be614 18import glucose.effect.*;
f3f5a876 19import glucose.model.*;
49815cc0 20import glucose.pattern.*;
f3f5a876 21import glucose.transform.*;
49815cc0 22import glucose.transition.*;
49815cc0 23import heronarts.lx.*;
3f8be614 24import heronarts.lx.control.*;
49815cc0 25import heronarts.lx.effect.*;
49815cc0 26import heronarts.lx.modulator.*;
f3f5a876 27import heronarts.lx.pattern.*;
49815cc0
MS
28import heronarts.lx.transition.*;
29import ddf.minim.*;
30import ddf.minim.analysis.*;
31import processing.opengl.*;
5d70e4d7 32import rwmidi.*;
49815cc0
MS
33
34final int VIEWPORT_WIDTH = 900;
35final int VIEWPORT_HEIGHT = 700;
0e3c5542 36
92c06c97 37// The trailer is measured from the outside of the black metal (but not including the higher welded part on the front)
87998ff3
MS
38final float TRAILER_WIDTH = 240;
39final float TRAILER_DEPTH = 97;
40final float TRAILER_HEIGHT = 33;
41
51d0d59a 42int targetFramerate = 60;
49815cc0 43int startMillis, lastMillis;
a898d79b
MS
44
45// Core engine variables
49815cc0
MS
46GLucose glucose;
47HeronLX lx;
48LXPattern[] patterns;
a898d79b 49MappingTool mappingTool;
79ae8245 50PandaDriver[] pandaBoards;
d6ac1ee8 51MidiEngine midiEngine;
a898d79b
MS
52
53// Display configuration mode
bf551144 54boolean mappingMode = false;
cc9fcf4b 55boolean debugMode = false;
554e38ff 56DebugUI debugUI;
34327c96
MS
57boolean uiOn = true;
58LXPattern restoreToPattern = null;
4c640acc 59PImage logo;
d626bc9b 60
a898d79b 61// Handles to UI objects
d626bc9b
MS
62UIContext[] overlays;
63UIPatternDeck uiPatternA;
a898d79b 64UICrossfader uiCrossfader;
a8d55ade 65UIMidi uiMidi;
d626bc9b
MS
66UIMapping uiMapping;
67UIDebugText uiDebugText;
cc9fcf4b 68
0a9f99cc 69// Camera variables
bda5421d 70float eyeR, eyeA, eyeX, eyeY, eyeZ, midX, midY, midZ;
0a9f99cc 71
34327c96
MS
72/**
73 * Engine construction and initialization.
74 */
d626bc9b
MS
75LXPattern[] _patterns(GLucose glucose) {
76 LXPattern[] patterns = patterns(glucose);
77 for (LXPattern p : patterns) {
78 p.setTransition(new DissolveTransition(glucose.lx).setDuration(1000));
79 }
80 return patterns;
81}
82
34327c96
MS
83void logTime(String evt) {
84 int now = millis();
85 println(evt + ": " + (now - lastMillis) + "ms");
86 lastMillis = now;
87}
88
49815cc0
MS
89void setup() {
90 startMillis = lastMillis = millis();
91
92 // Initialize the Processing graphics environment
93 size(VIEWPORT_WIDTH, VIEWPORT_HEIGHT, OPENGL);
0e3c5542 94 frameRate(targetFramerate);
3f8be614 95 noSmooth();
49815cc0
MS
96 // hint(ENABLE_OPENGL_4X_SMOOTH); // no discernable improvement?
97 logTime("Created viewport");
98
99 // Create the GLucose engine to run the cubes
186bc4d3 100 glucose = new GLucose(this, buildModel());
49815cc0 101 lx = glucose.lx;
cc9fcf4b 102 lx.enableKeyboardTempo();
49815cc0
MS
103 logTime("Built GLucose engine");
104
2e45d1c9
MS
105 // MIDI devices
106 midiEngine = new MidiEngine();
107 logTime("Setup MIDI devices");
108
49815cc0 109 // Set the patterns
d626bc9b 110 Engine engine = lx.engine;
d626bc9b
MS
111 engine.setPatterns(patterns = _patterns(glucose));
112 engine.addDeck(_patterns(glucose));
49815cc0 113 logTime("Built patterns");
a898d79b
MS
114 glucose.setTransitions(transitions(glucose));
115 logTime("Built transitions");
116 glucose.lx.addEffects(effects(glucose));
49815cc0 117 logTime("Built effects");
e73ef85d
MS
118
119 // Build output driver
186bc4d3 120 PandaMapping[] pandaMappings = buildPandaList();
45f43cc2
MS
121 pandaBoards = new PandaDriver[pandaMappings.length];
122 int pbi = 0;
123 for (PandaMapping pm : pandaMappings) {
44b8de9c 124 pandaBoards[pbi++] = new PandaDriver(pm.ip, glucose.model, pm);
45f43cc2
MS
125 }
126 mappingTool = new MappingTool(glucose, pandaMappings);
127 logTime("Built PandaDriver");
a8d55ade 128
49815cc0 129 // Build overlay UI
45f43cc2 130 debugUI = new DebugUI(pandaMappings);
d626bc9b 131 overlays = new UIContext[] {
a8d55ade
MS
132 uiPatternA = new UIPatternDeck(lx.engine.getDeck(0), "PATTERN A", 4, 4, 140, 324),
133 new UIBlendMode(4, 332, 140, 86),
134 new UIEffects(4, 422, 140, 144),
135 new UITempo(4, 570, 140, 50),
136 new UISpeed(4, 624, 140, 50),
137
138 new UIPatternDeck(lx.engine.getDeck(1), "PATTERN B", width-144, 4, 140, 324),
d6ac1ee8 139 uiMidi = new UIMidi(midiEngine, width-144, 332, 140, 158),
4df91daf 140 new UIOutput(width-144, 494, 140, 106),
d626bc9b 141
a8d55ade 142 uiCrossfader = new UICrossfader(width/2-90, height-90, 180, 86),
d626bc9b 143
a8d55ade
MS
144 uiDebugText = new UIDebugText(148, height-138, width-304, 44),
145 uiMapping = new UIMapping(mappingTool, 4, 4, 140, 324),
d626bc9b
MS
146 };
147 uiMapping.setVisible(false);
49815cc0 148 logTime("Built overlay UI");
4c640acc
MS
149
150 // Load logo image
151 logo = loadImage("data/logo.png");
152
0a9f99cc 153 // Setup camera
d626bc9b 154 midX = TRAILER_WIDTH/2.;
0a9f99cc 155 midY = glucose.model.yMax/2;
e0cea600
MS
156 midZ = TRAILER_DEPTH/2.;
157 eyeR = -290;
0a9f99cc 158 eyeA = .15;
d626bc9b 159 eyeY = midY + 70;
0a9f99cc
MS
160 eyeX = midX + eyeR*sin(eyeA);
161 eyeZ = midZ + eyeR*cos(eyeA);
d6ac1ee8
MS
162
163 // Add mouse scrolling event support
bda5421d
MS
164 addMouseWheelListener(new java.awt.event.MouseWheelListener() {
165 public void mouseWheelMoved(java.awt.event.MouseWheelEvent mwe) {
166 mouseWheel(mwe.getWheelRotation());
167 }});
168
49815cc0 169 println("Total setup: " + (millis() - startMillis) + "ms");
e73ef85d 170 println("Hit the 'p' key to toggle Panda Board output");
49815cc0
MS
171}
172
34327c96
MS
173/**
174 * Core render loop and drawing functionality.
175 */
49815cc0 176void draw() {
0a9f99cc
MS
177 // Draws the simulation and the 2D UI overlay
178 background(40);
a898d79b
MS
179 color[] colors = glucose.getColors();
180
181 String displayMode = uiCrossfader.getDisplayMode();
d626bc9b
MS
182 if (displayMode == "A") {
183 colors = lx.engine.getDeck(0).getColors();
184 } else if (displayMode == "B") {
185 colors = lx.engine.getDeck(1).getColors();
186 }
554e38ff
MS
187 if (debugMode) {
188 debugUI.maskColors(colors);
189 }
51d0d59a 190
0a9f99cc
MS
191 camera(
192 eyeX, eyeY, eyeZ,
193 midX, midY, midZ,
194 0, -1, 0
195 );
51d0d59a 196
a8d55ade 197 translate(0, 40, 0);
d626bc9b 198
51d0d59a
MS
199 noStroke();
200 fill(#141414);
87998ff3 201 drawBox(0, -TRAILER_HEIGHT, 0, 0, 0, 0, TRAILER_WIDTH, TRAILER_HEIGHT, TRAILER_DEPTH, TRAILER_HEIGHT/2.);
51d0d59a
MS
202 fill(#070707);
203 stroke(#222222);
0a9f99cc 204 beginShape();
51d0d59a 205 vertex(0, 0, 0);
87998ff3
MS
206 vertex(TRAILER_WIDTH, 0, 0);
207 vertex(TRAILER_WIDTH, 0, TRAILER_DEPTH);
208 vertex(0, 0, TRAILER_DEPTH);
51d0d59a 209 endShape();
4c640acc
MS
210
211 // Draw the logo on the front of platform
212 pushMatrix();
213 translate(0, 0, -1);
214 float s = .07;
215 scale(s, -s, s);
216 image(logo, TRAILER_WIDTH/2/s-logo.width/2, TRAILER_HEIGHT/2/s-logo.height/2-2/s);
217 popMatrix();
0a9f99cc 218
51d0d59a 219 noStroke();
ab77005f
MS
220// drawBassBox(glucose.model.bassBox);
221// for (Speaker s : glucose.model.speakers) {
222// drawSpeaker(s);
223// }
51d0d59a
MS
224 for (Cube c : glucose.model.cubes) {
225 drawCube(c);
226 }
227
0a9f99cc
MS
228 noFill();
229 strokeWeight(2);
230 beginShape(POINTS);
922def98
MS
231 // TODO(mcslee): restore when bassBox/speakers are right again
232 // for (Point p : glucose.model.points) {
233 for (Cube cube : glucose.model.cubes) {
234 for (Point p : cube.points) {
235 stroke(colors[p.index]);
236 vertex(p.fx, p.fy, p.fz);
237 }
0a9f99cc
MS
238 }
239 endShape();
240
d626bc9b 241 // 2D Overlay UI
0a9f99cc 242 drawUI();
d626bc9b
MS
243
244 // Send output colors
245 color[] sendColors = glucose.getColors();
554e38ff 246 if (debugMode) {
d626bc9b 247 debugUI.maskColors(colors);
554e38ff 248 }
a797d019 249
6702151a
MS
250 // Gamma correction here. Apply a cubic to the brightness
251 // for better representation of dynamic range
252 for (int i = 0; i < colors.length; ++i) {
253 float b = brightness(colors[i]) / 100.f;
254 colors[i] = color(
255 hue(colors[i]),
256 saturation(colors[i]),
257 (b*b*b) * 100.
258 );
259 }
e7f14d4d 260
e73ef85d 261 // TODO(mcslee): move into GLucose engine
79ae8245 262 for (PandaDriver p : pandaBoards) {
b58e5a1d 263 p.send(colors);
e73ef85d 264 }
49815cc0
MS
265}
266
92c06c97
MS
267void drawBassBox(BassBox b) {
268 float in = .15;
e76480d4
MS
269
270 noStroke();
271 fill(#191919);
272 pushMatrix();
273 translate(b.x + BassBox.EDGE_WIDTH/2., b.y + BassBox.EDGE_HEIGHT/2, b.z + BassBox.EDGE_DEPTH/2.);
274 box(BassBox.EDGE_WIDTH-20*in, BassBox.EDGE_HEIGHT-20*in, BassBox.EDGE_DEPTH-20*in);
275 popMatrix();
276
277 noStroke();
278 fill(#393939);
92c06c97
MS
279 drawBox(b.x+in, b.y+in, b.z+in, 0, 0, 0, BassBox.EDGE_WIDTH-in*2, BassBox.EDGE_HEIGHT-in*2, BassBox.EDGE_DEPTH-in*2, Cube.CHANNEL_WIDTH-in);
280
39011e7e
MS
281 pushMatrix();
282 translate(b.x+(Cube.CHANNEL_WIDTH-in)/2., b.y + BassBox.EDGE_HEIGHT-in, b.z + BassBox.EDGE_DEPTH/2.);
283 float lastOffset = 0;
284 for (float offset : BoothFloor.STRIP_OFFSETS) {
285 translate(offset - lastOffset, 0, 0);
286 box(Cube.CHANNEL_WIDTH-in, 0, BassBox.EDGE_DEPTH - 2*in);
287 lastOffset = offset;
288 }
289 popMatrix();
290
92c06c97
MS
291 pushMatrix();
292 translate(b.x + (Cube.CHANNEL_WIDTH-in)/2., b.y + BassBox.EDGE_HEIGHT/2., b.z + in);
293 for (int j = 0; j < 2; ++j) {
294 pushMatrix();
295 for (int i = 0; i < BassBox.NUM_FRONT_STRUTS; ++i) {
296 translate(BassBox.FRONT_STRUT_SPACING, 0, 0);
297 box(Cube.CHANNEL_WIDTH-in, BassBox.EDGE_HEIGHT - in*2, 0);
298 }
299 popMatrix();
300 translate(0, 0, BassBox.EDGE_DEPTH - 2*in);
301 }
302 popMatrix();
303
304 pushMatrix();
305 translate(b.x + in, b.y + BassBox.EDGE_HEIGHT/2., b.z + BassBox.SIDE_STRUT_SPACING + (Cube.CHANNEL_WIDTH-in)/2.);
306 box(0, BassBox.EDGE_HEIGHT - in*2, Cube.CHANNEL_WIDTH-in);
307 translate(BassBox.EDGE_WIDTH-2*in, 0, 0);
308 box(0, BassBox.EDGE_HEIGHT - in*2, Cube.CHANNEL_WIDTH-in);
ab77005f 309 popMatrix();
e76480d4 310
92c06c97
MS
311}
312
51d0d59a 313void drawCube(Cube c) {
254d34c0 314 float in = .15;
e76480d4
MS
315 noStroke();
316 fill(#393939);
254d34c0 317 drawBox(c.x+in, c.y+in, c.z+in, c.rx, c.ry, c.rz, Cube.EDGE_WIDTH-in*2, Cube.EDGE_HEIGHT-in*2, Cube.EDGE_WIDTH-in*2, Cube.CHANNEL_WIDTH-in);
51d0d59a
MS
318}
319
e76480d4
MS
320void drawSpeaker(Speaker s) {
321 float in = .15;
322
323 noStroke();
324 fill(#191919);
325 pushMatrix();
326 translate(s.x, s.y, s.z);
327 rotate(s.ry / 180. * PI, 0, -1, 0);
328 translate(Speaker.EDGE_WIDTH/2., Speaker.EDGE_HEIGHT/2., Speaker.EDGE_DEPTH/2.);
329 box(Speaker.EDGE_WIDTH-20*in, Speaker.EDGE_HEIGHT-20*in, Speaker.EDGE_DEPTH-20*in);
254fbb68
MS
330 translate(0, Speaker.EDGE_HEIGHT/2. + Speaker.EDGE_HEIGHT*.8/2, 0);
331
332 fill(#222222);
333 box(Speaker.EDGE_WIDTH*.6, Speaker.EDGE_HEIGHT*.8, Speaker.EDGE_DEPTH*.75);
e76480d4
MS
334 popMatrix();
335
e76480d4
MS
336 noStroke();
337 fill(#393939);
338 drawBox(s.x+in, s.y+in, s.z+in, 0, s.ry, 0, Speaker.EDGE_WIDTH-in*2, Speaker.EDGE_HEIGHT-in*2, Speaker.EDGE_DEPTH-in*2, Cube.CHANNEL_WIDTH-in);
ab77005f 339}
e76480d4 340
51d0d59a
MS
341void drawBox(float x, float y, float z, float rx, float ry, float rz, float xd, float yd, float zd, float sw) {
342 pushMatrix();
343 translate(x, y, z);
e0cea600 344 rotate(rx / 180. * PI, -1, 0, 0);
51d0d59a 345 rotate(ry / 180. * PI, 0, -1, 0);
e0cea600 346 rotate(rz / 180. * PI, 0, 0, -1);
51d0d59a
MS
347 for (int i = 0; i < 4; ++i) {
348 float wid = (i % 2 == 0) ? xd : zd;
349
350 beginShape();
351 vertex(0, 0);
352 vertex(wid, 0);
353 vertex(wid, yd);
354 vertex(wid - sw, yd);
355 vertex(wid - sw, sw);
356 vertex(0, sw);
357 endShape();
358 beginShape();
359 vertex(0, sw);
360 vertex(0, yd);
361 vertex(wid - sw, yd);
362 vertex(wid - sw, yd - sw);
363 vertex(sw, yd - sw);
364 vertex(sw, sw);
365 endShape();
366
367 translate(wid, 0, 0);
368 rotate(HALF_PI, 0, -1, 0);
369 }
370 popMatrix();
371}
372
49815cc0 373void drawUI() {
d626bc9b
MS
374 camera();
375 javax.media.opengl.GL gl = ((PGraphicsOpenGL)g).beginGL();
376 gl.glClear(javax.media.opengl.GL.GL_DEPTH_BUFFER_BIT);
377 ((PGraphicsOpenGL)g).endGL();
378 strokeWeight(1);
379
49815cc0 380 if (uiOn) {
d626bc9b
MS
381 for (UIContext context : overlays) {
382 context.draw();
383 }
384 }
385
386 // Always draw FPS meter
387 fill(#555555);
388 textSize(9);
389 textAlign(LEFT, BASELINE);
390 text("FPS: " + ((int) (frameRate*10)) / 10. + " / " + targetFramerate + " (-/+)", 4, height-4);
391
392 if (debugMode) {
393 debugUI.draw();
49815cc0 394 }
49815cc0
MS
395}
396
4c640acc 397
34327c96
MS
398/**
399 * Top-level keyboard event handling
400 */
49815cc0 401void keyPressed() {
bf551144 402 if (mappingMode) {
d626bc9b 403 mappingTool.keyPressed(uiMapping);
bf551144 404 }
3f8be614 405 switch (key) {
0e3c5542
MS
406 case '-':
407 case '_':
408 frameRate(--targetFramerate);
409 break;
410 case '=':
411 case '+':
412 frameRate(++targetFramerate);
4c640acc 413 break;
cc9fcf4b 414 case 'd':
d6ac1ee8 415 if (!midiEngine.isQwertyEnabled()) {
a8d55ade
MS
416 debugMode = !debugMode;
417 println("Debug output: " + (debugMode ? "ON" : "OFF"));
418 }
554e38ff 419 break;
bf551144 420 case 'm':
d6ac1ee8 421 if (!midiEngine.isQwertyEnabled()) {
a8d55ade
MS
422 mappingMode = !mappingMode;
423 uiPatternA.setVisible(!mappingMode);
424 uiMapping.setVisible(mappingMode);
425 if (mappingMode) {
426 restoreToPattern = lx.getPattern();
427 lx.setPatterns(new LXPattern[] { mappingTool });
428 } else {
429 lx.setPatterns(patterns);
430 LXTransition pop = restoreToPattern.getTransition();
431 restoreToPattern.setTransition(null);
432 lx.goPattern(restoreToPattern);
433 restoreToPattern.setTransition(pop);
434 }
bf551144
MS
435 }
436 break;
e73ef85d 437 case 'p':
79ae8245
MS
438 for (PandaDriver p : pandaBoards) {
439 p.toggle();
440 }
cc9fcf4b 441 break;
3f8be614 442 case 'u':
d6ac1ee8 443 if (!midiEngine.isQwertyEnabled()) {
4df91daf
MS
444 uiOn = !uiOn;
445 }
3f8be614 446 break;
49815cc0
MS
447 }
448}
449
34327c96
MS
450/**
451 * Top-level mouse event handling
452 */
0a9f99cc 453int mx, my;
0a9f99cc 454void mousePressed() {
d626bc9b
MS
455 boolean debugged = false;
456 if (debugMode) {
457 debugged = debugUI.mousePressed();
458 }
459 if (!debugged) {
460 for (UIContext context : overlays) {
461 context.mousePressed(mouseX, mouseY);
462 }
0a9f99cc 463 }
d626bc9b
MS
464 mx = mouseX;
465 my = mouseY;
0a9f99cc
MS
466}
467
468void mouseDragged() {
d626bc9b
MS
469 boolean dragged = false;
470 for (UIContext context : overlays) {
471 dragged |= context.mouseDragged(mouseX, mouseY);
472 }
473 if (!dragged) {
0a9f99cc
MS
474 int dx = mouseX - mx;
475 int dy = mouseY - my;
476 mx = mouseX;
477 my = mouseY;
478 eyeA += dx*.003;
479 eyeX = midX + eyeR*sin(eyeA);
480 eyeZ = midZ + eyeR*cos(eyeA);
481 eyeY += dy;
482 }
483}
484
485void mouseReleased() {
d626bc9b
MS
486 for (UIContext context : overlays) {
487 context.mouseReleased(mouseX, mouseY);
488 }
0a9f99cc 489}
bda5421d
MS
490
491void mouseWheel(int delta) {
d626bc9b
MS
492 boolean wheeled = false;
493 for (UIContext context : overlays) {
494 wheeled |= context.mouseWheel(mouseX, mouseY, delta);
495 }
496
497 if (!wheeled) {
0ba6ac44
MS
498 eyeR = constrain(eyeR - delta, -500, -80);
499 eyeX = midX + eyeR*sin(eyeA);
500 eyeZ = midZ + eyeR*cos(eyeA);
501 }
bda5421d 502}