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 = 5;
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;
55 GridController gridController;
57 // Display configuration mode
58 boolean mappingMode = false;
59 boolean debugMode = false;
62 LXPattern restoreToPattern = null;
64 float[] hsb = new float[3];
66 // Handles to UI objects
68 UIPatternDeck uiPatternA;
69 UICrossfader uiCrossfader;
72 UIDebugText uiDebugText;
75 float eyeR, eyeA, eyeX, eyeY, eyeZ, midX, midY, midZ;
78 * Engine construction and initialization.
81 LXTransition _transition(GLucose glucose) {
82 return new DissolveTransition(glucose.lx).setDuration(1000);
85 LXPattern[] _leftPatterns(GLucose glucose) {
86 LXPattern[] patterns = patterns(glucose);
87 for (LXPattern p : patterns) {
88 p.setTransition(_transition(glucose));
93 LXPattern[] _rightPatterns(GLucose glucose) {
94 LXPattern[] patterns = _leftPatterns(glucose);
95 LXPattern[] rightPatterns = new LXPattern[patterns.length+1];
97 rightPatterns[i++] = new BlankPattern(glucose).setTransition(_transition(glucose));
98 for (LXPattern p : patterns) {
99 rightPatterns[i++] = p;
101 return rightPatterns;
104 void logTime(String evt) {
106 println(evt + ": " + (now - lastMillis) + "ms");
111 startMillis = lastMillis = millis();
113 // Initialize the Processing graphics environment
114 size(VIEWPORT_WIDTH, VIEWPORT_HEIGHT, OPENGL);
115 frameRate(targetFramerate);
117 // hint(ENABLE_OPENGL_4X_SMOOTH); // no discernable improvement?
118 logTime("Created viewport");
120 // Create the GLucose engine to run the cubes
121 glucose = new GLucose(this, buildModel());
123 lx.enableKeyboardTempo();
124 logTime("Built GLucose engine");
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");
137 midiEngine = new MidiEngine();
138 logTime("Setup MIDI devices");
140 // Build output driver
141 PandaMapping[] pandaMappings = buildPandaList();
142 pandaBoards = new PandaDriver[pandaMappings.length];
144 for (PandaMapping pm : pandaMappings) {
145 pandaBoards[pbi++] = new PandaDriver(pm.ip, glucose.model, pm);
147 mappingTool = new MappingTool(glucose, pandaMappings);
148 logTime("Built PandaDriver");
151 debugUI = new DebugUI(pandaMappings);
152 overlays = new UIContext[] {
153 uiPatternA = new UIPatternDeck(lx.engine.getDeck(0), "PATTERN A", 4, 4, 140, 324),
154 new UIBlendMode(4, 332, 140, 86),
155 new UIEffects(4, 422, 140, 144),
156 new UITempo(4, 570, 140, 50),
157 new UISpeed(4, 624, 140, 50),
159 new UIPatternDeck(lx.engine.getDeck(1), "PATTERN B", width-144, 4, 140, 324),
160 uiMidi = new UIMidi(midiEngine, width-144, 332, 140, 158),
161 new UIOutput(width-144, 494, 140, 106),
163 uiCrossfader = new UICrossfader(width/2-90, height-90, 180, 86),
165 uiDebugText = new UIDebugText(148, height-138, width-304, 44),
166 uiMapping = new UIMapping(mappingTool, 4, 4, 140, 324),
168 uiMapping.setVisible(false);
169 logTime("Built overlay UI");
172 logo = loadImage("data/logo.png");
175 midX = TRAILER_WIDTH/2.;
176 midY = glucose.model.yMax/2;
177 midZ = TRAILER_DEPTH/2.;
181 eyeX = midX + eyeR*sin(eyeA);
182 eyeZ = midZ + eyeR*cos(eyeA);
184 // Add mouse scrolling event support
185 addMouseWheelListener(new java.awt.event.MouseWheelListener() {
186 public void mouseWheelMoved(java.awt.event.MouseWheelEvent mwe) {
187 mouseWheel(mwe.getWheelRotation());
190 println("Total setup: " + (millis() - startMillis) + "ms");
191 println("Hit the 'p' key to toggle Panda Board output");
195 * Core render loop and drawing functionality.
198 // Draws the simulation and the 2D UI overlay
201 color[] simulationColors;
203 simulationColors = sendColors = glucose.getColors();
204 String displayMode = uiCrossfader.getDisplayMode();
205 if (displayMode == "A") {
206 simulationColors = lx.engine.getDeck(0).getColors();
207 } else if (displayMode == "B") {
208 simulationColors = lx.engine.getDeck(1).getColors();
211 debugUI.maskColors(simulationColors);
212 debugUI.maskColors(sendColors);
225 drawBox(0, -TRAILER_HEIGHT, 0, 0, 0, 0, TRAILER_WIDTH, TRAILER_HEIGHT, TRAILER_DEPTH, TRAILER_HEIGHT/2.);
230 vertex(TRAILER_WIDTH, 0, 0);
231 vertex(TRAILER_WIDTH, 0, TRAILER_DEPTH);
232 vertex(0, 0, TRAILER_DEPTH);
235 // Draw the logo on the front of platform
240 image(logo, TRAILER_WIDTH/2/s-logo.width/2, TRAILER_HEIGHT/2/s-logo.height/2-2/s);
244 if (glucose.model.bassBox.exists) {
245 drawBassBox(glucose.model.bassBox, false);
247 for (Speaker speaker : glucose.model.speakers) {
248 drawSpeaker(speaker);
250 for (Cube c : glucose.model.cubes) {
257 for (Point p : glucose.model.points) {
258 stroke(simulationColors[p.index]);
259 vertex(p.x, p.y, p.z);
266 // Gamma correction here. Apply a cubic to the brightness
267 // for better representation of dynamic range
268 for (int i = 0; i < sendColors.length; ++i) {
269 lx.RGBtoHSB(sendColors[i], hsb);
271 sendColors[i] = lx.hsb(360.*hsb[0], 100.*hsb[1], 100.*(b*b*b));
274 // TODO(mcslee): move into GLucose engine
275 for (PandaDriver p : pandaBoards) {
280 void drawBassBox(BassBox b, boolean hasSub) {
288 translate(b.x + BassBox.EDGE_WIDTH/2., b.y + BassBox.EDGE_HEIGHT/2, b.z + BassBox.EDGE_DEPTH/2.);
289 box(BassBox.EDGE_WIDTH-20*in, BassBox.EDGE_HEIGHT-20*in, BassBox.EDGE_DEPTH-20*in);
295 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 translate(b.x+(Cube.CHANNEL_WIDTH-in)/2., b.y + BassBox.EDGE_HEIGHT-in, b.z + BassBox.EDGE_DEPTH/2.);
299 float lastOffset = 0;
300 for (float offset : BoothFloor.STRIP_OFFSETS) {
301 translate(offset - lastOffset, 0, 0);
302 box(Cube.CHANNEL_WIDTH-in, 0, BassBox.EDGE_DEPTH - 2*in);
308 translate(b.x + (Cube.CHANNEL_WIDTH-in)/2., b.y + BassBox.EDGE_HEIGHT/2., b.z + in);
309 for (int j = 0; j < 2; ++j) {
311 for (int i = 0; i < BassBox.NUM_FRONT_STRUTS; ++i) {
312 translate(BassBox.FRONT_STRUT_SPACING, 0, 0);
313 box(Cube.CHANNEL_WIDTH-in, BassBox.EDGE_HEIGHT - in*2, 0);
316 translate(0, 0, BassBox.EDGE_DEPTH - 2*in);
321 translate(b.x + in, b.y + BassBox.EDGE_HEIGHT/2., b.z + BassBox.SIDE_STRUT_SPACING + (Cube.CHANNEL_WIDTH-in)/2.);
322 box(0, BassBox.EDGE_HEIGHT - in*2, Cube.CHANNEL_WIDTH-in);
323 translate(BassBox.EDGE_WIDTH-2*in, 0, 0);
324 box(0, BassBox.EDGE_HEIGHT - in*2, Cube.CHANNEL_WIDTH-in);
329 void drawCube(Cube c) {
333 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 void drawSpeaker(Speaker s) {
342 translate(s.x, s.y, s.z);
343 rotate(s.ry / 180. * PI, 0, -1, 0);
344 translate(Speaker.EDGE_WIDTH/2., Speaker.EDGE_HEIGHT/2., Speaker.EDGE_DEPTH/2.);
345 box(Speaker.EDGE_WIDTH-20*in, Speaker.EDGE_HEIGHT-20*in, Speaker.EDGE_DEPTH-20*in);
346 translate(0, Speaker.EDGE_HEIGHT/2. + Speaker.EDGE_HEIGHT*.8/2, 0);
349 box(Speaker.EDGE_WIDTH*.6, Speaker.EDGE_HEIGHT*.8, Speaker.EDGE_DEPTH*.75);
354 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 void drawBox(float x, float y, float z, float rx, float ry, float rz, float xd, float yd, float zd, float sw) {
360 rotate(rx / 180. * PI, -1, 0, 0);
361 rotate(ry / 180. * PI, 0, -1, 0);
362 rotate(rz / 180. * PI, 0, 0, -1);
363 for (int i = 0; i < 4; ++i) {
364 float wid = (i % 2 == 0) ? xd : zd;
370 vertex(wid - sw, yd);
371 vertex(wid - sw, sw);
377 vertex(wid - sw, yd);
378 vertex(wid - sw, yd - sw);
383 translate(wid, 0, 0);
384 rotate(HALF_PI, 0, -1, 0);
391 javax.media.opengl.GL gl = ((PGraphicsOpenGL)g).beginGL();
392 gl.glClear(javax.media.opengl.GL.GL_DEPTH_BUFFER_BIT);
393 ((PGraphicsOpenGL)g).endGL();
397 for (UIContext context : overlays) {
402 // Always draw FPS meter
405 textAlign(LEFT, BASELINE);
406 text("FPS: " + ((int) (frameRate*10)) / 10. + " / " + targetFramerate + " (-/+)", 4, height-4);
415 * Top-level keyboard event handling
419 mappingTool.keyPressed(uiMapping);
424 frameRate(--targetFramerate);
428 frameRate(++targetFramerate);
434 if (!midiEngine.isQwertyEnabled()) {
435 debugMode = !debugMode;
436 println("Debug output: " + (debugMode ? "ON" : "OFF"));
440 if (!midiEngine.isQwertyEnabled()) {
441 mappingMode = !mappingMode;
442 uiPatternA.setVisible(!mappingMode);
443 uiMapping.setVisible(mappingMode);
445 restoreToPattern = lx.getPattern();
446 lx.setPatterns(new LXPattern[] { mappingTool });
448 lx.setPatterns(patterns);
449 LXTransition pop = restoreToPattern.getTransition();
450 restoreToPattern.setTransition(null);
451 lx.goPattern(restoreToPattern);
452 restoreToPattern.setTransition(pop);
457 if (!midiEngine.isQwertyEnabled()) {
458 lx.engine.setThreaded(!lx.engine.isThreaded());
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);