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