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.
75 LXPattern[] _patterns(GLucose glucose) {
76 LXPattern[] patterns = patterns(glucose);
77 for (LXPattern p : patterns) {
78 p.setTransition(new DissolveTransition(glucose.lx).setDuration(1000));
83 void logTime(String evt) {
85 println(evt + ": " + (now - lastMillis) + "ms");
90 startMillis = lastMillis = millis();
92 // Initialize the Processing graphics environment
93 size(VIEWPORT_WIDTH, VIEWPORT_HEIGHT, OPENGL);
94 frameRate(targetFramerate);
96 // hint(ENABLE_OPENGL_4X_SMOOTH); // no discernable improvement?
97 logTime("Created viewport");
99 // Create the GLucose engine to run the cubes
100 glucose = new GLucose(this, buildModel());
102 lx.enableKeyboardTempo();
103 logTime("Built GLucose engine");
106 midiEngine = new MidiEngine();
107 logTime("Setup MIDI devices");
110 Engine engine = lx.engine;
111 engine.setPatterns(patterns = _patterns(glucose));
112 engine.addDeck(_patterns(glucose));
113 logTime("Built patterns");
114 glucose.setTransitions(transitions(glucose));
115 logTime("Built transitions");
116 glucose.lx.addEffects(effects(glucose));
117 logTime("Built effects");
119 // Build output driver
120 PandaMapping[] pandaMappings = buildPandaList();
121 pandaBoards = new PandaDriver[pandaMappings.length];
123 for (PandaMapping pm : pandaMappings) {
124 pandaBoards[pbi++] = new PandaDriver(pm.ip, glucose.model, pm);
126 mappingTool = new MappingTool(glucose, pandaMappings);
127 logTime("Built PandaDriver");
130 debugUI = new DebugUI(pandaMappings);
131 overlays = new UIContext[] {
132 uiPatternA = new UIPatternDeck(lx.engine.getDeck(0), "PATTERN A", 4, 4, 140, 324),
133 new UIBlendMode(4, 332, 140, 86),
134 new UIEffects(4, 422, 140, 144),
135 new UITempo(4, 570, 140, 50),
136 new UISpeed(4, 624, 140, 50),
138 new UIPatternDeck(lx.engine.getDeck(1), "PATTERN B", width-144, 4, 140, 324),
139 uiMidi = new UIMidi(midiEngine, width-144, 332, 140, 158),
140 new UIOutput(width-144, 494, 140, 106),
142 uiCrossfader = new UICrossfader(width/2-90, height-90, 180, 86),
144 uiDebugText = new UIDebugText(148, height-138, width-304, 44),
145 uiMapping = new UIMapping(mappingTool, 4, 4, 140, 324),
147 uiMapping.setVisible(false);
148 logTime("Built overlay UI");
151 logo = loadImage("data/logo.png");
154 midX = TRAILER_WIDTH/2.;
155 midY = glucose.model.yMax/2;
156 midZ = TRAILER_DEPTH/2.;
160 eyeX = midX + eyeR*sin(eyeA);
161 eyeZ = midZ + eyeR*cos(eyeA);
163 // Add mouse scrolling event support
164 addMouseWheelListener(new java.awt.event.MouseWheelListener() {
165 public void mouseWheelMoved(java.awt.event.MouseWheelEvent mwe) {
166 mouseWheel(mwe.getWheelRotation());
169 println("Total setup: " + (millis() - startMillis) + "ms");
170 println("Hit the 'p' key to toggle Panda Board output");
174 * Core render loop and drawing functionality.
177 // Draws the simulation and the 2D UI overlay
179 color[] colors = glucose.getColors();
181 String displayMode = uiCrossfader.getDisplayMode();
182 if (displayMode == "A") {
183 colors = lx.engine.getDeck(0).getColors();
184 } else if (displayMode == "B") {
185 colors = lx.engine.getDeck(1).getColors();
188 debugUI.maskColors(colors);
201 drawBox(0, -TRAILER_HEIGHT, 0, 0, 0, 0, TRAILER_WIDTH, TRAILER_HEIGHT, TRAILER_DEPTH, TRAILER_HEIGHT/2.);
206 vertex(TRAILER_WIDTH, 0, 0);
207 vertex(TRAILER_WIDTH, 0, TRAILER_DEPTH);
208 vertex(0, 0, TRAILER_DEPTH);
211 // Draw the logo on the front of platform
216 image(logo, TRAILER_WIDTH/2/s-logo.width/2, TRAILER_HEIGHT/2/s-logo.height/2-2/s);
220 if (glucose.model.bassBox.exists) {
221 drawBassBox(glucose.model.bassBox, false);
223 for (Speaker speaker : glucose.model.speakers) {
224 drawSpeaker(speaker);
226 for (Cube c : glucose.model.cubes) {
233 for (Point p : glucose.model.points) {
234 stroke(colors[p.index]);
235 vertex(p.fx, p.fy, p.fz);
242 // Send output colors
243 color[] sendColors = glucose.getColors();
245 debugUI.maskColors(colors);
248 // Gamma correction here. Apply a cubic to the brightness
249 // for better representation of dynamic range
250 for (int i = 0; i < colors.length; ++i) {
251 float b = brightness(colors[i]) / 100.f;
254 saturation(colors[i]),
259 // TODO(mcslee): move into GLucose engine
260 for (PandaDriver p : pandaBoards) {
265 void drawBassBox(BassBox b, boolean hasSub) {
273 translate(b.x + BassBox.EDGE_WIDTH/2., b.y + BassBox.EDGE_HEIGHT/2, b.z + BassBox.EDGE_DEPTH/2.);
274 box(BassBox.EDGE_WIDTH-20*in, BassBox.EDGE_HEIGHT-20*in, BassBox.EDGE_DEPTH-20*in);
280 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);
283 translate(b.x+(Cube.CHANNEL_WIDTH-in)/2., b.y + BassBox.EDGE_HEIGHT-in, b.z + BassBox.EDGE_DEPTH/2.);
284 float lastOffset = 0;
285 for (float offset : BoothFloor.STRIP_OFFSETS) {
286 translate(offset - lastOffset, 0, 0);
287 box(Cube.CHANNEL_WIDTH-in, 0, BassBox.EDGE_DEPTH - 2*in);
293 translate(b.x + (Cube.CHANNEL_WIDTH-in)/2., b.y + BassBox.EDGE_HEIGHT/2., b.z + in);
294 for (int j = 0; j < 2; ++j) {
296 for (int i = 0; i < BassBox.NUM_FRONT_STRUTS; ++i) {
297 translate(BassBox.FRONT_STRUT_SPACING, 0, 0);
298 box(Cube.CHANNEL_WIDTH-in, BassBox.EDGE_HEIGHT - in*2, 0);
301 translate(0, 0, BassBox.EDGE_DEPTH - 2*in);
306 translate(b.x + in, b.y + BassBox.EDGE_HEIGHT/2., b.z + BassBox.SIDE_STRUT_SPACING + (Cube.CHANNEL_WIDTH-in)/2.);
307 box(0, BassBox.EDGE_HEIGHT - in*2, Cube.CHANNEL_WIDTH-in);
308 translate(BassBox.EDGE_WIDTH-2*in, 0, 0);
309 box(0, BassBox.EDGE_HEIGHT - in*2, Cube.CHANNEL_WIDTH-in);
314 void drawCube(Cube c) {
318 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);
321 void drawSpeaker(Speaker s) {
327 translate(s.x, s.y, s.z);
328 rotate(s.ry / 180. * PI, 0, -1, 0);
329 translate(Speaker.EDGE_WIDTH/2., Speaker.EDGE_HEIGHT/2., Speaker.EDGE_DEPTH/2.);
330 box(Speaker.EDGE_WIDTH-20*in, Speaker.EDGE_HEIGHT-20*in, Speaker.EDGE_DEPTH-20*in);
331 translate(0, Speaker.EDGE_HEIGHT/2. + Speaker.EDGE_HEIGHT*.8/2, 0);
334 box(Speaker.EDGE_WIDTH*.6, Speaker.EDGE_HEIGHT*.8, Speaker.EDGE_DEPTH*.75);
339 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);
342 void drawBox(float x, float y, float z, float rx, float ry, float rz, float xd, float yd, float zd, float sw) {
345 rotate(rx / 180. * PI, -1, 0, 0);
346 rotate(ry / 180. * PI, 0, -1, 0);
347 rotate(rz / 180. * PI, 0, 0, -1);
348 for (int i = 0; i < 4; ++i) {
349 float wid = (i % 2 == 0) ? xd : zd;
355 vertex(wid - sw, yd);
356 vertex(wid - sw, sw);
362 vertex(wid - sw, yd);
363 vertex(wid - sw, yd - sw);
368 translate(wid, 0, 0);
369 rotate(HALF_PI, 0, -1, 0);
376 javax.media.opengl.GL gl = ((PGraphicsOpenGL)g).beginGL();
377 gl.glClear(javax.media.opengl.GL.GL_DEPTH_BUFFER_BIT);
378 ((PGraphicsOpenGL)g).endGL();
382 for (UIContext context : overlays) {
387 // Always draw FPS meter
390 textAlign(LEFT, BASELINE);
391 text("FPS: " + ((int) (frameRate*10)) / 10. + " / " + targetFramerate + " (-/+)", 4, height-4);
400 * Top-level keyboard event handling
404 mappingTool.keyPressed(uiMapping);
409 frameRate(--targetFramerate);
413 frameRate(++targetFramerate);
419 if (!midiEngine.isQwertyEnabled()) {
420 debugMode = !debugMode;
421 println("Debug output: " + (debugMode ? "ON" : "OFF"));
425 if (!midiEngine.isQwertyEnabled()) {
426 mappingMode = !mappingMode;
427 uiPatternA.setVisible(!mappingMode);
428 uiMapping.setVisible(mappingMode);
430 restoreToPattern = lx.getPattern();
431 lx.setPatterns(new LXPattern[] { mappingTool });
433 lx.setPatterns(patterns);
434 LXTransition pop = restoreToPattern.getTransition();
435 restoreToPattern.setTransition(null);
436 lx.goPattern(restoreToPattern);
437 restoreToPattern.setTransition(pop);
442 for (PandaDriver p : pandaBoards) {
447 if (!midiEngine.isQwertyEnabled()) {
455 * Top-level mouse event handling
458 void mousePressed() {
459 boolean debugged = false;
461 debugged = debugUI.mousePressed();
464 for (UIContext context : overlays) {
465 context.mousePressed(mouseX, mouseY);
472 void mouseDragged() {
473 boolean dragged = false;
474 for (UIContext context : overlays) {
475 dragged |= context.mouseDragged(mouseX, mouseY);
478 int dx = mouseX - mx;
479 int dy = mouseY - my;
483 eyeX = midX + eyeR*sin(eyeA);
484 eyeZ = midZ + eyeR*cos(eyeA);
489 void mouseReleased() {
490 for (UIContext context : overlays) {
491 context.mouseReleased(mouseX, mouseY);
495 void mouseWheel(int delta) {
496 boolean wheeled = false;
497 for (UIContext context : overlays) {
498 wheeled |= context.mouseWheel(mouseX, mouseY, delta);
502 eyeR = constrain(eyeR - delta, -500, -80);
503 eyeX = midX + eyeR*sin(eyeA);
504 eyeZ = midZ + eyeR*cos(eyeA);