more dpat fixes
[SugarCubes.git] / _Internals.pde
CommitLineData
49815cc0 1/**
1ecdb44a
MS
2 * DOUBLE BLACK DIAMOND DOUBLE BLACK DIAMOND
3 *
4 * //\\ //\\ //\\ //\\
5 * ///\\\ ///\\\ ///\\\ ///\\\
6 * \\\/// \\\/// \\\/// \\\///
7 * \\// \\// \\// \\//
8 *
9 * EXPERTS ONLY!! EXPERTS ONLY!!
10 *
49815cc0
MS
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.
14 */
15
16import glucose.*;
17import glucose.control.*;
3f8be614 18import glucose.effect.*;
f3f5a876 19import glucose.model.*;
49815cc0 20import glucose.pattern.*;
f3f5a876 21import glucose.transform.*;
49815cc0 22import glucose.transition.*;
49815cc0 23import heronarts.lx.*;
3f8be614 24import heronarts.lx.control.*;
49815cc0 25import heronarts.lx.effect.*;
49815cc0 26import heronarts.lx.modulator.*;
f3f5a876 27import heronarts.lx.pattern.*;
49815cc0
MS
28import heronarts.lx.transition.*;
29import ddf.minim.*;
30import ddf.minim.analysis.*;
31import processing.opengl.*;
5d70e4d7 32import rwmidi.*;
49815cc0
MS
33
34final int VIEWPORT_WIDTH = 900;
35final int VIEWPORT_HEIGHT = 700;
0e3c5542 36
92c06c97 37// The trailer is measured from the outside of the black metal (but not including the higher welded part on the front)
87998ff3
MS
38final float TRAILER_WIDTH = 240;
39final float TRAILER_DEPTH = 97;
40final float TRAILER_HEIGHT = 33;
41
51d0d59a 42int targetFramerate = 60;
49815cc0 43int startMillis, lastMillis;
a898d79b
MS
44
45// Core engine variables
49815cc0
MS
46GLucose glucose;
47HeronLX lx;
48LXPattern[] patterns;
a898d79b 49MappingTool mappingTool;
79ae8245 50PandaDriver[] pandaBoards;
d6ac1ee8 51MidiEngine midiEngine;
a898d79b
MS
52
53// Display configuration mode
bf551144 54boolean mappingMode = false;
cc9fcf4b 55boolean debugMode = false;
554e38ff 56DebugUI debugUI;
34327c96
MS
57boolean uiOn = true;
58LXPattern restoreToPattern = null;
4c640acc 59PImage logo;
d626bc9b 60
a898d79b 61// Handles to UI objects
d626bc9b
MS
62UIContext[] overlays;
63UIPatternDeck uiPatternA;
a898d79b 64UICrossfader uiCrossfader;
a8d55ade 65UIMidi uiMidi;
d626bc9b
MS
66UIMapping uiMapping;
67UIDebugText uiDebugText;
cc9fcf4b 68
0a9f99cc 69// Camera variables
bda5421d 70float eyeR, eyeA, eyeX, eyeY, eyeZ, midX, midY, midZ;
0a9f99cc 71
34327c96
MS
72/**
73 * Engine construction and initialization.
74 */
d626bc9b
MS
75LXPattern[] _patterns(GLucose glucose) {
76 LXPattern[] patterns = patterns(glucose);
77 for (LXPattern p : patterns) {
78 p.setTransition(new DissolveTransition(glucose.lx).setDuration(1000));
79 }
80 return patterns;
81}
82
34327c96
MS
83void logTime(String evt) {
84 int now = millis();
85 println(evt + ": " + (now - lastMillis) + "ms");
86 lastMillis = now;
87}
88
49815cc0
MS
89void setup() {
90 startMillis = lastMillis = millis();
91
92 // Initialize the Processing graphics environment
93 size(VIEWPORT_WIDTH, VIEWPORT_HEIGHT, OPENGL);
0e3c5542 94 frameRate(targetFramerate);
3f8be614 95 noSmooth();
49815cc0
MS
96 // hint(ENABLE_OPENGL_4X_SMOOTH); // no discernable improvement?
97 logTime("Created viewport");
98
99 // Create the GLucose engine to run the cubes
186bc4d3 100 glucose = new GLucose(this, buildModel());
49815cc0 101 lx = glucose.lx;
cc9fcf4b 102 lx.enableKeyboardTempo();
49815cc0
MS
103 logTime("Built GLucose engine");
104
2e45d1c9
MS
105 // MIDI devices
106 midiEngine = new MidiEngine();
107 logTime("Setup MIDI devices");
108
49815cc0 109 // Set the patterns
d626bc9b 110 Engine engine = lx.engine;
d626bc9b
MS
111 engine.setPatterns(patterns = _patterns(glucose));
112 engine.addDeck(_patterns(glucose));
49815cc0 113 logTime("Built patterns");
a898d79b
MS
114 glucose.setTransitions(transitions(glucose));
115 logTime("Built transitions");
116 glucose.lx.addEffects(effects(glucose));
49815cc0 117 logTime("Built effects");
e73ef85d
MS
118
119 // Build output driver
186bc4d3 120 PandaMapping[] pandaMappings = buildPandaList();
45f43cc2
MS
121 pandaBoards = new PandaDriver[pandaMappings.length];
122 int pbi = 0;
123 for (PandaMapping pm : pandaMappings) {
44b8de9c 124 pandaBoards[pbi++] = new PandaDriver(pm.ip, glucose.model, pm);
45f43cc2
MS
125 }
126 mappingTool = new MappingTool(glucose, pandaMappings);
127 logTime("Built PandaDriver");
a8d55ade 128
49815cc0 129 // Build overlay UI
45f43cc2 130 debugUI = new DebugUI(pandaMappings);
d626bc9b 131 overlays = new UIContext[] {
a8d55ade
MS
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),
137
138 new UIPatternDeck(lx.engine.getDeck(1), "PATTERN B", width-144, 4, 140, 324),
d6ac1ee8 139 uiMidi = new UIMidi(midiEngine, width-144, 332, 140, 158),
4df91daf 140 new UIOutput(width-144, 494, 140, 106),
d626bc9b 141
a8d55ade 142 uiCrossfader = new UICrossfader(width/2-90, height-90, 180, 86),
d626bc9b 143
a8d55ade
MS
144 uiDebugText = new UIDebugText(148, height-138, width-304, 44),
145 uiMapping = new UIMapping(mappingTool, 4, 4, 140, 324),
d626bc9b
MS
146 };
147 uiMapping.setVisible(false);
49815cc0 148 logTime("Built overlay UI");
4c640acc
MS
149
150 // Load logo image
151 logo = loadImage("data/logo.png");
152
0a9f99cc 153 // Setup camera
d626bc9b 154 midX = TRAILER_WIDTH/2.;
0a9f99cc 155 midY = glucose.model.yMax/2;
e0cea600
MS
156 midZ = TRAILER_DEPTH/2.;
157 eyeR = -290;
0a9f99cc 158 eyeA = .15;
d626bc9b 159 eyeY = midY + 70;
0a9f99cc
MS
160 eyeX = midX + eyeR*sin(eyeA);
161 eyeZ = midZ + eyeR*cos(eyeA);
d6ac1ee8
MS
162
163 // Add mouse scrolling event support
bda5421d
MS
164 addMouseWheelListener(new java.awt.event.MouseWheelListener() {
165 public void mouseWheelMoved(java.awt.event.MouseWheelEvent mwe) {
166 mouseWheel(mwe.getWheelRotation());
167 }});
168
49815cc0 169 println("Total setup: " + (millis() - startMillis) + "ms");
e73ef85d 170 println("Hit the 'p' key to toggle Panda Board output");
49815cc0
MS
171}
172
34327c96
MS
173/**
174 * Core render loop and drawing functionality.
175 */
49815cc0 176void draw() {
0a9f99cc
MS
177 // Draws the simulation and the 2D UI overlay
178 background(40);
a898d79b
MS
179 color[] colors = glucose.getColors();
180
181 String displayMode = uiCrossfader.getDisplayMode();
d626bc9b
MS
182 if (displayMode == "A") {
183 colors = lx.engine.getDeck(0).getColors();
184 } else if (displayMode == "B") {
185 colors = lx.engine.getDeck(1).getColors();
186 }
554e38ff
MS
187 if (debugMode) {
188 debugUI.maskColors(colors);
189 }
51d0d59a 190
0a9f99cc
MS
191 camera(
192 eyeX, eyeY, eyeZ,
193 midX, midY, midZ,
194 0, -1, 0
195 );
51d0d59a 196
a8d55ade 197 translate(0, 40, 0);
d626bc9b 198
51d0d59a
MS
199 noStroke();
200 fill(#141414);
87998ff3 201 drawBox(0, -TRAILER_HEIGHT, 0, 0, 0, 0, TRAILER_WIDTH, TRAILER_HEIGHT, TRAILER_DEPTH, TRAILER_HEIGHT/2.);
51d0d59a
MS
202 fill(#070707);
203 stroke(#222222);
0a9f99cc 204 beginShape();
51d0d59a 205 vertex(0, 0, 0);
87998ff3
MS
206 vertex(TRAILER_WIDTH, 0, 0);
207 vertex(TRAILER_WIDTH, 0, TRAILER_DEPTH);
208 vertex(0, 0, TRAILER_DEPTH);
51d0d59a 209 endShape();
4c640acc
MS
210
211 // Draw the logo on the front of platform
212 pushMatrix();
213 translate(0, 0, -1);
214 float s = .07;
215 scale(s, -s, s);
216 image(logo, TRAILER_WIDTH/2/s-logo.width/2, TRAILER_HEIGHT/2/s-logo.height/2-2/s);
217 popMatrix();
0a9f99cc 218
51d0d59a 219 noStroke();
e89eda9f
MS
220 if (glucose.model.bassBox.exists) {
221 drawBassBox(glucose.model.bassBox, false);
222 }
223 for (Speaker speaker : glucose.model.speakers) {
224 drawSpeaker(speaker);
225 }
51d0d59a
MS
226 for (Cube c : glucose.model.cubes) {
227 drawCube(c);
228 }
229
0a9f99cc
MS
230 noFill();
231 strokeWeight(2);
232 beginShape(POINTS);
e89eda9f
MS
233 for (Point p : glucose.model.points) {
234 stroke(colors[p.index]);
235 vertex(p.fx, p.fy, p.fz);
0a9f99cc
MS
236 }
237 endShape();
238
d626bc9b 239 // 2D Overlay UI
0a9f99cc 240 drawUI();
d626bc9b
MS
241
242 // Send output colors
243 color[] sendColors = glucose.getColors();
554e38ff 244 if (debugMode) {
d626bc9b 245 debugUI.maskColors(colors);
554e38ff 246 }
a797d019 247
6702151a
MS
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;
252 colors[i] = color(
253 hue(colors[i]),
254 saturation(colors[i]),
255 (b*b*b) * 100.
256 );
257 }
e7f14d4d 258
e73ef85d 259 // TODO(mcslee): move into GLucose engine
79ae8245 260 for (PandaDriver p : pandaBoards) {
b58e5a1d 261 p.send(colors);
e73ef85d 262 }
49815cc0
MS
263}
264
e89eda9f
MS
265void drawBassBox(BassBox b, boolean hasSub) {
266
92c06c97 267 float in = .15;
e89eda9f
MS
268
269 if (hasSub) {
270 noStroke();
271 fill(#191919);
272 pushMatrix();
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);
275 popMatrix();
276 }
e76480d4
MS
277
278 noStroke();
279 fill(#393939);
92c06c97
MS
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);
281
39011e7e
MS
282 pushMatrix();
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);
288 lastOffset = offset;
289 }
290 popMatrix();
291
92c06c97
MS
292 pushMatrix();
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) {
295 pushMatrix();
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);
299 }
300 popMatrix();
301 translate(0, 0, BassBox.EDGE_DEPTH - 2*in);
302 }
303 popMatrix();
304
305 pushMatrix();
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);
ab77005f 310 popMatrix();
e76480d4 311
92c06c97
MS
312}
313
51d0d59a 314void drawCube(Cube c) {
254d34c0 315 float in = .15;
e76480d4
MS
316 noStroke();
317 fill(#393939);
254d34c0 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);
51d0d59a
MS
319}
320
e76480d4
MS
321void drawSpeaker(Speaker s) {
322 float in = .15;
323
324 noStroke();
325 fill(#191919);
326 pushMatrix();
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);
254fbb68
MS
331 translate(0, Speaker.EDGE_HEIGHT/2. + Speaker.EDGE_HEIGHT*.8/2, 0);
332
333 fill(#222222);
334 box(Speaker.EDGE_WIDTH*.6, Speaker.EDGE_HEIGHT*.8, Speaker.EDGE_DEPTH*.75);
e76480d4
MS
335 popMatrix();
336
e76480d4
MS
337 noStroke();
338 fill(#393939);
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);
ab77005f 340}
e76480d4 341
51d0d59a
MS
342void drawBox(float x, float y, float z, float rx, float ry, float rz, float xd, float yd, float zd, float sw) {
343 pushMatrix();
344 translate(x, y, z);
e0cea600 345 rotate(rx / 180. * PI, -1, 0, 0);
51d0d59a 346 rotate(ry / 180. * PI, 0, -1, 0);
e0cea600 347 rotate(rz / 180. * PI, 0, 0, -1);
51d0d59a
MS
348 for (int i = 0; i < 4; ++i) {
349 float wid = (i % 2 == 0) ? xd : zd;
350
351 beginShape();
352 vertex(0, 0);
353 vertex(wid, 0);
354 vertex(wid, yd);
355 vertex(wid - sw, yd);
356 vertex(wid - sw, sw);
357 vertex(0, sw);
358 endShape();
359 beginShape();
360 vertex(0, sw);
361 vertex(0, yd);
362 vertex(wid - sw, yd);
363 vertex(wid - sw, yd - sw);
364 vertex(sw, yd - sw);
365 vertex(sw, sw);
366 endShape();
367
368 translate(wid, 0, 0);
369 rotate(HALF_PI, 0, -1, 0);
370 }
371 popMatrix();
372}
373
49815cc0 374void drawUI() {
d626bc9b
MS
375 camera();
376 javax.media.opengl.GL gl = ((PGraphicsOpenGL)g).beginGL();
377 gl.glClear(javax.media.opengl.GL.GL_DEPTH_BUFFER_BIT);
378 ((PGraphicsOpenGL)g).endGL();
379 strokeWeight(1);
380
49815cc0 381 if (uiOn) {
d626bc9b
MS
382 for (UIContext context : overlays) {
383 context.draw();
384 }
385 }
386
387 // Always draw FPS meter
388 fill(#555555);
389 textSize(9);
390 textAlign(LEFT, BASELINE);
391 text("FPS: " + ((int) (frameRate*10)) / 10. + " / " + targetFramerate + " (-/+)", 4, height-4);
392
393 if (debugMode) {
394 debugUI.draw();
49815cc0 395 }
49815cc0
MS
396}
397
4c640acc 398
34327c96
MS
399/**
400 * Top-level keyboard event handling
401 */
49815cc0 402void keyPressed() {
bf551144 403 if (mappingMode) {
d626bc9b 404 mappingTool.keyPressed(uiMapping);
bf551144 405 }
3f8be614 406 switch (key) {
0e3c5542
MS
407 case '-':
408 case '_':
409 frameRate(--targetFramerate);
410 break;
411 case '=':
412 case '+':
413 frameRate(++targetFramerate);
75e1ddde
MS
414 break;
415 case 'b':
3661fcee 416 EFF_boom.trigger();
75e1ddde 417 break;
cc9fcf4b 418 case 'd':
d6ac1ee8 419 if (!midiEngine.isQwertyEnabled()) {
a8d55ade
MS
420 debugMode = !debugMode;
421 println("Debug output: " + (debugMode ? "ON" : "OFF"));
422 }
554e38ff 423 break;
bf551144 424 case 'm':
d6ac1ee8 425 if (!midiEngine.isQwertyEnabled()) {
a8d55ade
MS
426 mappingMode = !mappingMode;
427 uiPatternA.setVisible(!mappingMode);
428 uiMapping.setVisible(mappingMode);
429 if (mappingMode) {
430 restoreToPattern = lx.getPattern();
431 lx.setPatterns(new LXPattern[] { mappingTool });
432 } else {
433 lx.setPatterns(patterns);
434 LXTransition pop = restoreToPattern.getTransition();
435 restoreToPattern.setTransition(null);
436 lx.goPattern(restoreToPattern);
437 restoreToPattern.setTransition(pop);
438 }
bf551144
MS
439 }
440 break;
e73ef85d 441 case 'p':
79ae8245
MS
442 for (PandaDriver p : pandaBoards) {
443 p.toggle();
444 }
cc9fcf4b 445 break;
3f8be614 446 case 'u':
d6ac1ee8 447 if (!midiEngine.isQwertyEnabled()) {
4df91daf
MS
448 uiOn = !uiOn;
449 }
3f8be614 450 break;
49815cc0
MS
451 }
452}
453
34327c96
MS
454/**
455 * Top-level mouse event handling
456 */
0a9f99cc 457int mx, my;
0a9f99cc 458void mousePressed() {
d626bc9b
MS
459 boolean debugged = false;
460 if (debugMode) {
461 debugged = debugUI.mousePressed();
462 }
463 if (!debugged) {
464 for (UIContext context : overlays) {
465 context.mousePressed(mouseX, mouseY);
466 }
0a9f99cc 467 }
d626bc9b
MS
468 mx = mouseX;
469 my = mouseY;
0a9f99cc
MS
470}
471
472void mouseDragged() {
d626bc9b
MS
473 boolean dragged = false;
474 for (UIContext context : overlays) {
475 dragged |= context.mouseDragged(mouseX, mouseY);
476 }
477 if (!dragged) {
0a9f99cc
MS
478 int dx = mouseX - mx;
479 int dy = mouseY - my;
480 mx = mouseX;
481 my = mouseY;
482 eyeA += dx*.003;
483 eyeX = midX + eyeR*sin(eyeA);
484 eyeZ = midZ + eyeR*cos(eyeA);
485 eyeY += dy;
486 }
487}
488
489void mouseReleased() {
d626bc9b
MS
490 for (UIContext context : overlays) {
491 context.mouseReleased(mouseX, mouseY);
492 }
0a9f99cc 493}
bda5421d
MS
494
495void mouseWheel(int delta) {
d626bc9b
MS
496 boolean wheeled = false;
497 for (UIContext context : overlays) {
498 wheeled |= context.mouseWheel(mouseX, mouseY, delta);
499 }
500
501 if (!wheeled) {
0ba6ac44
MS
502 eyeR = constrain(eyeR - delta, -500, -80);
503 eyeX = midX + eyeR*sin(eyeA);
504 eyeZ = midZ + eyeR*cos(eyeA);
505 }
bda5421d 506}