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