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;
57 // Display configuration mode
58 boolean mappingMode = false;
59 boolean debugMode = false;
62 LXPattern restoreToPattern = null;
65 // Handles to UI objects
67 UIPatternDeck uiPatternA;
68 UICrossfader uiCrossfader;
71 UIDebugText uiDebugText;
74 float eyeR, eyeA, eyeX, eyeY, eyeZ, midX, midY, midZ;
77 * Engine construction and initialization.
80 LXTransition _transition(GLucose glucose) {
81 return new DissolveTransition(glucose.lx).setDuration(1000);
84 LXPattern[] _leftPatterns(GLucose glucose) {
85 LXPattern[] patterns = patterns(glucose);
86 for (LXPattern p : patterns) {
87 p.setTransition(_transition(glucose));
92 LXPattern[] _rightPatterns(GLucose glucose) {
93 LXPattern[] patterns = _leftPatterns(glucose);
94 LXPattern[] rightPatterns = new LXPattern[patterns.length+1];
96 rightPatterns[i++] = new BlankPattern(glucose).setTransition(_transition(glucose));
97 for (LXPattern p : patterns) {
98 rightPatterns[i++] = p;
100 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 threadColors = new color[lx.total];
125 logTime("Built GLucose engine");
128 Engine 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");
138 midiEngine = new MidiEngine();
139 logTime("Setup MIDI devices");
141 // Build output driver
142 PandaMapping[] pandaMappings = buildPandaList();
143 pandaBoards = new PandaDriver[pandaMappings.length];
145 for (PandaMapping pm : pandaMappings) {
146 pandaBoards[pbi++] = new PandaDriver(pm.ip, glucose.model, pm);
148 mappingTool = new MappingTool(glucose, pandaMappings);
149 logTime("Built PandaDriver");
152 debugUI = new DebugUI(pandaMappings);
153 overlays = new UIContext[] {
154 uiPatternA = new UIPatternDeck(lx.engine.getDeck(0), "PATTERN A", 4, 4, 140, 324),
155 new UIBlendMode(4, 332, 140, 86),
156 new UIEffects(4, 422, 140, 144),
157 new UITempo(4, 570, 140, 50),
158 new UISpeed(4, 624, 140, 50),
160 new UIPatternDeck(lx.engine.getDeck(1), "PATTERN B", width-144, 4, 140, 324),
161 uiMidi = new UIMidi(midiEngine, width-144, 332, 140, 158),
162 new UIOutput(width-144, 494, 140, 106),
164 uiCrossfader = new UICrossfader(width/2-90, height-90, 180, 86),
166 uiDebugText = new UIDebugText(148, height-138, width-304, 44),
167 uiMapping = new UIMapping(mappingTool, 4, 4, 140, 324),
169 uiMapping.setVisible(false);
170 logTime("Built overlay UI");
173 logo = loadImage("data/logo.png");
176 midX = TRAILER_WIDTH/2.;
177 midY = glucose.model.yMax/2;
178 midZ = TRAILER_DEPTH/2.;
182 eyeX = midX + eyeR*sin(eyeA);
183 eyeZ = midZ + eyeR*cos(eyeA);
185 // Add mouse scrolling event support
186 addMouseWheelListener(new java.awt.event.MouseWheelListener() {
187 public void mouseWheelMoved(java.awt.event.MouseWheelEvent mwe) {
188 mouseWheel(mwe.getWheelRotation());
191 println("Total setup: " + (millis() - startMillis) + "ms");
192 println("Hit the 'p' key to toggle Panda Board output");
196 * Core render loop and drawing functionality.
199 // Draws the simulation and the 2D UI overlay
202 color[] simulationColors;
204 simulationColors = sendColors = glucose.getColors();
205 String displayMode = uiCrossfader.getDisplayMode();
206 if (displayMode == "A") {
207 simulationColors = lx.engine.getDeck(0).getColors();
208 } else if (displayMode == "B") {
209 simulationColors = lx.engine.getDeck(1).getColors();
212 debugUI.maskColors(simulationColors);
213 debugUI.maskColors(sendColors);
226 drawBox(0, -TRAILER_HEIGHT, 0, 0, 0, 0, TRAILER_WIDTH, TRAILER_HEIGHT, TRAILER_DEPTH, TRAILER_HEIGHT/2.);
231 vertex(TRAILER_WIDTH, 0, 0);
232 vertex(TRAILER_WIDTH, 0, TRAILER_DEPTH);
233 vertex(0, 0, TRAILER_DEPTH);
236 // Draw the logo on the front of platform
241 image(logo, TRAILER_WIDTH/2/s-logo.width/2, TRAILER_HEIGHT/2/s-logo.height/2-2/s);
245 if (glucose.model.bassBox.exists) {
246 drawBassBox(glucose.model.bassBox, false);
248 for (Speaker speaker : glucose.model.speakers) {
249 drawSpeaker(speaker);
251 for (Cube c : glucose.model.cubes) {
258 for (Point p : glucose.model.points) {
259 stroke(simulationColors[p.index]);
260 vertex(p.x, p.y, p.z);
267 // Gamma correction here. Apply a cubic to the brightness
268 // for better representation of dynamic range
269 for (int i = 0; i < sendColors.length; ++i) {
270 float b = brightness(sendColors[i]) / 100.f;
271 sendColors[i] = color(
273 saturation(sendColors[i]),
278 // TODO(mcslee): move into GLucose engine
279 for (PandaDriver p : pandaBoards) {
284 void drawBassBox(BassBox b, boolean hasSub) {
292 translate(b.x + BassBox.EDGE_WIDTH/2., b.y + BassBox.EDGE_HEIGHT/2, b.z + BassBox.EDGE_DEPTH/2.);
293 box(BassBox.EDGE_WIDTH-20*in, BassBox.EDGE_HEIGHT-20*in, BassBox.EDGE_DEPTH-20*in);
299 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 translate(b.x+(Cube.CHANNEL_WIDTH-in)/2., b.y + BassBox.EDGE_HEIGHT-in, b.z + BassBox.EDGE_DEPTH/2.);
303 float lastOffset = 0;
304 for (float offset : BoothFloor.STRIP_OFFSETS) {
305 translate(offset - lastOffset, 0, 0);
306 box(Cube.CHANNEL_WIDTH-in, 0, BassBox.EDGE_DEPTH - 2*in);
312 translate(b.x + (Cube.CHANNEL_WIDTH-in)/2., b.y + BassBox.EDGE_HEIGHT/2., b.z + in);
313 for (int j = 0; j < 2; ++j) {
315 for (int i = 0; i < BassBox.NUM_FRONT_STRUTS; ++i) {
316 translate(BassBox.FRONT_STRUT_SPACING, 0, 0);
317 box(Cube.CHANNEL_WIDTH-in, BassBox.EDGE_HEIGHT - in*2, 0);
320 translate(0, 0, BassBox.EDGE_DEPTH - 2*in);
325 translate(b.x + in, b.y + BassBox.EDGE_HEIGHT/2., b.z + BassBox.SIDE_STRUT_SPACING + (Cube.CHANNEL_WIDTH-in)/2.);
326 box(0, BassBox.EDGE_HEIGHT - in*2, Cube.CHANNEL_WIDTH-in);
327 translate(BassBox.EDGE_WIDTH-2*in, 0, 0);
328 box(0, BassBox.EDGE_HEIGHT - in*2, Cube.CHANNEL_WIDTH-in);
333 void drawCube(Cube c) {
337 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 void drawSpeaker(Speaker s) {
346 translate(s.x, s.y, s.z);
347 rotate(s.ry / 180. * PI, 0, -1, 0);
348 translate(Speaker.EDGE_WIDTH/2., Speaker.EDGE_HEIGHT/2., Speaker.EDGE_DEPTH/2.);
349 box(Speaker.EDGE_WIDTH-20*in, Speaker.EDGE_HEIGHT-20*in, Speaker.EDGE_DEPTH-20*in);
350 translate(0, Speaker.EDGE_HEIGHT/2. + Speaker.EDGE_HEIGHT*.8/2, 0);
353 box(Speaker.EDGE_WIDTH*.6, Speaker.EDGE_HEIGHT*.8, Speaker.EDGE_DEPTH*.75);
358 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 void drawBox(float x, float y, float z, float rx, float ry, float rz, float xd, float yd, float zd, float sw) {
364 rotate(rx / 180. * PI, -1, 0, 0);
365 rotate(ry / 180. * PI, 0, -1, 0);
366 rotate(rz / 180. * PI, 0, 0, -1);
367 for (int i = 0; i < 4; ++i) {
368 float wid = (i % 2 == 0) ? xd : zd;
374 vertex(wid - sw, yd);
375 vertex(wid - sw, sw);
381 vertex(wid - sw, yd);
382 vertex(wid - sw, yd - sw);
387 translate(wid, 0, 0);
388 rotate(HALF_PI, 0, -1, 0);
395 javax.media.opengl.GL gl = ((PGraphicsOpenGL)g).beginGL();
396 gl.glClear(javax.media.opengl.GL.GL_DEPTH_BUFFER_BIT);
397 ((PGraphicsOpenGL)g).endGL();
401 for (UIContext context : overlays) {
406 // Always draw FPS meter
409 textAlign(LEFT, BASELINE);
410 text("FPS: " + ((int) (frameRate*10)) / 10. + " / " + targetFramerate + " (-/+)", 4, height-4);
419 * Top-level keyboard event handling
423 mappingTool.keyPressed(uiMapping);
428 frameRate(--targetFramerate);
432 frameRate(++targetFramerate);
438 if (!midiEngine.isQwertyEnabled()) {
439 debugMode = !debugMode;
440 println("Debug output: " + (debugMode ? "ON" : "OFF"));
444 if (!midiEngine.isQwertyEnabled()) {
445 mappingMode = !mappingMode;
446 uiPatternA.setVisible(!mappingMode);
447 uiMapping.setVisible(mappingMode);
449 restoreToPattern = lx.getPattern();
450 lx.setPatterns(new LXPattern[] { mappingTool });
452 lx.setPatterns(patterns);
453 LXTransition pop = restoreToPattern.getTransition();
454 restoreToPattern.setTransition(null);
455 lx.goPattern(restoreToPattern);
456 restoreToPattern.setTransition(pop);
461 if (!midiEngine.isQwertyEnabled()) {
462 lx.engine.setThreaded(!lx.engine.isThreaded());
466 for (PandaDriver p : pandaBoards) {
471 if (!midiEngine.isQwertyEnabled()) {
479 * Top-level mouse event handling
482 void mousePressed() {
483 boolean debugged = false;
485 debugged = debugUI.mousePressed();
488 for (UIContext context : overlays) {
489 context.mousePressed(mouseX, mouseY);
496 void mouseDragged() {
497 boolean dragged = false;
498 for (UIContext context : overlays) {
499 dragged |= context.mouseDragged(mouseX, mouseY);
502 int dx = mouseX - mx;
503 int dy = mouseY - my;
507 eyeX = midX + eyeR*sin(eyeA);
508 eyeZ = midZ + eyeR*cos(eyeA);
513 void mouseReleased() {
514 for (UIContext context : overlays) {
515 context.mouseReleased(mouseX, mouseY);
519 void mouseWheel(int delta) {
520 boolean wheeled = false;
521 for (UIContext context : overlays) {
522 wheeled |= context.mouseWheel(mouseX, mouseY, delta);
526 eyeR = constrain(eyeR - delta, -500, -80);
527 eyeX = midX + eyeR*sin(eyeA);
528 eyeZ = midZ + eyeR*cos(eyeA);