Draw a cute logo on front of the box, gotta have a logo in the UI!
[SugarCubes.git] / _Internals.pde
1 /**
2 * DOUBLE BLACK DIAMOND DOUBLE BLACK DIAMOND
3 *
4 * //\\ //\\ //\\ //\\
5 * ///\\\ ///\\\ ///\\\ ///\\\
6 * \\\/// \\\/// \\\/// \\\///
7 * \\// \\// \\// \\//
8 *
9 * EXPERTS ONLY!! EXPERTS ONLY!!
10 *
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
16 import glucose.*;
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.*;
29 import ddf.minim.*;
30 import ddf.minim.analysis.*;
31 import processing.opengl.*;
32 import rwmidi.*;
33
34 final int VIEWPORT_WIDTH = 900;
35 final int VIEWPORT_HEIGHT = 700;
36
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;
41
42 int targetFramerate = 60;
43 int startMillis, lastMillis;
44
45 // Core engine variables
46 GLucose glucose;
47 HeronLX lx;
48 LXPattern[] patterns;
49 MappingTool mappingTool;
50 PandaDriver[] pandaBoards;
51 MidiListener midiQwertyKeys;
52 MidiListener midiQwertyAPC;
53
54 // Display configuration mode
55 boolean mappingMode = false;
56 boolean debugMode = false;
57 DebugUI debugUI;
58 boolean uiOn = true;
59 LXPattern restoreToPattern = null;
60 PImage logo;
61
62 // Handles to UI objects
63 UIContext[] overlays;
64 UIPatternDeck uiPatternA;
65 UICrossfader uiCrossfader;
66 UIMidi uiMidi;
67 UIMapping uiMapping;
68 UIDebugText uiDebugText;
69
70 // Camera variables
71 float eyeR, eyeA, eyeX, eyeY, eyeZ, midX, midY, midZ;
72
73 /**
74 * Engine construction and initialization.
75 */
76 LXPattern[] _patterns(GLucose glucose) {
77 LXPattern[] patterns = patterns(glucose);
78 for (LXPattern p : patterns) {
79 p.setTransition(new DissolveTransition(glucose.lx).setDuration(1000));
80 }
81 return patterns;
82 }
83
84 void logTime(String evt) {
85 int now = millis();
86 println(evt + ": " + (now - lastMillis) + "ms");
87 lastMillis = now;
88 }
89
90 void setup() {
91 startMillis = lastMillis = millis();
92
93 // Initialize the Processing graphics environment
94 size(VIEWPORT_WIDTH, VIEWPORT_HEIGHT, OPENGL);
95 frameRate(targetFramerate);
96 noSmooth();
97 // hint(ENABLE_OPENGL_4X_SMOOTH); // no discernable improvement?
98 logTime("Created viewport");
99
100 // Create the GLucose engine to run the cubes
101 glucose = new GLucose(this, buildModel());
102 lx = glucose.lx;
103 lx.enableKeyboardTempo();
104 logTime("Built GLucose engine");
105
106 // Set the patterns
107 Engine engine = lx.engine;
108 engine.setPatterns(patterns = _patterns(glucose));
109 engine.addDeck(_patterns(glucose));
110 logTime("Built patterns");
111 glucose.setTransitions(transitions(glucose));
112 logTime("Built transitions");
113 glucose.lx.addEffects(effects(glucose));
114 logTime("Built effects");
115
116 // Build output driver
117 PandaMapping[] pandaMappings = buildPandaList();
118 pandaBoards = new PandaDriver[pandaMappings.length];
119 int pbi = 0;
120 for (PandaMapping pm : pandaMappings) {
121 pandaBoards[pbi++] = new PandaDriver(pm.ip, glucose.model, pm);
122 }
123 mappingTool = new MappingTool(glucose, pandaMappings);
124 logTime("Built PandaDriver");
125
126 // MIDI devices
127 List<MidiListener> midiListeners = new ArrayList<MidiListener>();
128 midiListeners.add(midiQwertyKeys = new MidiListener(MidiListener.KEYS));
129 midiListeners.add(midiQwertyAPC = new MidiListener(MidiListener.APC));
130 for (MidiInputDevice device : RWMidi.getInputDevices()) {
131 boolean enableDevice = device.getName().contains("APC");
132 midiListeners.add(new MidiListener(device).setEnabled(enableDevice));
133 }
134 SCMidiDevices.initializeStandardDevices(glucose);
135 logTime("Setup MIDI devices");
136
137 // Build overlay UI
138 debugUI = new DebugUI(pandaMappings);
139 overlays = new UIContext[] {
140 uiPatternA = new UIPatternDeck(lx.engine.getDeck(0), "PATTERN A", 4, 4, 140, 324),
141 new UIBlendMode(4, 332, 140, 86),
142 new UIEffects(4, 422, 140, 144),
143 new UITempo(4, 570, 140, 50),
144 new UISpeed(4, 624, 140, 50),
145
146 new UIPatternDeck(lx.engine.getDeck(1), "PATTERN B", width-144, 4, 140, 324),
147 uiMidi = new UIMidi(midiListeners, width-144, 332, 140, 158),
148 new UIOutput(width-144, 494, 140, 106),
149
150 uiCrossfader = new UICrossfader(width/2-90, height-90, 180, 86),
151
152 uiDebugText = new UIDebugText(148, height-138, width-304, 44),
153 uiMapping = new UIMapping(mappingTool, 4, 4, 140, 324),
154 };
155 uiMapping.setVisible(false);
156 logTime("Built overlay UI");
157
158 // Load logo image
159 logo = loadImage("data/logo.png");
160
161 // Setup camera
162 midX = TRAILER_WIDTH/2.;
163 midY = glucose.model.yMax/2;
164 midZ = TRAILER_DEPTH/2.;
165 eyeR = -290;
166 eyeA = .15;
167 eyeY = midY + 70;
168 eyeX = midX + eyeR*sin(eyeA);
169 eyeZ = midZ + eyeR*cos(eyeA);
170 addMouseWheelListener(new java.awt.event.MouseWheelListener() {
171 public void mouseWheelMoved(java.awt.event.MouseWheelEvent mwe) {
172 mouseWheel(mwe.getWheelRotation());
173 }});
174
175 println("Total setup: " + (millis() - startMillis) + "ms");
176 println("Hit the 'p' key to toggle Panda Board output");
177 }
178
179 public class MidiListener extends AbstractScrollItem {
180
181 public static final int MIDI = 0;
182 public static final int KEYS = 1;
183 public static final int APC = 2;
184
185 private boolean enabled = false;
186 private final String name;
187
188 MidiListener(MidiInputDevice d) {
189 mode = MIDI;
190 d.createInput(this);
191 name = d.getName();
192 }
193
194 class NoteMeta {
195 int channel;
196 int number;
197 NoteMeta(int channel, int number) {
198 this.channel = channel;
199 this.number = number;
200 }
201 }
202
203 final Map<Character, NoteMeta> keyToNote = new HashMap<Character, NoteMeta>();
204
205 private final int mode;
206 private int octaveShift = 0;
207
208 MidiListener(int mode) {
209 this.mode = mode;
210 switch (mode) {
211 case APC:
212 name = "QWERTY (APC Mode)";
213 mapAPC();
214 break;
215 default:
216 case KEYS:
217 name = "QWERTY (Key Mode)";
218 mapKeys();
219 break;
220 }
221 }
222
223 private void mapAPC() {
224 mapNote('1', 0, 53);
225 mapNote('2', 1, 53);
226 mapNote('3', 2, 53);
227 mapNote('4', 3, 53);
228 mapNote('5', 4, 53);
229 mapNote('6', 5, 53);
230 mapNote('q', 0, 54);
231 mapNote('w', 1, 54);
232 mapNote('e', 2, 54);
233 mapNote('r', 3, 54);
234 mapNote('t', 4, 54);
235 mapNote('y', 5, 54);
236 mapNote('a', 0, 55);
237 mapNote('s', 1, 55);
238 mapNote('d', 2, 55);
239 mapNote('f', 3, 55);
240 mapNote('g', 4, 55);
241 mapNote('h', 5, 55);
242 mapNote('z', 0, 56);
243 mapNote('x', 1, 56);
244 mapNote('c', 2, 56);
245 mapNote('v', 3, 56);
246 mapNote('b', 4, 56);
247 mapNote('n', 5, 56);
248 registerKeyEvent(this);
249 }
250
251 private void mapKeys() {
252 int note = 48;
253 mapNote('a', 1, note++);
254 mapNote('w', 1, note++);
255 mapNote('s', 1, note++);
256 mapNote('e', 1, note++);
257 mapNote('d', 1, note++);
258 mapNote('f', 1, note++);
259 mapNote('t', 1, note++);
260 mapNote('g', 1, note++);
261 mapNote('y', 1, note++);
262 mapNote('h', 1, note++);
263 mapNote('u', 1, note++);
264 mapNote('j', 1, note++);
265 mapNote('k', 1, note++);
266 mapNote('o', 1, note++);
267 mapNote('l', 1, note++);
268 registerKeyEvent(this);
269 }
270
271 void mapNote(char ch, int channel, int number) {
272 keyToNote.put(ch, new NoteMeta(channel, number));
273 }
274
275 public String getLabel() {
276 return name;
277 }
278
279 public void keyEvent(KeyEvent e) {
280 if (!enabled) {
281 return;
282 }
283 char c = Character.toLowerCase(e.getKeyChar());
284 NoteMeta nm = keyToNote.get(c);
285 if (nm != null) {
286 switch (e.getID()) {
287 case KeyEvent.KEY_PRESSED:
288 noteOnReceived(new Note(Note.NOTE_ON, nm.channel, nm.number + octaveShift*12, 127));
289 break;
290 case KeyEvent.KEY_RELEASED:
291 noteOffReceived(new Note(Note.NOTE_OFF, nm.channel, nm.number + octaveShift*12, 0));
292 break;
293 }
294 }
295 if ((mode == KEYS) && (e.getID() == KeyEvent.KEY_PRESSED)) {
296 switch (c) {
297 case 'z':
298 octaveShift = constrain(octaveShift-1, -4, 4);
299 break;
300 case 'x':
301 octaveShift = constrain(octaveShift+1, -4, 4);
302 break;
303 }
304 }
305 }
306
307 public boolean isEnabled() {
308 return enabled;
309 }
310
311 public boolean isSelected() {
312 return enabled;
313 }
314
315 public void onMousePressed() {
316 setEnabled(!enabled);
317 }
318
319 public MidiListener setEnabled(boolean enabled) {
320 if (enabled != this.enabled) {
321 this.enabled = enabled;
322 uiMidi.redraw();
323 }
324 return this;
325 }
326
327 private SCPattern getFocusedPattern() {
328 return (SCPattern) uiMidi.getFocusedDeck().getActivePattern();
329 }
330
331 void programChangeReceived(ProgramChange pc) {
332 if (!enabled) {
333 return;
334 }
335 if (uiMidi.logMidi()) {
336 println(getLabel() + " :: Program Change :: " + pc.getNumber());
337 }
338 }
339
340 void controllerChangeReceived(rwmidi.Controller cc) {
341 if (!enabled) {
342 return;
343 }
344 if (uiMidi.logMidi()) {
345 println(getLabel() + " :: Controller :: " + cc.getCC() + ":" + cc.getValue());
346 }
347 getFocusedPattern().controllerChangeReceived(cc);
348 }
349
350 void noteOnReceived(Note note) {
351 if (!enabled) {
352 return;
353 }
354 if (uiMidi.logMidi()) {
355 println(getLabel() + " :: Note On :: " + note.getChannel() + ":" + note.getPitch() + ":" + note.getVelocity());
356 }
357 getFocusedPattern().noteOnReceived(note);
358 }
359
360 void noteOffReceived(Note note) {
361 if (!enabled) {
362 return;
363 }
364 if (uiMidi.logMidi()) {
365 println(getLabel() + " :: Note Off :: " + note.getChannel() + ":" + note.getPitch() + ":" + note.getVelocity());
366 }
367 getFocusedPattern().noteOffReceived(note);
368 }
369
370 }
371
372 /**
373 * Core render loop and drawing functionality.
374 */
375 void draw() {
376 // Draws the simulation and the 2D UI overlay
377 background(40);
378 color[] colors = glucose.getColors();
379
380 String displayMode = uiCrossfader.getDisplayMode();
381 if (displayMode == "A") {
382 colors = lx.engine.getDeck(0).getColors();
383 } else if (displayMode == "B") {
384 colors = lx.engine.getDeck(1).getColors();
385 }
386 if (debugMode) {
387 debugUI.maskColors(colors);
388 }
389
390 camera(
391 eyeX, eyeY, eyeZ,
392 midX, midY, midZ,
393 0, -1, 0
394 );
395
396 translate(0, 40, 0);
397
398 noStroke();
399 fill(#141414);
400 drawBox(0, -TRAILER_HEIGHT, 0, 0, 0, 0, TRAILER_WIDTH, TRAILER_HEIGHT, TRAILER_DEPTH, TRAILER_HEIGHT/2.);
401 fill(#070707);
402 stroke(#222222);
403 beginShape();
404 vertex(0, 0, 0);
405 vertex(TRAILER_WIDTH, 0, 0);
406 vertex(TRAILER_WIDTH, 0, TRAILER_DEPTH);
407 vertex(0, 0, TRAILER_DEPTH);
408 endShape();
409
410 // Draw the logo on the front of platform
411 pushMatrix();
412 translate(0, 0, -1);
413 float s = .07;
414 scale(s, -s, s);
415 image(logo, TRAILER_WIDTH/2/s-logo.width/2, TRAILER_HEIGHT/2/s-logo.height/2-2/s);
416 popMatrix();
417
418 noStroke();
419 // drawBassBox(glucose.model.bassBox);
420 // for (Speaker s : glucose.model.speakers) {
421 // drawSpeaker(s);
422 // }
423 for (Cube c : glucose.model.cubes) {
424 drawCube(c);
425 }
426
427 noFill();
428 strokeWeight(2);
429 beginShape(POINTS);
430 // TODO(mcslee): restore when bassBox/speakers are right again
431 // for (Point p : glucose.model.points) {
432 for (Cube cube : glucose.model.cubes) {
433 for (Point p : cube.points) {
434 stroke(colors[p.index]);
435 vertex(p.fx, p.fy, p.fz);
436 }
437 }
438 endShape();
439
440 // 2D Overlay UI
441 drawUI();
442
443 // Send output colors
444 color[] sendColors = glucose.getColors();
445 if (debugMode) {
446 debugUI.maskColors(colors);
447 }
448
449 // Gamma correction here. Apply a cubic to the brightness
450 // for better representation of dynamic range
451 for (int i = 0; i < colors.length; ++i) {
452 float b = brightness(colors[i]) / 100.f;
453 colors[i] = color(
454 hue(colors[i]),
455 saturation(colors[i]),
456 (b*b*b) * 100.
457 );
458 }
459
460 // TODO(mcslee): move into GLucose engine
461 for (PandaDriver p : pandaBoards) {
462 p.send(colors);
463 }
464 }
465
466 void drawBassBox(BassBox b) {
467 float in = .15;
468
469 noStroke();
470 fill(#191919);
471 pushMatrix();
472 translate(b.x + BassBox.EDGE_WIDTH/2., b.y + BassBox.EDGE_HEIGHT/2, b.z + BassBox.EDGE_DEPTH/2.);
473 box(BassBox.EDGE_WIDTH-20*in, BassBox.EDGE_HEIGHT-20*in, BassBox.EDGE_DEPTH-20*in);
474 popMatrix();
475
476 noStroke();
477 fill(#393939);
478 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);
479
480 pushMatrix();
481 translate(b.x+(Cube.CHANNEL_WIDTH-in)/2., b.y + BassBox.EDGE_HEIGHT-in, b.z + BassBox.EDGE_DEPTH/2.);
482 float lastOffset = 0;
483 for (float offset : BoothFloor.STRIP_OFFSETS) {
484 translate(offset - lastOffset, 0, 0);
485 box(Cube.CHANNEL_WIDTH-in, 0, BassBox.EDGE_DEPTH - 2*in);
486 lastOffset = offset;
487 }
488 popMatrix();
489
490 pushMatrix();
491 translate(b.x + (Cube.CHANNEL_WIDTH-in)/2., b.y + BassBox.EDGE_HEIGHT/2., b.z + in);
492 for (int j = 0; j < 2; ++j) {
493 pushMatrix();
494 for (int i = 0; i < BassBox.NUM_FRONT_STRUTS; ++i) {
495 translate(BassBox.FRONT_STRUT_SPACING, 0, 0);
496 box(Cube.CHANNEL_WIDTH-in, BassBox.EDGE_HEIGHT - in*2, 0);
497 }
498 popMatrix();
499 translate(0, 0, BassBox.EDGE_DEPTH - 2*in);
500 }
501 popMatrix();
502
503 pushMatrix();
504 translate(b.x + in, b.y + BassBox.EDGE_HEIGHT/2., b.z + BassBox.SIDE_STRUT_SPACING + (Cube.CHANNEL_WIDTH-in)/2.);
505 box(0, BassBox.EDGE_HEIGHT - in*2, Cube.CHANNEL_WIDTH-in);
506 translate(BassBox.EDGE_WIDTH-2*in, 0, 0);
507 box(0, BassBox.EDGE_HEIGHT - in*2, Cube.CHANNEL_WIDTH-in);
508 popMatrix();
509
510 }
511
512 void drawCube(Cube c) {
513 float in = .15;
514 noStroke();
515 fill(#393939);
516 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);
517 }
518
519 void drawSpeaker(Speaker s) {
520 float in = .15;
521
522 noStroke();
523 fill(#191919);
524 pushMatrix();
525 translate(s.x, s.y, s.z);
526 rotate(s.ry / 180. * PI, 0, -1, 0);
527 translate(Speaker.EDGE_WIDTH/2., Speaker.EDGE_HEIGHT/2., Speaker.EDGE_DEPTH/2.);
528 box(Speaker.EDGE_WIDTH-20*in, Speaker.EDGE_HEIGHT-20*in, Speaker.EDGE_DEPTH-20*in);
529 translate(0, Speaker.EDGE_HEIGHT/2. + Speaker.EDGE_HEIGHT*.8/2, 0);
530
531 fill(#222222);
532 box(Speaker.EDGE_WIDTH*.6, Speaker.EDGE_HEIGHT*.8, Speaker.EDGE_DEPTH*.75);
533 popMatrix();
534
535 noStroke();
536 fill(#393939);
537 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);
538 }
539
540 void drawBox(float x, float y, float z, float rx, float ry, float rz, float xd, float yd, float zd, float sw) {
541 pushMatrix();
542 translate(x, y, z);
543 rotate(rx / 180. * PI, -1, 0, 0);
544 rotate(ry / 180. * PI, 0, -1, 0);
545 rotate(rz / 180. * PI, 0, 0, -1);
546 for (int i = 0; i < 4; ++i) {
547 float wid = (i % 2 == 0) ? xd : zd;
548
549 beginShape();
550 vertex(0, 0);
551 vertex(wid, 0);
552 vertex(wid, yd);
553 vertex(wid - sw, yd);
554 vertex(wid - sw, sw);
555 vertex(0, sw);
556 endShape();
557 beginShape();
558 vertex(0, sw);
559 vertex(0, yd);
560 vertex(wid - sw, yd);
561 vertex(wid - sw, yd - sw);
562 vertex(sw, yd - sw);
563 vertex(sw, sw);
564 endShape();
565
566 translate(wid, 0, 0);
567 rotate(HALF_PI, 0, -1, 0);
568 }
569 popMatrix();
570 }
571
572 void drawUI() {
573 camera();
574 javax.media.opengl.GL gl = ((PGraphicsOpenGL)g).beginGL();
575 gl.glClear(javax.media.opengl.GL.GL_DEPTH_BUFFER_BIT);
576 ((PGraphicsOpenGL)g).endGL();
577 strokeWeight(1);
578
579 if (uiOn) {
580 for (UIContext context : overlays) {
581 context.draw();
582 }
583 }
584
585 // Always draw FPS meter
586 fill(#555555);
587 textSize(9);
588 textAlign(LEFT, BASELINE);
589 text("FPS: " + ((int) (frameRate*10)) / 10. + " / " + targetFramerate + " (-/+)", 4, height-4);
590
591 if (debugMode) {
592 debugUI.draw();
593 }
594 }
595
596
597 /**
598 * Top-level keyboard event handling
599 */
600 void keyPressed() {
601 if (mappingMode) {
602 mappingTool.keyPressed(uiMapping);
603 }
604 switch (key) {
605 case '-':
606 case '_':
607 frameRate(--targetFramerate);
608 break;
609 case '=':
610 case '+':
611 frameRate(++targetFramerate);
612 break;
613 case 'd':
614 if (!midiQwertyAPC.isEnabled() && !midiQwertyKeys.isEnabled()) {
615 debugMode = !debugMode;
616 println("Debug output: " + (debugMode ? "ON" : "OFF"));
617 }
618 break;
619 case 'm':
620 if (!midiQwertyAPC.isEnabled() && !midiQwertyKeys.isEnabled()) {
621 mappingMode = !mappingMode;
622 uiPatternA.setVisible(!mappingMode);
623 uiMapping.setVisible(mappingMode);
624 if (mappingMode) {
625 restoreToPattern = lx.getPattern();
626 lx.setPatterns(new LXPattern[] { mappingTool });
627 } else {
628 lx.setPatterns(patterns);
629 LXTransition pop = restoreToPattern.getTransition();
630 restoreToPattern.setTransition(null);
631 lx.goPattern(restoreToPattern);
632 restoreToPattern.setTransition(pop);
633 }
634 }
635 break;
636 case 'p':
637 for (PandaDriver p : pandaBoards) {
638 p.toggle();
639 }
640 break;
641 case 'u':
642 if (!midiQwertyAPC.isEnabled() && !midiQwertyKeys.isEnabled()) {
643 uiOn = !uiOn;
644 }
645 break;
646 }
647 }
648
649 /**
650 * Top-level mouse event handling
651 */
652 int mx, my;
653 void mousePressed() {
654 boolean debugged = false;
655 if (debugMode) {
656 debugged = debugUI.mousePressed();
657 }
658 if (!debugged) {
659 for (UIContext context : overlays) {
660 context.mousePressed(mouseX, mouseY);
661 }
662 }
663 mx = mouseX;
664 my = mouseY;
665 }
666
667 void mouseDragged() {
668 boolean dragged = false;
669 for (UIContext context : overlays) {
670 dragged |= context.mouseDragged(mouseX, mouseY);
671 }
672 if (!dragged) {
673 int dx = mouseX - mx;
674 int dy = mouseY - my;
675 mx = mouseX;
676 my = mouseY;
677 eyeA += dx*.003;
678 eyeX = midX + eyeR*sin(eyeA);
679 eyeZ = midZ + eyeR*cos(eyeA);
680 eyeY += dy;
681 }
682 }
683
684 void mouseReleased() {
685 for (UIContext context : overlays) {
686 context.mouseReleased(mouseX, mouseY);
687 }
688 }
689
690 void mouseWheel(int delta) {
691 boolean wheeled = false;
692 for (UIContext context : overlays) {
693 wheeled |= context.mouseWheel(mouseX, mouseY, delta);
694 }
695
696 if (!wheeled) {
697 eyeR = constrain(eyeR - delta, -500, -80);
698 eyeX = midX + eyeR*sin(eyeA);
699 eyeZ = midZ + eyeR*cos(eyeA);
700 }
701 }