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 = 11;
45 int targetFramerate = 60;
46 int startMillis, lastMillis;
48 // Core engine variables
52 MappingTool mappingTool;
53 PandaDriver[] pandaBoards;
54 MidiEngine midiEngine;
55 PresetManager presetManager;
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;
105 void logTime(String evt) {
107 println(evt + ": " + (now - lastMillis) + "ms");
112 startMillis = lastMillis = millis();
114 // Initialize the Processing graphics environment
115 size(VIEWPORT_WIDTH, VIEWPORT_HEIGHT, OPENGL);
116 frameRate(targetFramerate);
118 // hint(ENABLE_OPENGL_4X_SMOOTH); // no discernable improvement?
119 logTime("Created viewport");
121 // Create the GLucose engine to run the cubes
122 glucose = new GLucose(this, buildModel());
124 lx.enableKeyboardTempo();
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");
142 presetManager = new PresetManager();
143 logTime("Loaded presets");
145 // Build output driver
146 PandaMapping[] pandaMappings = buildPandaList();
147 pandaBoards = new PandaDriver[pandaMappings.length];
149 for (PandaMapping pm : pandaMappings) {
150 pandaBoards[pbi++] = new PandaDriver(pm.ip, glucose.model, pm);
152 mappingTool = new MappingTool(glucose, pandaMappings);
153 logTime("Built PandaDriver");
156 debugUI = new DebugUI(pandaMappings);
157 overlays = new UIContext[] {
158 uiPatternA = new UIPatternDeck(lx.engine.getDeck(0), "PATTERN A", 4, 4, 140, 324),
159 new UIBlendMode(4, 332, 140, 86),
160 new UIEffects(4, 422, 140, 144),
161 new UITempo(4, 570, 140, 50),
162 new UISpeed(4, 624, 140, 50),
164 new UIPatternDeck(lx.engine.getDeck(1), "PATTERN B", width-144, 4, 140, 324),
165 uiMidi = new UIMidi(midiEngine, width-144, 332, 140, 158),
166 new UIOutput(width-144, 494, 140, 106),
168 uiCrossfader = new UICrossfader(width/2-90, height-90, 180, 86),
170 uiDebugText = new UIDebugText(148, height-138, width-304, 44),
171 uiMapping = new UIMapping(mappingTool, 4, 4, 140, 324),
173 uiMapping.setVisible(false);
174 logTime("Built overlay UI");
177 logo = loadImage("data/logo.png");
180 midX = TRAILER_WIDTH/2.;
181 midY = glucose.model.yMax/2;
182 midZ = TRAILER_DEPTH/2.;
186 eyeX = midX + eyeR*sin(eyeA);
187 eyeZ = midZ + eyeR*cos(eyeA);
189 // Add mouse scrolling event support
190 addMouseWheelListener(new java.awt.event.MouseWheelListener() {
191 public void mouseWheelMoved(java.awt.event.MouseWheelEvent mwe) {
192 mouseWheel(mwe.getWheelRotation());
195 println("Total setup: " + (millis() - startMillis) + "ms");
196 println("Hit the 'p' key to toggle Panda Board output");
200 * Core render loop and drawing functionality.
203 // Draws the simulation and the 2D UI overlay
206 color[] simulationColors;
208 simulationColors = sendColors = glucose.getColors();
209 String displayMode = uiCrossfader.getDisplayMode();
210 if (displayMode == "A") {
211 simulationColors = lx.engine.getDeck(0).getColors();
212 } else if (displayMode == "B") {
213 simulationColors = lx.engine.getDeck(1).getColors();
216 debugUI.maskColors(simulationColors);
217 debugUI.maskColors(sendColors);
230 drawBox(0, -TRAILER_HEIGHT, 0, 0, 0, 0, TRAILER_WIDTH, TRAILER_HEIGHT, TRAILER_DEPTH, TRAILER_HEIGHT/2.);
235 vertex(TRAILER_WIDTH, 0, 0);
236 vertex(TRAILER_WIDTH, 0, TRAILER_DEPTH);
237 vertex(0, 0, TRAILER_DEPTH);
240 // Draw the logo on the front of platform
245 image(logo, TRAILER_WIDTH/2/s-logo.width/2, TRAILER_HEIGHT/2/s-logo.height/2-2/s);
249 if (glucose.model.bassBox.exists) {
250 drawBassBox(glucose.model.bassBox, false);
252 for (Speaker speaker : glucose.model.speakers) {
253 drawSpeaker(speaker);
255 for (Cube c : glucose.model.cubes) {
262 for (Point p : glucose.model.points) {
263 stroke(simulationColors[p.index]);
264 vertex(p.x, p.y, p.z);
271 // Gamma correction here. Apply a cubic to the brightness
272 // for better representation of dynamic range
273 for (int i = 0; i < sendColors.length; ++i) {
274 lx.RGBtoHSB(sendColors[i], hsb);
276 sendColors[i] = lx.hsb(360.*hsb[0], 100.*hsb[1], 100.*(b*b*b));
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 if (!midiEngine.isQwertyEnabled()) {
463 lx.engine.setThreaded(!lx.engine.isThreaded());
467 for (PandaDriver p : pandaBoards) {
472 if (!midiEngine.isQwertyEnabled()) {
480 * Top-level mouse event handling
483 void mousePressed() {
484 boolean debugged = false;
486 debugged = debugUI.mousePressed();
489 for (UIContext context : overlays) {
490 context.mousePressed(mouseX, mouseY);
497 void mouseDragged() {
498 boolean dragged = false;
499 for (UIContext context : overlays) {
500 dragged |= context.mouseDragged(mouseX, mouseY);
503 int dx = mouseX - mx;
504 int dy = mouseY - my;
508 eyeX = midX + eyeR*sin(eyeA);
509 eyeZ = midZ + eyeR*cos(eyeA);
514 void mouseReleased() {
515 for (UIContext context : overlays) {
516 context.mouseReleased(mouseX, mouseY);
520 void mouseWheel(int delta) {
521 boolean wheeled = false;
522 for (UIContext context : overlays) {
523 wheeled |= context.mouseWheel(mouseX, mouseY, delta);
527 eyeR = constrain(eyeR - delta, -500, -80);
528 eyeX = midX + eyeR*sin(eyeA);
529 eyeZ = midZ + eyeR*cos(eyeA);