2 * DOUBLE BLACK DIAMOND DOUBLE BLACK DIAMOND
5 * ///\\\ ///\\\ ///\\\ ///\\\
6 * \\\/// \\\/// \\\/// \\\///
9 * EXPERTS ONLY!! EXPERTS ONLY!!
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.
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.*;
30 import ddf.minim.analysis.*;
31 import processing.opengl.*;
34 final int VIEWPORT_WIDTH = 900;
35 final int VIEWPORT_HEIGHT = 700;
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;
42 final int MaxCubeHeight = 7;
43 final int NumBackTowers = 9;
45 int targetFramerate = 60;
46 int startMillis, lastMillis;
48 // Core engine variables
52 MappingTool mappingTool;
53 PandaDriver[] pandaBoards;
54 MidiEngine midiEngine;
56 // Display configuration mode
57 boolean mappingMode = false;
58 boolean debugMode = false;
61 LXPattern restoreToPattern = null;
64 // Handles to UI objects
66 UIPatternDeck uiPatternA;
67 UICrossfader uiCrossfader;
70 UIDebugText uiDebugText;
73 float eyeR, eyeA, eyeX, eyeY, eyeZ, midX, midY, midZ;
76 * Engine construction and initialization.
79 LXTransition _transition(GLucose glucose) {
80 return new DissolveTransition(glucose.lx).setDuration(1000);
83 LXPattern[] _leftPatterns(GLucose glucose) {
84 LXPattern[] patterns = patterns(glucose);
85 for (LXPattern p : patterns) {
86 p.setTransition(_transition(glucose));
91 LXPattern[] _rightPatterns(GLucose glucose) {
92 LXPattern[] patterns = _leftPatterns(glucose);
93 LXPattern[] rightPatterns = new LXPattern[patterns.length+1];
95 rightPatterns[i++] = new BlankPattern(glucose).setTransition(_transition(glucose));
96 for (LXPattern p : patterns) {
97 rightPatterns[i++] = p;
103 void logTime(String evt) {
105 println(evt + ": " + (now - lastMillis) + "ms");
110 startMillis = lastMillis = millis();
112 // Initialize the Processing graphics environment
113 size(VIEWPORT_WIDTH, VIEWPORT_HEIGHT, OPENGL);
114 frameRate(targetFramerate);
116 // hint(ENABLE_OPENGL_4X_SMOOTH); // no discernable improvement?
117 logTime("Created viewport");
119 // Create the GLucose engine to run the cubes
120 glucose = new GLucose(this, buildModel());
122 lx.enableKeyboardTempo();
123 logTime("Built GLucose engine");
126 midiEngine = new MidiEngine();
127 logTime("Setup MIDI devices");
130 Engine engine = lx.engine;
131 engine.setPatterns(patterns = _leftPatterns(glucose));
132 engine.addDeck(_rightPatterns(glucose));
133 logTime("Built patterns");
134 glucose.setTransitions(transitions(glucose));
135 logTime("Built transitions");
136 glucose.lx.addEffects(effects(glucose));
137 logTime("Built effects");
139 // Build output driver
140 PandaMapping[] pandaMappings = buildPandaList();
141 pandaBoards = new PandaDriver[pandaMappings.length];
143 for (PandaMapping pm : pandaMappings) {
144 pandaBoards[pbi++] = new PandaDriver(pm.ip, glucose.model, pm);
146 mappingTool = new MappingTool(glucose, pandaMappings);
147 logTime("Built PandaDriver");
150 debugUI = new DebugUI(pandaMappings);
151 overlays = new UIContext[] {
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),
158 new UIPatternDeck(lx.engine.getDeck(1), "PATTERN B", width-144, 4, 140, 324),
159 uiMidi = new UIMidi(midiEngine, width-144, 332, 140, 158),
160 new UIOutput(width-144, 494, 140, 106),
162 uiCrossfader = new UICrossfader(width/2-90, height-90, 180, 86),
164 uiDebugText = new UIDebugText(148, height-138, width-304, 44),
165 uiMapping = new UIMapping(mappingTool, 4, 4, 140, 324),
167 uiMapping.setVisible(false);
168 logTime("Built overlay UI");
171 logo = loadImage("data/logo.png");
174 midX = TRAILER_WIDTH/2.;
175 midY = glucose.model.yMax/2;
176 midZ = TRAILER_DEPTH/2.;
180 eyeX = midX + eyeR*sin(eyeA);
181 eyeZ = midZ + eyeR*cos(eyeA);
183 // Add mouse scrolling event support
184 addMouseWheelListener(new java.awt.event.MouseWheelListener() {
185 public void mouseWheelMoved(java.awt.event.MouseWheelEvent mwe) {
186 mouseWheel(mwe.getWheelRotation());
189 println("Total setup: " + (millis() - startMillis) + "ms");
190 println("Hit the 'p' key to toggle Panda Board output");
194 * Core render loop and drawing functionality.
197 // Draws the simulation and the 2D UI overlay
199 color[] colors = glucose.getColors();
201 String displayMode = uiCrossfader.getDisplayMode();
202 if (displayMode == "A") {
203 colors = lx.engine.getDeck(0).getColors();
204 } else if (displayMode == "B") {
205 colors = lx.engine.getDeck(1).getColors();
208 debugUI.maskColors(colors);
221 drawBox(0, -TRAILER_HEIGHT, 0, 0, 0, 0, TRAILER_WIDTH, TRAILER_HEIGHT, TRAILER_DEPTH, TRAILER_HEIGHT/2.);
226 vertex(TRAILER_WIDTH, 0, 0);
227 vertex(TRAILER_WIDTH, 0, TRAILER_DEPTH);
228 vertex(0, 0, TRAILER_DEPTH);
231 // Draw the logo on the front of platform
236 image(logo, TRAILER_WIDTH/2/s-logo.width/2, TRAILER_HEIGHT/2/s-logo.height/2-2/s);
240 if (glucose.model.bassBox.exists) {
241 drawBassBox(glucose.model.bassBox, false);
243 for (Speaker speaker : glucose.model.speakers) {
244 drawSpeaker(speaker);
246 for (Cube c : glucose.model.cubes) {
253 for (Point p : glucose.model.points) {
254 stroke(colors[p.index]);
255 vertex(p.fx, p.fy, p.fz);
262 // Send output colors
263 color[] sendColors = glucose.getColors();
265 debugUI.maskColors(sendColors);
268 // Gamma correction here. Apply a cubic to the brightness
269 // for better representation of dynamic range
270 for (int i = 0; i < sendColors.length; ++i) {
271 float b = brightness(sendColors[i]) / 100.f;
272 sendColors[i] = color(
274 saturation(sendColors[i]),
279 // TODO(mcslee): move into GLucose engine
280 for (PandaDriver p : pandaBoards) {
285 void drawBassBox(BassBox b, boolean hasSub) {
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);
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);
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);
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) {
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);
321 translate(0, 0, BassBox.EDGE_DEPTH - 2*in);
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);
334 void drawCube(Cube c) {
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);
341 void drawSpeaker(Speaker s) {
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);
351 translate(0, Speaker.EDGE_HEIGHT/2. + Speaker.EDGE_HEIGHT*.8/2, 0);
354 box(Speaker.EDGE_WIDTH*.6, Speaker.EDGE_HEIGHT*.8, Speaker.EDGE_DEPTH*.75);
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);
362 void drawBox(float x, float y, float z, float rx, float ry, float rz, float xd, float yd, float zd, float sw) {
365 rotate(rx / 180. * PI, -1, 0, 0);
366 rotate(ry / 180. * PI, 0, -1, 0);
367 rotate(rz / 180. * PI, 0, 0, -1);
368 for (int i = 0; i < 4; ++i) {
369 float wid = (i % 2 == 0) ? xd : zd;
375 vertex(wid - sw, yd);
376 vertex(wid - sw, sw);
382 vertex(wid - sw, yd);
383 vertex(wid - sw, yd - sw);
388 translate(wid, 0, 0);
389 rotate(HALF_PI, 0, -1, 0);
396 javax.media.opengl.GL gl = ((PGraphicsOpenGL)g).beginGL();
397 gl.glClear(javax.media.opengl.GL.GL_DEPTH_BUFFER_BIT);
398 ((PGraphicsOpenGL)g).endGL();
402 for (UIContext context : overlays) {
407 // Always draw FPS meter
410 textAlign(LEFT, BASELINE);
411 text("FPS: " + ((int) (frameRate*10)) / 10. + " / " + targetFramerate + " (-/+)", 4, height-4);
420 * Top-level keyboard event handling
424 mappingTool.keyPressed(uiMapping);
429 frameRate(--targetFramerate);
433 frameRate(++targetFramerate);
439 if (!midiEngine.isQwertyEnabled()) {
440 debugMode = !debugMode;
441 println("Debug output: " + (debugMode ? "ON" : "OFF"));
445 if (!midiEngine.isQwertyEnabled()) {
446 mappingMode = !mappingMode;
447 uiPatternA.setVisible(!mappingMode);
448 uiMapping.setVisible(mappingMode);
450 restoreToPattern = lx.getPattern();
451 lx.setPatterns(new LXPattern[] { mappingTool });
453 lx.setPatterns(patterns);
454 LXTransition pop = restoreToPattern.getTransition();
455 restoreToPattern.setTransition(null);
456 lx.goPattern(restoreToPattern);
457 restoreToPattern.setTransition(pop);
462 for (PandaDriver p : pandaBoards) {
467 if (!midiEngine.isQwertyEnabled()) {
475 * Top-level mouse event handling
478 void mousePressed() {
479 boolean debugged = false;
481 debugged = debugUI.mousePressed();
484 for (UIContext context : overlays) {
485 context.mousePressed(mouseX, mouseY);
492 void mouseDragged() {
493 boolean dragged = false;
494 for (UIContext context : overlays) {
495 dragged |= context.mouseDragged(mouseX, mouseY);
498 int dx = mouseX - mx;
499 int dy = mouseY - my;
503 eyeX = midX + eyeR*sin(eyeA);
504 eyeZ = midZ + eyeR*cos(eyeA);
509 void mouseReleased() {
510 for (UIContext context : overlays) {
511 context.mouseReleased(mouseX, mouseY);
515 void mouseWheel(int delta) {
516 boolean wheeled = false;
517 for (UIContext context : overlays) {
518 wheeled |= context.mouseWheel(mouseX, mouseY, delta);
522 eyeR = constrain(eyeR - delta, -500, -80);
523 eyeX = midX + eyeR*sin(eyeA);
524 eyeZ = midZ + eyeR*cos(eyeA);