Give controllers top priority for note/controller events, so patterns can't eat them...
[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
41f26e8f 42final int MaxCubeHeight = 5;
41c436e4 43final int NumBackTowers = 11;
7e3329d2 44
51d0d59a 45int targetFramerate = 60;
49815cc0 46int startMillis, lastMillis;
a898d79b
MS
47
48// Core engine variables
49815cc0
MS
49GLucose glucose;
50HeronLX lx;
51LXPattern[] patterns;
a898d79b 52MappingTool mappingTool;
79ae8245 53PandaDriver[] pandaBoards;
e0794d3a 54PresetManager presetManager;
1f974cbc 55MidiEngine midiEngine;
a898d79b
MS
56
57// Display configuration mode
bf551144 58boolean mappingMode = false;
cc9fcf4b 59boolean debugMode = false;
554e38ff 60DebugUI debugUI;
34327c96
MS
61boolean uiOn = true;
62LXPattern restoreToPattern = null;
4c640acc 63PImage logo;
a41f334c 64float[] hsb = new float[3];
d626bc9b 65
a898d79b 66// Handles to UI objects
d626bc9b
MS
67UIContext[] overlays;
68UIPatternDeck uiPatternA;
a898d79b 69UICrossfader uiCrossfader;
a8d55ade 70UIMidi uiMidi;
d626bc9b
MS
71UIMapping uiMapping;
72UIDebugText uiDebugText;
cc9fcf4b 73
0a9f99cc 74// Camera variables
bda5421d 75float eyeR, eyeA, eyeX, eyeY, eyeZ, midX, midY, midZ;
0a9f99cc 76
34327c96
MS
77/**
78 * Engine construction and initialization.
79 */
d1dcc4b5
MS
80
81LXTransition _transition(GLucose glucose) {
82 return new DissolveTransition(glucose.lx).setDuration(1000);
83}
84
85LXPattern[] _leftPatterns(GLucose glucose) {
d626bc9b
MS
86 LXPattern[] patterns = patterns(glucose);
87 for (LXPattern p : patterns) {
d1dcc4b5 88 p.setTransition(_transition(glucose));
d626bc9b
MS
89 }
90 return patterns;
91}
92
d1dcc4b5
MS
93LXPattern[] _rightPatterns(GLucose glucose) {
94 LXPattern[] patterns = _leftPatterns(glucose);
95 LXPattern[] rightPatterns = new LXPattern[patterns.length+1];
96 int i = 0;
97 rightPatterns[i++] = new BlankPattern(glucose).setTransition(_transition(glucose));
98 for (LXPattern p : patterns) {
99 rightPatterns[i++] = p;
100 }
101 return rightPatterns;
102}
e5308763 103
d1dcc4b5 104
34327c96
MS
105void logTime(String evt) {
106 int now = millis();
107 println(evt + ": " + (now - lastMillis) + "ms");
108 lastMillis = now;
109}
110
49815cc0
MS
111void setup() {
112 startMillis = lastMillis = millis();
113
114 // Initialize the Processing graphics environment
115 size(VIEWPORT_WIDTH, VIEWPORT_HEIGHT, OPENGL);
0e3c5542 116 frameRate(targetFramerate);
3f8be614 117 noSmooth();
49815cc0
MS
118 // hint(ENABLE_OPENGL_4X_SMOOTH); // no discernable improvement?
119 logTime("Created viewport");
120
121 // Create the GLucose engine to run the cubes
186bc4d3 122 glucose = new GLucose(this, buildModel());
49815cc0 123 lx = glucose.lx;
cc9fcf4b 124 lx.enableKeyboardTempo();
49815cc0
MS
125 logTime("Built GLucose engine");
126
127 // Set the patterns
42a424d7 128 LXEngine engine = lx.engine;
d1dcc4b5
MS
129 engine.setPatterns(patterns = _leftPatterns(glucose));
130 engine.addDeck(_rightPatterns(glucose));
49815cc0 131 logTime("Built patterns");
a898d79b
MS
132 glucose.setTransitions(transitions(glucose));
133 logTime("Built transitions");
134 glucose.lx.addEffects(effects(glucose));
49815cc0 135 logTime("Built effects");
4214e9a2 136
e0794d3a
MS
137 // Preset manager
138 presetManager = new PresetManager();
139 logTime("Loaded presets");
4214e9a2 140
1f974cbc
MS
141 // MIDI devices
142 midiEngine = new MidiEngine();
1f974cbc
MS
143 logTime("Setup MIDI devices");
144
e73ef85d 145 // Build output driver
186bc4d3 146 PandaMapping[] pandaMappings = buildPandaList();
45f43cc2
MS
147 pandaBoards = new PandaDriver[pandaMappings.length];
148 int pbi = 0;
149 for (PandaMapping pm : pandaMappings) {
44b8de9c 150 pandaBoards[pbi++] = new PandaDriver(pm.ip, glucose.model, pm);
45f43cc2
MS
151 }
152 mappingTool = new MappingTool(glucose, pandaMappings);
153 logTime("Built PandaDriver");
a8d55ade 154
49815cc0 155 // Build overlay UI
45f43cc2 156 debugUI = new DebugUI(pandaMappings);
d626bc9b 157 overlays = new UIContext[] {
42a424d7 158 uiPatternA = new UIPatternDeck(lx.engine.getDeck(GLucose.LEFT_DECK), "PATTERN A", 4, 4, 140, 324),
a8d55ade
MS
159 new UIBlendMode(4, 332, 140, 86),
160 new UIEffects(4, 422, 140, 144),
161 new UITempo(4, 570, 140, 50),
162 new UISpeed(4, 624, 140, 50),
163
42a424d7 164 new UIPatternDeck(lx.engine.getDeck(GLucose.RIGHT_DECK), "PATTERN B", width-144, 4, 140, 324),
d6ac1ee8 165 uiMidi = new UIMidi(midiEngine, width-144, 332, 140, 158),
4df91daf 166 new UIOutput(width-144, 494, 140, 106),
d626bc9b 167
a8d55ade 168 uiCrossfader = new UICrossfader(width/2-90, height-90, 180, 86),
d626bc9b 169
a8d55ade
MS
170 uiDebugText = new UIDebugText(148, height-138, width-304, 44),
171 uiMapping = new UIMapping(mappingTool, 4, 4, 140, 324),
d626bc9b
MS
172 };
173 uiMapping.setVisible(false);
49815cc0 174 logTime("Built overlay UI");
4c640acc
MS
175
176 // Load logo image
177 logo = loadImage("data/logo.png");
178
0a9f99cc 179 // Setup camera
d626bc9b 180 midX = TRAILER_WIDTH/2.;
0a9f99cc 181 midY = glucose.model.yMax/2;
e0cea600
MS
182 midZ = TRAILER_DEPTH/2.;
183 eyeR = -290;
0a9f99cc 184 eyeA = .15;
d626bc9b 185 eyeY = midY + 70;
0a9f99cc
MS
186 eyeX = midX + eyeR*sin(eyeA);
187 eyeZ = midZ + eyeR*cos(eyeA);
d6ac1ee8
MS
188
189 // Add mouse scrolling event support
bda5421d
MS
190 addMouseWheelListener(new java.awt.event.MouseWheelListener() {
191 public void mouseWheelMoved(java.awt.event.MouseWheelEvent mwe) {
192 mouseWheel(mwe.getWheelRotation());
193 }});
194
49815cc0 195 println("Total setup: " + (millis() - startMillis) + "ms");
e73ef85d 196 println("Hit the 'p' key to toggle Panda Board output");
49815cc0
MS
197}
198
34327c96
MS
199/**
200 * Core render loop and drawing functionality.
201 */
49815cc0 202void draw() {
0a9f99cc
MS
203 // Draws the simulation and the 2D UI overlay
204 background(40);
a898d79b 205
19d16a16
MS
206 color[] simulationColors;
207 color[] sendColors;
208 simulationColors = sendColors = glucose.getColors();
a898d79b 209 String displayMode = uiCrossfader.getDisplayMode();
d626bc9b 210 if (displayMode == "A") {
19d16a16 211 simulationColors = lx.engine.getDeck(0).getColors();
d626bc9b 212 } else if (displayMode == "B") {
19d16a16 213 simulationColors = lx.engine.getDeck(1).getColors();
d626bc9b 214 }
554e38ff 215 if (debugMode) {
19d16a16
MS
216 debugUI.maskColors(simulationColors);
217 debugUI.maskColors(sendColors);
554e38ff 218 }
51d0d59a 219
0a9f99cc
MS
220 camera(
221 eyeX, eyeY, eyeZ,
222 midX, midY, midZ,
223 0, -1, 0
224 );
51d0d59a 225
a8d55ade 226 translate(0, 40, 0);
d626bc9b 227
51d0d59a
MS
228 noStroke();
229 fill(#141414);
87998ff3 230 drawBox(0, -TRAILER_HEIGHT, 0, 0, 0, 0, TRAILER_WIDTH, TRAILER_HEIGHT, TRAILER_DEPTH, TRAILER_HEIGHT/2.);
51d0d59a
MS
231 fill(#070707);
232 stroke(#222222);
0a9f99cc 233 beginShape();
51d0d59a 234 vertex(0, 0, 0);
87998ff3
MS
235 vertex(TRAILER_WIDTH, 0, 0);
236 vertex(TRAILER_WIDTH, 0, TRAILER_DEPTH);
237 vertex(0, 0, TRAILER_DEPTH);
51d0d59a 238 endShape();
4c640acc
MS
239
240 // Draw the logo on the front of platform
241 pushMatrix();
242 translate(0, 0, -1);
243 float s = .07;
244 scale(s, -s, s);
245 image(logo, TRAILER_WIDTH/2/s-logo.width/2, TRAILER_HEIGHT/2/s-logo.height/2-2/s);
246 popMatrix();
0a9f99cc 247
51d0d59a 248 noStroke();
e89eda9f
MS
249 if (glucose.model.bassBox.exists) {
250 drawBassBox(glucose.model.bassBox, false);
251 }
252 for (Speaker speaker : glucose.model.speakers) {
253 drawSpeaker(speaker);
254 }
51d0d59a
MS
255 for (Cube c : glucose.model.cubes) {
256 drawCube(c);
257 }
258
0a9f99cc
MS
259 noFill();
260 strokeWeight(2);
261 beginShape(POINTS);
e89eda9f 262 for (Point p : glucose.model.points) {
19d16a16 263 stroke(simulationColors[p.index]);
190d91c2 264 vertex(p.x, p.y, p.z);
0a9f99cc
MS
265 }
266 endShape();
267
d626bc9b 268 // 2D Overlay UI
0a9f99cc 269 drawUI();
d626bc9b 270
6702151a
MS
271 // Gamma correction here. Apply a cubic to the brightness
272 // for better representation of dynamic range
7f782b99 273 for (int i = 0; i < sendColors.length; ++i) {
a41f334c
MS
274 lx.RGBtoHSB(sendColors[i], hsb);
275 float b = hsb[2];
276 sendColors[i] = lx.hsb(360.*hsb[0], 100.*hsb[1], 100.*(b*b*b));
6702151a 277 }
e7f14d4d 278
e73ef85d 279 // TODO(mcslee): move into GLucose engine
79ae8245 280 for (PandaDriver p : pandaBoards) {
7f782b99 281 p.send(sendColors);
e73ef85d 282 }
49815cc0
MS
283}
284
e89eda9f
MS
285void drawBassBox(BassBox b, boolean hasSub) {
286
92c06c97 287 float in = .15;
e89eda9f
MS
288
289 if (hasSub) {
290 noStroke();
291 fill(#191919);
292 pushMatrix();
293 translate(b.x + BassBox.EDGE_WIDTH/2., b.y + BassBox.EDGE_HEIGHT/2, b.z + BassBox.EDGE_DEPTH/2.);
294 box(BassBox.EDGE_WIDTH-20*in, BassBox.EDGE_HEIGHT-20*in, BassBox.EDGE_DEPTH-20*in);
295 popMatrix();
296 }
e76480d4
MS
297
298 noStroke();
299 fill(#393939);
92c06c97
MS
300 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);
301
39011e7e
MS
302 pushMatrix();
303 translate(b.x+(Cube.CHANNEL_WIDTH-in)/2., b.y + BassBox.EDGE_HEIGHT-in, b.z + BassBox.EDGE_DEPTH/2.);
304 float lastOffset = 0;
305 for (float offset : BoothFloor.STRIP_OFFSETS) {
306 translate(offset - lastOffset, 0, 0);
307 box(Cube.CHANNEL_WIDTH-in, 0, BassBox.EDGE_DEPTH - 2*in);
308 lastOffset = offset;
309 }
310 popMatrix();
311
92c06c97
MS
312 pushMatrix();
313 translate(b.x + (Cube.CHANNEL_WIDTH-in)/2., b.y + BassBox.EDGE_HEIGHT/2., b.z + in);
314 for (int j = 0; j < 2; ++j) {
315 pushMatrix();
316 for (int i = 0; i < BassBox.NUM_FRONT_STRUTS; ++i) {
317 translate(BassBox.FRONT_STRUT_SPACING, 0, 0);
318 box(Cube.CHANNEL_WIDTH-in, BassBox.EDGE_HEIGHT - in*2, 0);
319 }
320 popMatrix();
321 translate(0, 0, BassBox.EDGE_DEPTH - 2*in);
322 }
323 popMatrix();
324
325 pushMatrix();
326 translate(b.x + in, b.y + BassBox.EDGE_HEIGHT/2., b.z + BassBox.SIDE_STRUT_SPACING + (Cube.CHANNEL_WIDTH-in)/2.);
327 box(0, BassBox.EDGE_HEIGHT - in*2, Cube.CHANNEL_WIDTH-in);
328 translate(BassBox.EDGE_WIDTH-2*in, 0, 0);
329 box(0, BassBox.EDGE_HEIGHT - in*2, Cube.CHANNEL_WIDTH-in);
ab77005f 330 popMatrix();
e76480d4 331
92c06c97
MS
332}
333
51d0d59a 334void drawCube(Cube c) {
254d34c0 335 float in = .15;
e76480d4
MS
336 noStroke();
337 fill(#393939);
254d34c0 338 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
339}
340
e76480d4
MS
341void drawSpeaker(Speaker s) {
342 float in = .15;
343
344 noStroke();
345 fill(#191919);
346 pushMatrix();
347 translate(s.x, s.y, s.z);
348 rotate(s.ry / 180. * PI, 0, -1, 0);
349 translate(Speaker.EDGE_WIDTH/2., Speaker.EDGE_HEIGHT/2., Speaker.EDGE_DEPTH/2.);
350 box(Speaker.EDGE_WIDTH-20*in, Speaker.EDGE_HEIGHT-20*in, Speaker.EDGE_DEPTH-20*in);
254fbb68
MS
351 translate(0, Speaker.EDGE_HEIGHT/2. + Speaker.EDGE_HEIGHT*.8/2, 0);
352
353 fill(#222222);
354 box(Speaker.EDGE_WIDTH*.6, Speaker.EDGE_HEIGHT*.8, Speaker.EDGE_DEPTH*.75);
e76480d4
MS
355 popMatrix();
356
e76480d4
MS
357 noStroke();
358 fill(#393939);
359 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 360}
e76480d4 361
51d0d59a
MS
362void drawBox(float x, float y, float z, float rx, float ry, float rz, float xd, float yd, float zd, float sw) {
363 pushMatrix();
364 translate(x, y, z);
e0cea600 365 rotate(rx / 180. * PI, -1, 0, 0);
51d0d59a 366 rotate(ry / 180. * PI, 0, -1, 0);
e0cea600 367 rotate(rz / 180. * PI, 0, 0, -1);
51d0d59a
MS
368 for (int i = 0; i < 4; ++i) {
369 float wid = (i % 2 == 0) ? xd : zd;
370
371 beginShape();
372 vertex(0, 0);
373 vertex(wid, 0);
374 vertex(wid, yd);
375 vertex(wid - sw, yd);
376 vertex(wid - sw, sw);
377 vertex(0, sw);
378 endShape();
379 beginShape();
380 vertex(0, sw);
381 vertex(0, yd);
382 vertex(wid - sw, yd);
383 vertex(wid - sw, yd - sw);
384 vertex(sw, yd - sw);
385 vertex(sw, sw);
386 endShape();
387
388 translate(wid, 0, 0);
389 rotate(HALF_PI, 0, -1, 0);
390 }
391 popMatrix();
392}
393
49815cc0 394void drawUI() {
d626bc9b
MS
395 camera();
396 javax.media.opengl.GL gl = ((PGraphicsOpenGL)g).beginGL();
397 gl.glClear(javax.media.opengl.GL.GL_DEPTH_BUFFER_BIT);
398 ((PGraphicsOpenGL)g).endGL();
399 strokeWeight(1);
400
49815cc0 401 if (uiOn) {
d626bc9b
MS
402 for (UIContext context : overlays) {
403 context.draw();
404 }
405 }
406
407 // Always draw FPS meter
408 fill(#555555);
409 textSize(9);
410 textAlign(LEFT, BASELINE);
411 text("FPS: " + ((int) (frameRate*10)) / 10. + " / " + targetFramerate + " (-/+)", 4, height-4);
412
413 if (debugMode) {
414 debugUI.draw();
49815cc0 415 }
49815cc0
MS
416}
417
4c640acc 418
34327c96
MS
419/**
420 * Top-level keyboard event handling
421 */
49815cc0 422void keyPressed() {
bf551144 423 if (mappingMode) {
d626bc9b 424 mappingTool.keyPressed(uiMapping);
bf551144 425 }
3f8be614 426 switch (key) {
2b068dd5
MS
427 case '1':
428 case '2':
429 case '3':
430 case '4':
431 case '5':
432 case '6':
433 case '7':
434 case '8':
435 if (!midiEngine.isQwertyEnabled()) {
436 presetManager.select(midiEngine.getFocusedDeck(), key - '1');
437 }
438 break;
439
440 case '!':
441 if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 0);
442 break;
443 case '@':
444 if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 1);
445 break;
446 case '#':
447 if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 2);
448 break;
449 case '$':
450 if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 3);
451 break;
452 case '%':
453 if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 4);
454 break;
455 case '^':
456 if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 5);
457 break;
458 case '&':
459 if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 6);
460 break;
461 case '*':
462 if (!midiEngine.isQwertyEnabled()) presetManager.store(midiEngine.getFocusedDeck(), 7);
463 break;
464
0e3c5542
MS
465 case '-':
466 case '_':
467 frameRate(--targetFramerate);
468 break;
469 case '=':
470 case '+':
471 frameRate(++targetFramerate);
75e1ddde
MS
472 break;
473 case 'b':
3661fcee 474 EFF_boom.trigger();
75e1ddde 475 break;
cc9fcf4b 476 case 'd':
d6ac1ee8 477 if (!midiEngine.isQwertyEnabled()) {
a8d55ade
MS
478 debugMode = !debugMode;
479 println("Debug output: " + (debugMode ? "ON" : "OFF"));
480 }
554e38ff 481 break;
bf551144 482 case 'm':
d6ac1ee8 483 if (!midiEngine.isQwertyEnabled()) {
a8d55ade
MS
484 mappingMode = !mappingMode;
485 uiPatternA.setVisible(!mappingMode);
486 uiMapping.setVisible(mappingMode);
487 if (mappingMode) {
488 restoreToPattern = lx.getPattern();
489 lx.setPatterns(new LXPattern[] { mappingTool });
490 } else {
491 lx.setPatterns(patterns);
492 LXTransition pop = restoreToPattern.getTransition();
493 restoreToPattern.setTransition(null);
494 lx.goPattern(restoreToPattern);
495 restoreToPattern.setTransition(pop);
496 }
bf551144
MS
497 }
498 break;
19d16a16
MS
499 case 't':
500 if (!midiEngine.isQwertyEnabled()) {
501 lx.engine.setThreaded(!lx.engine.isThreaded());
502 }
503 break;
e73ef85d 504 case 'p':
79ae8245
MS
505 for (PandaDriver p : pandaBoards) {
506 p.toggle();
507 }
cc9fcf4b 508 break;
3f8be614 509 case 'u':
d6ac1ee8 510 if (!midiEngine.isQwertyEnabled()) {
4df91daf
MS
511 uiOn = !uiOn;
512 }
3f8be614 513 break;
49815cc0
MS
514 }
515}
516
34327c96
MS
517/**
518 * Top-level mouse event handling
519 */
0a9f99cc 520int mx, my;
0a9f99cc 521void mousePressed() {
d626bc9b
MS
522 boolean debugged = false;
523 if (debugMode) {
524 debugged = debugUI.mousePressed();
525 }
526 if (!debugged) {
527 for (UIContext context : overlays) {
528 context.mousePressed(mouseX, mouseY);
529 }
0a9f99cc 530 }
d626bc9b
MS
531 mx = mouseX;
532 my = mouseY;
0a9f99cc
MS
533}
534
535void mouseDragged() {
d626bc9b
MS
536 boolean dragged = false;
537 for (UIContext context : overlays) {
538 dragged |= context.mouseDragged(mouseX, mouseY);
539 }
540 if (!dragged) {
0a9f99cc
MS
541 int dx = mouseX - mx;
542 int dy = mouseY - my;
543 mx = mouseX;
544 my = mouseY;
545 eyeA += dx*.003;
546 eyeX = midX + eyeR*sin(eyeA);
547 eyeZ = midZ + eyeR*cos(eyeA);
548 eyeY += dy;
549 }
550}
551
552void mouseReleased() {
d626bc9b
MS
553 for (UIContext context : overlays) {
554 context.mouseReleased(mouseX, mouseY);
555 }
0a9f99cc 556}
73687629 557
a0140e21 558void mouseWheel(int delta) {
d626bc9b
MS
559 boolean wheeled = false;
560 for (UIContext context : overlays) {
561 wheeled |= context.mouseWheel(mouseX, mouseY, delta);
562 }
563
564 if (!wheeled) {
0ba6ac44
MS
565 eyeR = constrain(eyeR - delta, -500, -80);
566 eyeX = midX + eyeR*sin(eyeA);
567 eyeZ = midZ + eyeR*cos(eyeA);
568 }
bda5421d 569}