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