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 int targetFramerate = 60;
43 int startMillis, lastMillis;
45 // Core engine variables
49 MappingTool mappingTool;
50 PandaDriver[] pandaBoards;
51 MidiEngine midiEngine;
53 // Display configuration mode
54 boolean mappingMode = false;
55 boolean debugMode = false;
58 LXPattern restoreToPattern = null;
61 // Handles to UI objects
63 UIPatternDeck uiPatternA;
64 UICrossfader uiCrossfader;
67 UIDebugText uiDebugText;
70 float eyeR, eyeA, eyeX, eyeY, eyeZ, midX, midY, midZ;
73 * Engine construction and initialization.
76 LXTransition _transition(GLucose glucose) {
77 return new DissolveTransition(glucose.lx).setDuration(1000);
80 LXPattern[] _leftPatterns(GLucose glucose) {
81 LXPattern[] patterns = patterns(glucose);
82 for (LXPattern p : patterns) {
83 p.setTransition(_transition(glucose));
88 LXPattern[] _rightPatterns(GLucose glucose) {
89 LXPattern[] patterns = _leftPatterns(glucose);
90 LXPattern[] rightPatterns = new LXPattern[patterns.length+1];
92 rightPatterns[i++] = new BlankPattern(glucose).setTransition(_transition(glucose));
93 for (LXPattern p : patterns) {
94 rightPatterns[i++] = p;
100 void logTime(String evt) {
102 println(evt + ": " + (now - lastMillis) + "ms");
107 startMillis = lastMillis = millis();
109 // Initialize the Processing graphics environment
110 size(VIEWPORT_WIDTH, VIEWPORT_HEIGHT, OPENGL);
111 frameRate(targetFramerate);
113 // hint(ENABLE_OPENGL_4X_SMOOTH); // no discernable improvement?
114 logTime("Created viewport");
116 // Create the GLucose engine to run the cubes
117 glucose = new GLucose(this, buildModel());
119 lx.enableKeyboardTempo();
120 logTime("Built GLucose engine");
123 midiEngine = new MidiEngine();
124 logTime("Setup MIDI devices");
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");
136 // Build output driver
137 PandaMapping[] pandaMappings = buildPandaList();
138 pandaBoards = new PandaDriver[pandaMappings.length];
140 for (PandaMapping pm : pandaMappings) {
141 pandaBoards[pbi++] = new PandaDriver(pm.ip, glucose.model, pm);
143 mappingTool = new MappingTool(glucose, pandaMappings);
144 logTime("Built PandaDriver");
147 debugUI = new DebugUI(pandaMappings);
148 overlays = new UIContext[] {
149 uiPatternA = new UIPatternDeck(lx.engine.getDeck(0), "PATTERN A", 4, 4, 140, 324),
150 new UIBlendMode(4, 332, 140, 86),
151 new UIEffects(4, 422, 140, 144),
152 new UITempo(4, 570, 140, 50),
153 new UISpeed(4, 624, 140, 50),
155 new UIPatternDeck(lx.engine.getDeck(1), "PATTERN B", width-144, 4, 140, 324),
156 uiMidi = new UIMidi(midiEngine, width-144, 332, 140, 158),
157 new UIOutput(width-144, 494, 140, 106),
159 uiCrossfader = new UICrossfader(width/2-90, height-90, 180, 86),
161 uiDebugText = new UIDebugText(148, height-138, width-304, 44),
162 uiMapping = new UIMapping(mappingTool, 4, 4, 140, 324),
164 uiMapping.setVisible(false);
165 logTime("Built overlay UI");
168 logo = loadImage("data/logo.png");
171 midX = TRAILER_WIDTH/2.;
172 midY = glucose.model.yMax/2;
173 midZ = TRAILER_DEPTH/2.;
177 eyeX = midX + eyeR*sin(eyeA);
178 eyeZ = midZ + eyeR*cos(eyeA);
180 // Add mouse scrolling event support
181 addMouseWheelListener(new java.awt.event.MouseWheelListener() {
182 public void mouseWheelMoved(java.awt.event.MouseWheelEvent mwe) {
183 mouseWheel(mwe.getWheelRotation());
186 println("Total setup: " + (millis() - startMillis) + "ms");
187 println("Hit the 'p' key to toggle Panda Board output");
191 * Core render loop and drawing functionality.
194 // Draws the simulation and the 2D UI overlay
196 color[] colors = glucose.getColors();
198 String displayMode = uiCrossfader.getDisplayMode();
199 if (displayMode == "A") {
200 colors = lx.engine.getDeck(0).getColors();
201 } else if (displayMode == "B") {
202 colors = lx.engine.getDeck(1).getColors();
205 debugUI.maskColors(colors);
218 drawBox(0, -TRAILER_HEIGHT, 0, 0, 0, 0, TRAILER_WIDTH, TRAILER_HEIGHT, TRAILER_DEPTH, TRAILER_HEIGHT/2.);
223 vertex(TRAILER_WIDTH, 0, 0);
224 vertex(TRAILER_WIDTH, 0, TRAILER_DEPTH);
225 vertex(0, 0, TRAILER_DEPTH);
228 // Draw the logo on the front of platform
233 image(logo, TRAILER_WIDTH/2/s-logo.width/2, TRAILER_HEIGHT/2/s-logo.height/2-2/s);
237 if (glucose.model.bassBox.exists) {
238 drawBassBox(glucose.model.bassBox, false);
240 for (Speaker speaker : glucose.model.speakers) {
241 drawSpeaker(speaker);
243 for (Cube c : glucose.model.cubes) {
250 for (Point p : glucose.model.points) {
251 stroke(colors[p.index]);
252 vertex(p.fx, p.fy, p.fz);
259 // Send output colors
260 color[] sendColors = glucose.getColors();
262 debugUI.maskColors(colors);
265 // Gamma correction here. Apply a cubic to the brightness
266 // for better representation of dynamic range
267 for (int i = 0; i < colors.length; ++i) {
268 float b = brightness(colors[i]) / 100.f;
271 saturation(colors[i]),
276 // TODO(mcslee): move into GLucose engine
277 for (PandaDriver p : pandaBoards) {
282 void drawBassBox(BassBox b, boolean hasSub) {
290 translate(b.x + BassBox.EDGE_WIDTH/2., b.y + BassBox.EDGE_HEIGHT/2, b.z + BassBox.EDGE_DEPTH/2.);
291 box(BassBox.EDGE_WIDTH-20*in, BassBox.EDGE_HEIGHT-20*in, BassBox.EDGE_DEPTH-20*in);
297 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);
300 translate(b.x+(Cube.CHANNEL_WIDTH-in)/2., b.y + BassBox.EDGE_HEIGHT-in, b.z + BassBox.EDGE_DEPTH/2.);
301 float lastOffset = 0;
302 for (float offset : BoothFloor.STRIP_OFFSETS) {
303 translate(offset - lastOffset, 0, 0);
304 box(Cube.CHANNEL_WIDTH-in, 0, BassBox.EDGE_DEPTH - 2*in);
310 translate(b.x + (Cube.CHANNEL_WIDTH-in)/2., b.y + BassBox.EDGE_HEIGHT/2., b.z + in);
311 for (int j = 0; j < 2; ++j) {
313 for (int i = 0; i < BassBox.NUM_FRONT_STRUTS; ++i) {
314 translate(BassBox.FRONT_STRUT_SPACING, 0, 0);
315 box(Cube.CHANNEL_WIDTH-in, BassBox.EDGE_HEIGHT - in*2, 0);
318 translate(0, 0, BassBox.EDGE_DEPTH - 2*in);
323 translate(b.x + in, b.y + BassBox.EDGE_HEIGHT/2., b.z + BassBox.SIDE_STRUT_SPACING + (Cube.CHANNEL_WIDTH-in)/2.);
324 box(0, BassBox.EDGE_HEIGHT - in*2, Cube.CHANNEL_WIDTH-in);
325 translate(BassBox.EDGE_WIDTH-2*in, 0, 0);
326 box(0, BassBox.EDGE_HEIGHT - in*2, Cube.CHANNEL_WIDTH-in);
331 void drawCube(Cube c) {
335 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);
338 void drawSpeaker(Speaker s) {
344 translate(s.x, s.y, s.z);
345 rotate(s.ry / 180. * PI, 0, -1, 0);
346 translate(Speaker.EDGE_WIDTH/2., Speaker.EDGE_HEIGHT/2., Speaker.EDGE_DEPTH/2.);
347 box(Speaker.EDGE_WIDTH-20*in, Speaker.EDGE_HEIGHT-20*in, Speaker.EDGE_DEPTH-20*in);
348 translate(0, Speaker.EDGE_HEIGHT/2. + Speaker.EDGE_HEIGHT*.8/2, 0);
351 box(Speaker.EDGE_WIDTH*.6, Speaker.EDGE_HEIGHT*.8, Speaker.EDGE_DEPTH*.75);
356 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);
359 void drawBox(float x, float y, float z, float rx, float ry, float rz, float xd, float yd, float zd, float sw) {
362 rotate(rx / 180. * PI, -1, 0, 0);
363 rotate(ry / 180. * PI, 0, -1, 0);
364 rotate(rz / 180. * PI, 0, 0, -1);
365 for (int i = 0; i < 4; ++i) {
366 float wid = (i % 2 == 0) ? xd : zd;
372 vertex(wid - sw, yd);
373 vertex(wid - sw, sw);
379 vertex(wid - sw, yd);
380 vertex(wid - sw, yd - sw);
385 translate(wid, 0, 0);
386 rotate(HALF_PI, 0, -1, 0);
393 javax.media.opengl.GL gl = ((PGraphicsOpenGL)g).beginGL();
394 gl.glClear(javax.media.opengl.GL.GL_DEPTH_BUFFER_BIT);
395 ((PGraphicsOpenGL)g).endGL();
399 for (UIContext context : overlays) {
404 // Always draw FPS meter
407 textAlign(LEFT, BASELINE);
408 text("FPS: " + ((int) (frameRate*10)) / 10. + " / " + targetFramerate + " (-/+)", 4, height-4);
417 * Top-level keyboard event handling
421 mappingTool.keyPressed(uiMapping);
426 frameRate(--targetFramerate);
430 frameRate(++targetFramerate);
436 if (!midiEngine.isQwertyEnabled()) {
437 debugMode = !debugMode;
438 println("Debug output: " + (debugMode ? "ON" : "OFF"));
442 if (!midiEngine.isQwertyEnabled()) {
443 mappingMode = !mappingMode;
444 uiPatternA.setVisible(!mappingMode);
445 uiMapping.setVisible(mappingMode);
447 restoreToPattern = lx.getPattern();
448 lx.setPatterns(new LXPattern[] { mappingTool });
450 lx.setPatterns(patterns);
451 LXTransition pop = restoreToPattern.getTransition();
452 restoreToPattern.setTransition(null);
453 lx.goPattern(restoreToPattern);
454 restoreToPattern.setTransition(pop);
459 for (PandaDriver p : pandaBoards) {
464 if (!midiEngine.isQwertyEnabled()) {
472 * Top-level mouse event handling
475 void mousePressed() {
476 boolean debugged = false;
478 debugged = debugUI.mousePressed();
481 for (UIContext context : overlays) {
482 context.mousePressed(mouseX, mouseY);
489 void mouseDragged() {
490 boolean dragged = false;
491 for (UIContext context : overlays) {
492 dragged |= context.mouseDragged(mouseX, mouseY);
495 int dx = mouseX - mx;
496 int dy = mouseY - my;
500 eyeX = midX + eyeR*sin(eyeA);
501 eyeZ = midZ + eyeR*cos(eyeA);
506 void mouseReleased() {
507 for (UIContext context : overlays) {
508 context.mouseReleased(mouseX, mouseY);
512 void mouseWheel(int delta) {delta*=20;
513 boolean wheeled = false;
514 for (UIContext context : overlays) {
515 wheeled |= context.mouseWheel(mouseX, mouseY, delta);
519 eyeR = constrain(eyeR - delta, -500, -80);
520 eyeX = midX + eyeR*sin(eyeA);
521 eyeZ = midZ + eyeR*cos(eyeA);