Add 't' button to toggle optional threading mode, TestPerformancePattern
[SugarCubes.git] / _Internals.pde
1 /**
2 * DOUBLE BLACK DIAMOND DOUBLE BLACK DIAMOND
3 *
4 * //\\ //\\ //\\ //\\
5 * ///\\\ ///\\\ ///\\\ ///\\\
6 * \\\/// \\\/// \\\/// \\\///
7 * \\// \\// \\// \\//
8 *
9 * EXPERTS ONLY!! EXPERTS ONLY!!
10 *
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
16 import glucose.*;
17 import glucose.control.*;
18 import glucose.effect.*;
19 import glucose.model.*;
20 import glucose.pattern.*;
21 import glucose.transform.*;
22 import glucose.transition.*;
23 import heronarts.lx.*;
24 import heronarts.lx.control.*;
25 import heronarts.lx.effect.*;
26 import heronarts.lx.modulator.*;
27 import heronarts.lx.pattern.*;
28 import heronarts.lx.transition.*;
29 import ddf.minim.*;
30 import ddf.minim.analysis.*;
31 import processing.opengl.*;
32 import rwmidi.*;
33
34 final int VIEWPORT_WIDTH = 900;
35 final int VIEWPORT_HEIGHT = 700;
36
37 // The trailer is measured from the outside of the black metal (but not including the higher welded part on the front)
38 final float TRAILER_WIDTH = 240;
39 final float TRAILER_DEPTH = 97;
40 final float TRAILER_HEIGHT = 33;
41
42 final int MaxCubeHeight = 7;
43 final int NumBackTowers = 9;
44
45 int targetFramerate = 60;
46 int startMillis, lastMillis;
47
48 // Core engine variables
49 GLucose glucose;
50 HeronLX lx;
51 LXPattern[] patterns;
52 MappingTool mappingTool;
53 PandaDriver[] pandaBoards;
54 MidiEngine midiEngine;
55 color[] threadColors;
56
57 // Display configuration mode
58 boolean mappingMode = false;
59 boolean debugMode = false;
60 DebugUI debugUI;
61 boolean uiOn = true;
62 LXPattern restoreToPattern = null;
63 PImage logo;
64
65 // Handles to UI objects
66 UIContext[] overlays;
67 UIPatternDeck uiPatternA;
68 UICrossfader uiCrossfader;
69 UIMidi uiMidi;
70 UIMapping uiMapping;
71 UIDebugText uiDebugText;
72
73 // Camera variables
74 float eyeR, eyeA, eyeX, eyeY, eyeZ, midX, midY, midZ;
75
76 /**
77 * Engine construction and initialization.
78 */
79
80 LXTransition _transition(GLucose glucose) {
81 return new DissolveTransition(glucose.lx).setDuration(1000);
82 }
83
84 LXPattern[] _leftPatterns(GLucose glucose) {
85 LXPattern[] patterns = patterns(glucose);
86 for (LXPattern p : patterns) {
87 p.setTransition(_transition(glucose));
88 }
89 return patterns;
90 }
91
92 LXPattern[] _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
104 void logTime(String evt) {
105 int now = millis();
106 println(evt + ": " + (now - lastMillis) + "ms");
107 lastMillis = now;
108 }
109
110 void setup() {
111 startMillis = lastMillis = millis();
112
113 // Initialize the Processing graphics environment
114 size(VIEWPORT_WIDTH, VIEWPORT_HEIGHT, OPENGL);
115 frameRate(targetFramerate);
116 noSmooth();
117 // hint(ENABLE_OPENGL_4X_SMOOTH); // no discernable improvement?
118 logTime("Created viewport");
119
120 // Create the GLucose engine to run the cubes
121 glucose = new GLucose(this, buildModel());
122 lx = glucose.lx;
123 lx.enableKeyboardTempo();
124 threadColors = new color[lx.total];
125 logTime("Built GLucose engine");
126
127 // Set the patterns
128 Engine engine = lx.engine;
129 engine.setPatterns(patterns = _leftPatterns(glucose));
130 engine.addDeck(_rightPatterns(glucose));
131 logTime("Built patterns");
132 glucose.setTransitions(transitions(glucose));
133 logTime("Built transitions");
134 glucose.lx.addEffects(effects(glucose));
135 logTime("Built effects");
136
137 // MIDI devices
138 midiEngine = new MidiEngine();
139 logTime("Setup MIDI devices");
140
141 // Build output driver
142 PandaMapping[] pandaMappings = buildPandaList();
143 pandaBoards = new PandaDriver[pandaMappings.length];
144 int pbi = 0;
145 for (PandaMapping pm : pandaMappings) {
146 pandaBoards[pbi++] = new PandaDriver(pm.ip, glucose.model, pm);
147 }
148 mappingTool = new MappingTool(glucose, pandaMappings);
149 logTime("Built PandaDriver");
150
151 // Build overlay UI
152 debugUI = new DebugUI(pandaMappings);
153 overlays = new UIContext[] {
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),
161 uiMidi = new UIMidi(midiEngine, width-144, 332, 140, 158),
162 new UIOutput(width-144, 494, 140, 106),
163
164 uiCrossfader = new UICrossfader(width/2-90, height-90, 180, 86),
165
166 uiDebugText = new UIDebugText(148, height-138, width-304, 44),
167 uiMapping = new UIMapping(mappingTool, 4, 4, 140, 324),
168 };
169 uiMapping.setVisible(false);
170 logTime("Built overlay UI");
171
172 // Load logo image
173 logo = loadImage("data/logo.png");
174
175 // Setup camera
176 midX = TRAILER_WIDTH/2.;
177 midY = glucose.model.yMax/2;
178 midZ = TRAILER_DEPTH/2.;
179 eyeR = -290;
180 eyeA = .15;
181 eyeY = midY + 70;
182 eyeX = midX + eyeR*sin(eyeA);
183 eyeZ = midZ + eyeR*cos(eyeA);
184
185 // Add mouse scrolling event support
186 addMouseWheelListener(new java.awt.event.MouseWheelListener() {
187 public void mouseWheelMoved(java.awt.event.MouseWheelEvent mwe) {
188 mouseWheel(mwe.getWheelRotation());
189 }});
190
191 println("Total setup: " + (millis() - startMillis) + "ms");
192 println("Hit the 'p' key to toggle Panda Board output");
193 }
194
195 /**
196 * Core render loop and drawing functionality.
197 */
198 void draw() {
199 // Draws the simulation and the 2D UI overlay
200 background(40);
201
202 color[] simulationColors;
203 color[] sendColors;
204 simulationColors = sendColors = glucose.getColors();
205 String displayMode = uiCrossfader.getDisplayMode();
206 if (displayMode == "A") {
207 simulationColors = lx.engine.getDeck(0).getColors();
208 } else if (displayMode == "B") {
209 simulationColors = lx.engine.getDeck(1).getColors();
210 }
211 if (debugMode) {
212 debugUI.maskColors(simulationColors);
213 debugUI.maskColors(sendColors);
214 }
215
216 camera(
217 eyeX, eyeY, eyeZ,
218 midX, midY, midZ,
219 0, -1, 0
220 );
221
222 translate(0, 40, 0);
223
224 noStroke();
225 fill(#141414);
226 drawBox(0, -TRAILER_HEIGHT, 0, 0, 0, 0, TRAILER_WIDTH, TRAILER_HEIGHT, TRAILER_DEPTH, TRAILER_HEIGHT/2.);
227 fill(#070707);
228 stroke(#222222);
229 beginShape();
230 vertex(0, 0, 0);
231 vertex(TRAILER_WIDTH, 0, 0);
232 vertex(TRAILER_WIDTH, 0, TRAILER_DEPTH);
233 vertex(0, 0, TRAILER_DEPTH);
234 endShape();
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();
243
244 noStroke();
245 if (glucose.model.bassBox.exists) {
246 drawBassBox(glucose.model.bassBox, false);
247 }
248 for (Speaker speaker : glucose.model.speakers) {
249 drawSpeaker(speaker);
250 }
251 for (Cube c : glucose.model.cubes) {
252 drawCube(c);
253 }
254
255 noFill();
256 strokeWeight(2);
257 beginShape(POINTS);
258 for (Point p : glucose.model.points) {
259 stroke(simulationColors[p.index]);
260 vertex(p.x, p.y, p.z);
261 }
262 endShape();
263
264 // 2D Overlay UI
265 drawUI();
266
267 // Gamma correction here. Apply a cubic to the brightness
268 // for better representation of dynamic range
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]),
274 (b*b*b) * 100.
275 );
276 }
277
278 // TODO(mcslee): move into GLucose engine
279 for (PandaDriver p : pandaBoards) {
280 p.send(sendColors);
281 }
282 }
283
284 void drawBassBox(BassBox b, boolean hasSub) {
285
286 float in = .15;
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 }
296
297 noStroke();
298 fill(#393939);
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
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
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);
329 popMatrix();
330
331 }
332
333 void drawCube(Cube c) {
334 float in = .15;
335 noStroke();
336 fill(#393939);
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);
338 }
339
340 void 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);
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);
354 popMatrix();
355
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);
359 }
360
361 void 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);
364 rotate(rx / 180. * PI, -1, 0, 0);
365 rotate(ry / 180. * PI, 0, -1, 0);
366 rotate(rz / 180. * PI, 0, 0, -1);
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
393 void drawUI() {
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
400 if (uiOn) {
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();
414 }
415 }
416
417
418 /**
419 * Top-level keyboard event handling
420 */
421 void keyPressed() {
422 if (mappingMode) {
423 mappingTool.keyPressed(uiMapping);
424 }
425 switch (key) {
426 case '-':
427 case '_':
428 frameRate(--targetFramerate);
429 break;
430 case '=':
431 case '+':
432 frameRate(++targetFramerate);
433 break;
434 case 'b':
435 EFF_boom.trigger();
436 break;
437 case 'd':
438 if (!midiEngine.isQwertyEnabled()) {
439 debugMode = !debugMode;
440 println("Debug output: " + (debugMode ? "ON" : "OFF"));
441 }
442 break;
443 case 'm':
444 if (!midiEngine.isQwertyEnabled()) {
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 }
458 }
459 break;
460 case 't':
461 if (!midiEngine.isQwertyEnabled()) {
462 lx.engine.setThreaded(!lx.engine.isThreaded());
463 }
464 break;
465 case 'p':
466 for (PandaDriver p : pandaBoards) {
467 p.toggle();
468 }
469 break;
470 case 'u':
471 if (!midiEngine.isQwertyEnabled()) {
472 uiOn = !uiOn;
473 }
474 break;
475 }
476 }
477
478 /**
479 * Top-level mouse event handling
480 */
481 int mx, my;
482 void mousePressed() {
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 }
491 }
492 mx = mouseX;
493 my = mouseY;
494 }
495
496 void mouseDragged() {
497 boolean dragged = false;
498 for (UIContext context : overlays) {
499 dragged |= context.mouseDragged(mouseX, mouseY);
500 }
501 if (!dragged) {
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
513 void mouseReleased() {
514 for (UIContext context : overlays) {
515 context.mouseReleased(mouseX, mouseY);
516 }
517 }
518
519 void mouseWheel(int delta) {
520 boolean wheeled = false;
521 for (UIContext context : overlays) {
522 wheeled |= context.mouseWheel(mouseX, mouseY, delta);
523 }
524
525 if (!wheeled) {
526 eyeR = constrain(eyeR - delta, -500, -80);
527 eyeX = midX + eyeR*sin(eyeA);
528 eyeZ = midZ + eyeR*cos(eyeA);
529 }
530 }