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