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