Remove select() from UIScrollList, just mouse events
[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
MS
43
44int startMillis, lastMillis;
45GLucose glucose;
46HeronLX lx;
bf551144 47MappingTool mappingTool;
49815cc0
MS
48LXPattern[] patterns;
49LXTransition[] transitions;
50LXEffect[] effects;
79ae8245 51PandaDriver[] pandaBoards;
bf551144 52boolean mappingMode = false;
cc9fcf4b 53boolean debugMode = false;
554e38ff 54DebugUI debugUI;
d626bc9b
MS
55String displayMode;
56
57UIContext[] overlays;
58UIPatternDeck uiPatternA;
59UIMapping uiMapping;
60UIDebugText uiDebugText;
cc9fcf4b 61
0a9f99cc 62// Camera variables
bda5421d 63float eyeR, eyeA, eyeX, eyeY, eyeZ, midX, midY, midZ;
0a9f99cc 64
d626bc9b
MS
65LXPattern[] _patterns(GLucose glucose) {
66 LXPattern[] patterns = patterns(glucose);
67 for (LXPattern p : patterns) {
68 p.setTransition(new DissolveTransition(glucose.lx).setDuration(1000));
69 }
70 return patterns;
71}
72
49815cc0
MS
73void setup() {
74 startMillis = lastMillis = millis();
75
76 // Initialize the Processing graphics environment
77 size(VIEWPORT_WIDTH, VIEWPORT_HEIGHT, OPENGL);
0e3c5542 78 frameRate(targetFramerate);
3f8be614 79 noSmooth();
49815cc0
MS
80 // hint(ENABLE_OPENGL_4X_SMOOTH); // no discernable improvement?
81 logTime("Created viewport");
82
83 // Create the GLucose engine to run the cubes
186bc4d3 84 glucose = new GLucose(this, buildModel());
49815cc0 85 lx = glucose.lx;
cc9fcf4b 86 lx.enableKeyboardTempo();
49815cc0
MS
87 logTime("Built GLucose engine");
88
89 // Set the patterns
d626bc9b
MS
90 Engine engine = lx.engine;
91 glucose.setTransitions(transitions = transitions(glucose));
92 logTime("Built transitions");
93 engine.setPatterns(patterns = _patterns(glucose));
94 engine.addDeck(_patterns(glucose));
95 engine.getDeck(1).setBlendTransition(transitions[0]);
49815cc0
MS
96 logTime("Built patterns");
97 glucose.lx.addEffects(effects = effects(glucose));
98 logTime("Built effects");
e73ef85d
MS
99
100 // Build output driver
186bc4d3 101 PandaMapping[] pandaMappings = buildPandaList();
45f43cc2
MS
102 pandaBoards = new PandaDriver[pandaMappings.length];
103 int pbi = 0;
104 for (PandaMapping pm : pandaMappings) {
44b8de9c 105 pandaBoards[pbi++] = new PandaDriver(pm.ip, glucose.model, pm);
45f43cc2
MS
106 }
107 mappingTool = new MappingTool(glucose, pandaMappings);
108 logTime("Built PandaDriver");
49815cc0
MS
109
110 // Build overlay UI
45f43cc2 111 debugUI = new DebugUI(pandaMappings);
d626bc9b
MS
112 overlays = new UIContext[] {
113 uiPatternA = new UIPatternDeck(lx.engine.getDeck(0), "PATTERN A", 4, 4, 140, 344),
fe30b226 114 new UICrossfader(4, 352, 140, 212),
d626bc9b
MS
115
116 new UIPatternDeck(lx.engine.getDeck(1), "PATTERN B", width-144, 4, 140, 344),
117 new UIEffects(width-144, 352, 140, 144),
118 new UITempo(width-144, 498, 140, 50),
1f42cce7 119 new UIOutput(width-144, 552, 140, 106),
d626bc9b
MS
120
121 uiDebugText = new UIDebugText(4, height-64, width-8, 44),
122 uiMapping = new UIMapping(mappingTool, 4, 4, 140, 344),
123 };
124 uiMapping.setVisible(false);
49815cc0 125 logTime("Built overlay UI");
1ecdb44a 126
49815cc0 127 // MIDI devices
cc9fcf4b
MS
128 for (MidiInputDevice d : RWMidi.getInputDevices()) {
129 d.createInput(this);
130 }
131 SCMidiDevices.initializeStandardDevices(glucose);
3f8be614 132 logTime("Setup MIDI devices");
554e38ff 133
0a9f99cc 134 // Setup camera
d626bc9b 135 midX = TRAILER_WIDTH/2.;
0a9f99cc 136 midY = glucose.model.yMax/2;
e0cea600
MS
137 midZ = TRAILER_DEPTH/2.;
138 eyeR = -290;
0a9f99cc 139 eyeA = .15;
d626bc9b 140 eyeY = midY + 70;
0a9f99cc
MS
141 eyeX = midX + eyeR*sin(eyeA);
142 eyeZ = midZ + eyeR*cos(eyeA);
bda5421d
MS
143 addMouseWheelListener(new java.awt.event.MouseWheelListener() {
144 public void mouseWheelMoved(java.awt.event.MouseWheelEvent mwe) {
145 mouseWheel(mwe.getWheelRotation());
146 }});
147
49815cc0 148 println("Total setup: " + (millis() - startMillis) + "ms");
e73ef85d 149 println("Hit the 'p' key to toggle Panda Board output");
49815cc0
MS
150}
151
e28f168c 152
cc9fcf4b
MS
153void controllerChangeReceived(rwmidi.Controller cc) {
154 if (debugMode) {
155 println("CC: " + cc.toString());
156 }
157}
158
159void noteOnReceived(Note note) {
160 if (debugMode) {
161 println("Note On: " + note.toString());
162 }
163}
164
165void noteOffReceived(Note note) {
166 if (debugMode) {
167 println("Note Off: " + note.toString());
168 }
169}
170
49815cc0
MS
171void logTime(String evt) {
172 int now = millis();
173 println(evt + ": " + (now - lastMillis) + "ms");
174 lastMillis = now;
175}
176
177void draw() {
0a9f99cc
MS
178 // Draws the simulation and the 2D UI overlay
179 background(40);
d626bc9b
MS
180 color[] colors = glucose.getColors();;
181 if (displayMode == "A") {
182 colors = lx.engine.getDeck(0).getColors();
183 } else if (displayMode == "B") {
184 colors = lx.engine.getDeck(1).getColors();
185 }
554e38ff
MS
186 if (debugMode) {
187 debugUI.maskColors(colors);
188 }
51d0d59a 189
0a9f99cc
MS
190 camera(
191 eyeX, eyeY, eyeZ,
192 midX, midY, midZ,
193 0, -1, 0
194 );
51d0d59a 195
d626bc9b
MS
196 translate(0, 10, 0);
197
51d0d59a
MS
198 noStroke();
199 fill(#141414);
87998ff3 200 drawBox(0, -TRAILER_HEIGHT, 0, 0, 0, 0, TRAILER_WIDTH, TRAILER_HEIGHT, TRAILER_DEPTH, TRAILER_HEIGHT/2.);
51d0d59a
MS
201 fill(#070707);
202 stroke(#222222);
0a9f99cc 203 beginShape();
51d0d59a 204 vertex(0, 0, 0);
87998ff3
MS
205 vertex(TRAILER_WIDTH, 0, 0);
206 vertex(TRAILER_WIDTH, 0, TRAILER_DEPTH);
207 vertex(0, 0, TRAILER_DEPTH);
51d0d59a 208 endShape();
0a9f99cc 209
51d0d59a 210 noStroke();
ab77005f
MS
211// drawBassBox(glucose.model.bassBox);
212// for (Speaker s : glucose.model.speakers) {
213// drawSpeaker(s);
214// }
51d0d59a
MS
215 for (Cube c : glucose.model.cubes) {
216 drawCube(c);
217 }
218
0a9f99cc
MS
219 noFill();
220 strokeWeight(2);
221 beginShape(POINTS);
922def98
MS
222 // TODO(mcslee): restore when bassBox/speakers are right again
223 // for (Point p : glucose.model.points) {
224 for (Cube cube : glucose.model.cubes) {
225 for (Point p : cube.points) {
226 stroke(colors[p.index]);
227 vertex(p.fx, p.fy, p.fz);
228 }
0a9f99cc
MS
229 }
230 endShape();
231
d626bc9b 232 // 2D Overlay UI
0a9f99cc 233 drawUI();
d626bc9b
MS
234
235 // Send output colors
236 color[] sendColors = glucose.getColors();
554e38ff 237 if (debugMode) {
d626bc9b 238 debugUI.maskColors(colors);
554e38ff 239 }
a797d019 240
6702151a
MS
241 // Gamma correction here. Apply a cubic to the brightness
242 // for better representation of dynamic range
243 for (int i = 0; i < colors.length; ++i) {
244 float b = brightness(colors[i]) / 100.f;
245 colors[i] = color(
246 hue(colors[i]),
247 saturation(colors[i]),
248 (b*b*b) * 100.
249 );
250 }
e7f14d4d 251
e73ef85d 252 // TODO(mcslee): move into GLucose engine
79ae8245 253 for (PandaDriver p : pandaBoards) {
b58e5a1d 254 p.send(colors);
e73ef85d 255 }
49815cc0
MS
256}
257
92c06c97
MS
258void drawBassBox(BassBox b) {
259 float in = .15;
e76480d4
MS
260
261 noStroke();
262 fill(#191919);
263 pushMatrix();
264 translate(b.x + BassBox.EDGE_WIDTH/2., b.y + BassBox.EDGE_HEIGHT/2, b.z + BassBox.EDGE_DEPTH/2.);
265 box(BassBox.EDGE_WIDTH-20*in, BassBox.EDGE_HEIGHT-20*in, BassBox.EDGE_DEPTH-20*in);
266 popMatrix();
267
268 noStroke();
269 fill(#393939);
92c06c97
MS
270 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);
271
39011e7e
MS
272 pushMatrix();
273 translate(b.x+(Cube.CHANNEL_WIDTH-in)/2., b.y + BassBox.EDGE_HEIGHT-in, b.z + BassBox.EDGE_DEPTH/2.);
274 float lastOffset = 0;
275 for (float offset : BoothFloor.STRIP_OFFSETS) {
276 translate(offset - lastOffset, 0, 0);
277 box(Cube.CHANNEL_WIDTH-in, 0, BassBox.EDGE_DEPTH - 2*in);
278 lastOffset = offset;
279 }
280 popMatrix();
281
92c06c97
MS
282 pushMatrix();
283 translate(b.x + (Cube.CHANNEL_WIDTH-in)/2., b.y + BassBox.EDGE_HEIGHT/2., b.z + in);
284 for (int j = 0; j < 2; ++j) {
285 pushMatrix();
286 for (int i = 0; i < BassBox.NUM_FRONT_STRUTS; ++i) {
287 translate(BassBox.FRONT_STRUT_SPACING, 0, 0);
288 box(Cube.CHANNEL_WIDTH-in, BassBox.EDGE_HEIGHT - in*2, 0);
289 }
290 popMatrix();
291 translate(0, 0, BassBox.EDGE_DEPTH - 2*in);
292 }
293 popMatrix();
294
295 pushMatrix();
296 translate(b.x + in, b.y + BassBox.EDGE_HEIGHT/2., b.z + BassBox.SIDE_STRUT_SPACING + (Cube.CHANNEL_WIDTH-in)/2.);
297 box(0, BassBox.EDGE_HEIGHT - in*2, Cube.CHANNEL_WIDTH-in);
298 translate(BassBox.EDGE_WIDTH-2*in, 0, 0);
299 box(0, BassBox.EDGE_HEIGHT - in*2, Cube.CHANNEL_WIDTH-in);
ab77005f 300 popMatrix();
e76480d4 301
92c06c97
MS
302}
303
51d0d59a 304void drawCube(Cube c) {
254d34c0 305 float in = .15;
e76480d4
MS
306 noStroke();
307 fill(#393939);
254d34c0 308 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
309}
310
e76480d4
MS
311void drawSpeaker(Speaker s) {
312 float in = .15;
313
314 noStroke();
315 fill(#191919);
316 pushMatrix();
317 translate(s.x, s.y, s.z);
318 rotate(s.ry / 180. * PI, 0, -1, 0);
319 translate(Speaker.EDGE_WIDTH/2., Speaker.EDGE_HEIGHT/2., Speaker.EDGE_DEPTH/2.);
320 box(Speaker.EDGE_WIDTH-20*in, Speaker.EDGE_HEIGHT-20*in, Speaker.EDGE_DEPTH-20*in);
254fbb68
MS
321 translate(0, Speaker.EDGE_HEIGHT/2. + Speaker.EDGE_HEIGHT*.8/2, 0);
322
323 fill(#222222);
324 box(Speaker.EDGE_WIDTH*.6, Speaker.EDGE_HEIGHT*.8, Speaker.EDGE_DEPTH*.75);
e76480d4
MS
325 popMatrix();
326
e76480d4
MS
327 noStroke();
328 fill(#393939);
329 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 330}
e76480d4 331
51d0d59a
MS
332void drawBox(float x, float y, float z, float rx, float ry, float rz, float xd, float yd, float zd, float sw) {
333 pushMatrix();
334 translate(x, y, z);
e0cea600 335 rotate(rx / 180. * PI, -1, 0, 0);
51d0d59a 336 rotate(ry / 180. * PI, 0, -1, 0);
e0cea600 337 rotate(rz / 180. * PI, 0, 0, -1);
51d0d59a
MS
338 for (int i = 0; i < 4; ++i) {
339 float wid = (i % 2 == 0) ? xd : zd;
340
341 beginShape();
342 vertex(0, 0);
343 vertex(wid, 0);
344 vertex(wid, yd);
345 vertex(wid - sw, yd);
346 vertex(wid - sw, sw);
347 vertex(0, sw);
348 endShape();
349 beginShape();
350 vertex(0, sw);
351 vertex(0, yd);
352 vertex(wid - sw, yd);
353 vertex(wid - sw, yd - sw);
354 vertex(sw, yd - sw);
355 vertex(sw, sw);
356 endShape();
357
358 translate(wid, 0, 0);
359 rotate(HALF_PI, 0, -1, 0);
360 }
361 popMatrix();
362}
363
49815cc0 364void drawUI() {
d626bc9b
MS
365 camera();
366 javax.media.opengl.GL gl = ((PGraphicsOpenGL)g).beginGL();
367 gl.glClear(javax.media.opengl.GL.GL_DEPTH_BUFFER_BIT);
368 ((PGraphicsOpenGL)g).endGL();
369 strokeWeight(1);
370
49815cc0 371 if (uiOn) {
d626bc9b
MS
372 for (UIContext context : overlays) {
373 context.draw();
374 }
375 }
376
377 // Always draw FPS meter
378 fill(#555555);
379 textSize(9);
380 textAlign(LEFT, BASELINE);
381 text("FPS: " + ((int) (frameRate*10)) / 10. + " / " + targetFramerate + " (-/+)", 4, height-4);
382
383 if (debugMode) {
384 debugUI.draw();
49815cc0 385 }
49815cc0
MS
386}
387
388boolean uiOn = true;
d626bc9b 389LXPattern restoreToPattern = null;
ab77005f 390
49815cc0 391void keyPressed() {
bf551144 392 if (mappingMode) {
d626bc9b 393 mappingTool.keyPressed(uiMapping);
bf551144 394 }
3f8be614 395 switch (key) {
0e3c5542
MS
396 case '-':
397 case '_':
398 frameRate(--targetFramerate);
399 break;
400 case '=':
401 case '+':
402 frameRate(++targetFramerate);
403 break;
cc9fcf4b
MS
404 case 'd':
405 debugMode = !debugMode;
406 println("Debug output: " + (debugMode ? "ON" : "OFF"));
554e38ff 407 break;
bf551144
MS
408 case 'm':
409 mappingMode = !mappingMode;
d626bc9b
MS
410 uiPatternA.setVisible(!mappingMode);
411 uiMapping.setVisible(mappingMode);
bf551144 412 if (mappingMode) {
d626bc9b 413 restoreToPattern = lx.getPattern();
bf551144
MS
414 lx.setPatterns(new LXPattern[] { mappingTool });
415 } else {
bf551144 416 lx.setPatterns(patterns);
d626bc9b 417 lx.goPattern(restoreToPattern);
bf551144
MS
418 }
419 break;
e73ef85d 420 case 'p':
79ae8245
MS
421 for (PandaDriver p : pandaBoards) {
422 p.toggle();
423 }
cc9fcf4b 424 break;
3f8be614
MS
425 case 'u':
426 uiOn = !uiOn;
427 break;
49815cc0
MS
428 }
429}
430
0a9f99cc 431int mx, my;
0a9f99cc 432void mousePressed() {
d626bc9b
MS
433 boolean debugged = false;
434 if (debugMode) {
435 debugged = debugUI.mousePressed();
436 }
437 if (!debugged) {
438 for (UIContext context : overlays) {
439 context.mousePressed(mouseX, mouseY);
440 }
0a9f99cc 441 }
d626bc9b
MS
442 mx = mouseX;
443 my = mouseY;
0a9f99cc
MS
444}
445
446void mouseDragged() {
d626bc9b
MS
447 boolean dragged = false;
448 for (UIContext context : overlays) {
449 dragged |= context.mouseDragged(mouseX, mouseY);
450 }
451 if (!dragged) {
0a9f99cc
MS
452 int dx = mouseX - mx;
453 int dy = mouseY - my;
454 mx = mouseX;
455 my = mouseY;
456 eyeA += dx*.003;
457 eyeX = midX + eyeR*sin(eyeA);
458 eyeZ = midZ + eyeR*cos(eyeA);
459 eyeY += dy;
460 }
461}
462
463void mouseReleased() {
d626bc9b
MS
464 for (UIContext context : overlays) {
465 context.mouseReleased(mouseX, mouseY);
466 }
467
468 // ui.mouseReleased();
0a9f99cc 469}
bda5421d
MS
470
471void mouseWheel(int delta) {
d626bc9b
MS
472 boolean wheeled = false;
473 for (UIContext context : overlays) {
474 wheeled |= context.mouseWheel(mouseX, mouseY, delta);
475 }
476
477 if (!wheeled) {
0ba6ac44
MS
478 eyeR = constrain(eyeR - delta, -500, -80);
479 eyeX = midX + eyeR*sin(eyeA);
480 eyeZ = midZ + eyeR*cos(eyeA);
481 }
bda5421d 482}