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