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 PresetManager presetManager;
55 MidiEngine midiEngine;
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 presetManager = new PresetManager();
139 logTime("Loaded presets");
142 midiEngine = new MidiEngine();
143 presetManager.setMidiEngine(midiEngine);
144 logTime("Setup MIDI devices");
146 // Build output driver
147 PandaMapping[] pandaMappings = buildPandaList();
148 pandaBoards = new PandaDriver[pandaMappings.length];
150 for (PandaMapping pm : pandaMappings) {
151 pandaBoards[pbi++] = new PandaDriver(pm.ip, glucose.model, pm);
153 mappingTool = new MappingTool(glucose, pandaMappings);
154 logTime("Built PandaDriver");
157 debugUI = new DebugUI(pandaMappings);
158 overlays = new UIContext[] {
159 uiPatternA = new UIPatternDeck(lx.engine.getDeck(0), "PATTERN A", 4, 4, 140, 324),
160 new UIBlendMode(4, 332, 140, 86),
161 new UIEffects(4, 422, 140, 144),
162 new UITempo(4, 570, 140, 50),
163 new UISpeed(4, 624, 140, 50),
165 new UIPatternDeck(lx.engine.getDeck(1), "PATTERN B", width-144, 4, 140, 324),
166 uiMidi = new UIMidi(midiEngine, width-144, 332, 140, 158),
167 new UIOutput(width-144, 494, 140, 106),
169 uiCrossfader = new UICrossfader(width/2-90, height-90, 180, 86),
171 uiDebugText = new UIDebugText(148, height-138, width-304, 44),
172 uiMapping = new UIMapping(mappingTool, 4, 4, 140, 324),
174 uiMapping.setVisible(false);
175 logTime("Built overlay UI");
178 logo = loadImage("data/logo.png");
181 midX = TRAILER_WIDTH/2.;
182 midY = glucose.model.yMax/2;
183 midZ = TRAILER_DEPTH/2.;
187 eyeX = midX + eyeR*sin(eyeA);
188 eyeZ = midZ + eyeR*cos(eyeA);
190 // Add mouse scrolling event support
191 addMouseWheelListener(new java.awt.event.MouseWheelListener() {
192 public void mouseWheelMoved(java.awt.event.MouseWheelEvent mwe) {
193 mouseWheel(mwe.getWheelRotation());
196 println("Total setup: " + (millis() - startMillis) + "ms");
197 println("Hit the 'p' key to toggle Panda Board output");
201 * Core render loop and drawing functionality.
204 // Draws the simulation and the 2D UI overlay
207 color[] simulationColors;
209 simulationColors = sendColors = glucose.getColors();
210 String displayMode = uiCrossfader.getDisplayMode();
211 if (displayMode == "A") {
212 simulationColors = lx.engine.getDeck(0).getColors();
213 } else if (displayMode == "B") {
214 simulationColors = lx.engine.getDeck(1).getColors();
217 debugUI.maskColors(simulationColors);
218 debugUI.maskColors(sendColors);
231 drawBox(0, -TRAILER_HEIGHT, 0, 0, 0, 0, TRAILER_WIDTH, TRAILER_HEIGHT, TRAILER_DEPTH, TRAILER_HEIGHT/2.);
236 vertex(TRAILER_WIDTH, 0, 0);
237 vertex(TRAILER_WIDTH, 0, TRAILER_DEPTH);
238 vertex(0, 0, TRAILER_DEPTH);
241 // Draw the logo on the front of platform
246 image(logo, TRAILER_WIDTH/2/s-logo.width/2, TRAILER_HEIGHT/2/s-logo.height/2-2/s);
250 if (glucose.model.bassBox.exists) {
251 drawBassBox(glucose.model.bassBox, false);
253 for (Speaker speaker : glucose.model.speakers) {
254 drawSpeaker(speaker);
256 for (Cube c : glucose.model.cubes) {
263 for (Point p : glucose.model.points) {
264 stroke(simulationColors[p.index]);
265 vertex(p.x, p.y, p.z);
272 // Gamma correction here. Apply a cubic to the brightness
273 // for better representation of dynamic range
274 for (int i = 0; i < sendColors.length; ++i) {
275 lx.RGBtoHSB(sendColors[i], hsb);
277 sendColors[i] = lx.hsb(360.*hsb[0], 100.*hsb[1], 100.*(b*b*b));
280 // TODO(mcslee): move into GLucose engine
281 for (PandaDriver p : pandaBoards) {
286 void drawBassBox(BassBox b, boolean hasSub) {
294 translate(b.x + BassBox.EDGE_WIDTH/2., b.y + BassBox.EDGE_HEIGHT/2, b.z + BassBox.EDGE_DEPTH/2.);
295 box(BassBox.EDGE_WIDTH-20*in, BassBox.EDGE_HEIGHT-20*in, BassBox.EDGE_DEPTH-20*in);
301 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);
304 translate(b.x+(Cube.CHANNEL_WIDTH-in)/2., b.y + BassBox.EDGE_HEIGHT-in, b.z + BassBox.EDGE_DEPTH/2.);
305 float lastOffset = 0;
306 for (float offset : BoothFloor.STRIP_OFFSETS) {
307 translate(offset - lastOffset, 0, 0);
308 box(Cube.CHANNEL_WIDTH-in, 0, BassBox.EDGE_DEPTH - 2*in);
314 translate(b.x + (Cube.CHANNEL_WIDTH-in)/2., b.y + BassBox.EDGE_HEIGHT/2., b.z + in);
315 for (int j = 0; j < 2; ++j) {
317 for (int i = 0; i < BassBox.NUM_FRONT_STRUTS; ++i) {
318 translate(BassBox.FRONT_STRUT_SPACING, 0, 0);
319 box(Cube.CHANNEL_WIDTH-in, BassBox.EDGE_HEIGHT - in*2, 0);
322 translate(0, 0, BassBox.EDGE_DEPTH - 2*in);
327 translate(b.x + in, b.y + BassBox.EDGE_HEIGHT/2., b.z + BassBox.SIDE_STRUT_SPACING + (Cube.CHANNEL_WIDTH-in)/2.);
328 box(0, BassBox.EDGE_HEIGHT - in*2, Cube.CHANNEL_WIDTH-in);
329 translate(BassBox.EDGE_WIDTH-2*in, 0, 0);
330 box(0, BassBox.EDGE_HEIGHT - in*2, Cube.CHANNEL_WIDTH-in);
335 void drawCube(Cube c) {
339 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);
342 void drawSpeaker(Speaker s) {
348 translate(s.x, s.y, s.z);
349 rotate(s.ry / 180. * PI, 0, -1, 0);
350 translate(Speaker.EDGE_WIDTH/2., Speaker.EDGE_HEIGHT/2., Speaker.EDGE_DEPTH/2.);
351 box(Speaker.EDGE_WIDTH-20*in, Speaker.EDGE_HEIGHT-20*in, Speaker.EDGE_DEPTH-20*in);
352 translate(0, Speaker.EDGE_HEIGHT/2. + Speaker.EDGE_HEIGHT*.8/2, 0);
355 box(Speaker.EDGE_WIDTH*.6, Speaker.EDGE_HEIGHT*.8, Speaker.EDGE_DEPTH*.75);
360 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);
363 void drawBox(float x, float y, float z, float rx, float ry, float rz, float xd, float yd, float zd, float sw) {
366 rotate(rx / 180. * PI, -1, 0, 0);
367 rotate(ry / 180. * PI, 0, -1, 0);
368 rotate(rz / 180. * PI, 0, 0, -1);
369 for (int i = 0; i < 4; ++i) {
370 float wid = (i % 2 == 0) ? xd : zd;
376 vertex(wid - sw, yd);
377 vertex(wid - sw, sw);
383 vertex(wid - sw, yd);
384 vertex(wid - sw, yd - sw);
389 translate(wid, 0, 0);
390 rotate(HALF_PI, 0, -1, 0);
397 javax.media.opengl.GL gl = ((PGraphicsOpenGL)g).beginGL();
398 gl.glClear(javax.media.opengl.GL.GL_DEPTH_BUFFER_BIT);
399 ((PGraphicsOpenGL)g).endGL();
403 for (UIContext context : overlays) {
408 // Always draw FPS meter
411 textAlign(LEFT, BASELINE);
412 text("FPS: " + ((int) (frameRate*10)) / 10. + " / " + targetFramerate + " (-/+)", 4, height-4);
421 * Top-level keyboard event handling
425 mappingTool.keyPressed(uiMapping);
430 frameRate(--targetFramerate);
434 frameRate(++targetFramerate);
440 if (!midiEngine.isQwertyEnabled()) {
441 debugMode = !debugMode;
442 println("Debug output: " + (debugMode ? "ON" : "OFF"));
446 if (!midiEngine.isQwertyEnabled()) {
447 mappingMode = !mappingMode;
448 uiPatternA.setVisible(!mappingMode);
449 uiMapping.setVisible(mappingMode);
451 restoreToPattern = lx.getPattern();
452 lx.setPatterns(new LXPattern[] { mappingTool });
454 lx.setPatterns(patterns);
455 LXTransition pop = restoreToPattern.getTransition();
456 restoreToPattern.setTransition(null);
457 lx.goPattern(restoreToPattern);
458 restoreToPattern.setTransition(pop);
463 if (!midiEngine.isQwertyEnabled()) {
464 lx.engine.setThreaded(!lx.engine.isThreaded());
468 for (PandaDriver p : pandaBoards) {
473 if (!midiEngine.isQwertyEnabled()) {
481 * Top-level mouse event handling
484 void mousePressed() {
485 boolean debugged = false;
487 debugged = debugUI.mousePressed();
490 for (UIContext context : overlays) {
491 context.mousePressed(mouseX, mouseY);
498 void mouseDragged() {
499 boolean dragged = false;
500 for (UIContext context : overlays) {
501 dragged |= context.mouseDragged(mouseX, mouseY);
504 int dx = mouseX - mx;
505 int dy = mouseY - my;
509 eyeX = midX + eyeR*sin(eyeA);
510 eyeZ = midZ + eyeR*cos(eyeA);
515 void mouseReleased() {
516 for (UIContext context : overlays) {
517 context.mouseReleased(mouseX, mouseY);
521 void mouseWheel(int delta) {
522 boolean wheeled = false;
523 for (UIContext context : overlays) {
524 wheeled |= context.mouseWheel(mouseX, mouseY, delta);
528 eyeR = constrain(eyeR - delta, -500, -80);
529 eyeX = midX + eyeR*sin(eyeA);
530 eyeZ = midZ + eyeR*cos(eyeA);