/
SheepSimulator.pde
executable file
·1595 lines (1343 loc) · 47.9 KB
/
SheepSimulator.pde
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//
// SHIT TO START WITH
//
// Did you install the external libraries yet?
// - Use the Sketch menu -> Import Library -> Add Library
// - Search for G4P - Click Install
// - Search for Shapes - Click Install on Shapes 3D
//
// It should run now. Hit the play button in the upper left corner of this window.
//
// OMG! Is that window too F-ing big??? Change these two numbers right here
// to something smaller for your tiny little screen. Try a width of 640 and a
// height of 480 as if your computer is from 1993.
// |
// V
final int WINDOW_WIDTH = 1200;
final int WINDOW_HEIGHT = 800;
//
// After you change those numbers you have to hit the stop button and then the
// play button again to restart things.
//
// Now go start the python server to make the colors change.
//
// Enjoy. (BTW - try dragging with your mouse in the simulator window.
// That will move the camera around.)
//
// If things totally don't work, feel free to bug Tom Seago via facebook
// or via email <tom@tomseago.com>. I promise to make up as many answers
// as you want.
//
import processing.net.*;
import java.util.regex.*;
import java.util.*;
// External libraries. To install, select the menu Sketch | Import Library | Add Library
// Search for G4P and Shapes 3D
import shapes3d.*;
import shapes3d.animation.*;
import g4p_controls.*;
//Whole Object representation.
Sheep theSheep;
TreeMap<Integer, ArrayList<Integer>> partySideMap = new TreeMap<Integer, ArrayList<Integer>>();
TreeMap<Integer, ArrayList<Integer>> boringSideMap = new TreeMap<Integer, ArrayList<Integer>>();
Camera camera;
// network vars
int port = 4444;
Server _server;
StringBuffer _buf = new StringBuffer();
// TODO: The ground should probably be a Shapes3D terrain. Whatevs.
PShape ground;
GButton btnGroundFront;
GButton btnGroundSide;
GButton btnGroundRear;
GButton btnHighNext;
GButton btnHighPrev;
GCheckbox cbShowLabels;
GCheckbox cbLogicalHighlight;
GLabel lblHighNum;
GButton btnColors;
// This scale is applied to the loaded OBJ and then everything else that
// was originally scaled relative to the baseline OBJ. The issue was that
// at the original scale, all the font sizes were tiny, and apparently they
// get prerendered to surfaces because they came out extra fuzzy and crappy.
// Scaling everything up seems to have resolved that.
final int SHEEP_SCALE = 3;
// setup() is called once by the environment to initialize things
void setup() {
size(1200, 800, P3D);
// Some UI stuff. Need to get G4P up and running before PeasyCam
G4P.setGlobalColorScheme(G4P.YELLOW_SCHEME);
btnGroundFront = new GButton(this, 5, 5, 50, 15, "Front Q");
btnGroundSide = new GButton(this, 60, 5, 50, 15, "Side");
btnGroundRear = new GButton(this, 115, 5, 50, 15, "Rear Q");
cbShowLabels = new GCheckbox(this, 5, 25, 100, 15, "Show Labels");
cbLogicalHighlight = new GCheckbox(this, 5, 45, 300, 15, "Show Highlight");
btnHighPrev = new GButton(this, 5, 65, 50, 15, "Prev");
btnHighNext = new GButton(this, 60, 65, 50, 15, "Next");
lblHighNum = new GLabel(this, 115, 65, 30, 15, "1");
btnColors = new GButton(this, 5, 85, 120, 15, "Constrasting Colors");
// Initialize camera movements
camera = new Camera(this, 0, SHEEP_SCALE * -300, SHEEP_SCALE * 500);
// Load the .csv file which contains a mapping from logical panel
// numbers used by the shows (and by the construction documents) into
// surface indexes for the 3D model. Some panels map to up to 3
// surfaces (i.e. it's not strictly 1 to 1). The file is loaded twice,
// once for each side.
partySideMap = loadPolyMap("SheepPanelPolyMap.csv", "p");
boringSideMap = loadPolyMap("SheepPanelPolyMap.csv", "b");
// Most of the fun will happen in the sheep class, at least as far as
// display management goes.
theSheep = new Sheep(this, "model.obj");
// Will use the ground when drawing. It is just a really big rectangle
ground = createShape();
ground.beginShape();
ground.fill(#241906);
ground.vertex(SHEEP_SCALE * -10000,0,SHEEP_SCALE * -10000);
ground.vertex(SHEEP_SCALE * 10000,0,SHEEP_SCALE * -10000);
ground.vertex(SHEEP_SCALE * 10000,0,SHEEP_SCALE * 10000);
ground.vertex(SHEEP_SCALE * -10000,0,SHEEP_SCALE * 10000);
ground.endShape(CLOSE);
// let's start with contrasting colors because they are prettier
theSheep.setContrastingColors();
// The server is a TCP server listening on a defined port which accepts
// commands from the lighting server to change the color of individual
// panels or to modify the parameters of the lights. It reads data into
// a buffer which is periodically polled during the draw process.
_server = new Server(this, port);
println("server listening:" + _server);
}
// The draw() function can be considered the main event loop of the application.
// It's purpose is to draw the next frame onto the screen, and also to update
// any internal state that needs to change. It is called continously forever
// at whatever speed the rendering engine and host machine can handle.
void draw() {
pushMatrix();
// The last frame is not automatically cleared, so we do that first by
// setting a solid background color.
background(#1B2A36);
camera.feed();
// Draw the sheep surfaces in their current colors etc.
theSheep.draw();
shape(ground);
// Also draw a center axis for general 3d reference. Red in +X axis,
// Green in +Y, and Blue in +Z
stroke(#ff0000);
line(0, 0, 0, SHEEP_SCALE * 10, 0, 0);
stroke(#00ff00);
line(0, 0, 0, 0, SHEEP_SCALE * 10, 0);
stroke(#0000ff);
line(0, 0, 0, 0, 0, SHEEP_SCALE * 10);
// Check for any updates from the network. These won't be visible
// until the next time this function is called.
pollServer();
popMatrix();
// Reset the camera for the UI elements
perspective();
}
// Various mouse events to modify the camera in a reasonably natural feeling way
void mouseWheel(MouseEvent event) {
float e = event.getCount();
if (keyCode == SHIFT) {
camera.zoom(e/10);
} else {
camera.dolly(e*4);
}
}
float dragArcScaling = 60;
float dragCircleScaling = 60;
void mouseDragged() {
float x = (mouseX - pmouseX);
float y = (mouseY - pmouseY);
//println("x="+x+" y="+y);
if (mouseButton == CENTER || keyCode == SHIFT) {
camera.boom(y*4);
camera.truck(x*4);
} else if (mouseButton == RIGHT) {
camera.pan(x / dragCircleScaling);
camera.tilt(y / dragArcScaling);
} else {
// LEFT
camera.circle(-x / dragCircleScaling);
camera.arc(y / dragArcScaling);
}
// Correct for being below ground
float[] pos = camera.position();
//println("camera y = "+pos[1]);
if (pos[1] > -38f) {
camera.jump(pos[0], -38f, pos[2]);
}
}
StringBuffer panelValue = new StringBuffer();
// keyTyped is called outside of calls to the draw() function whenever
// a key is pressed and released, or possibly, depending on OS, it might
// be called multiple times if a key value is repeated. This is how we
// handle some simple UI input.
void keyTyped() {
println("typed "+key);
switch(key) {
case '.': // Next
theSheep.moveCursor(1);
println("Next panel = " + theSheep.panelCursor);
break;
case '>': // Next
theSheep.moveCursor(10);
println("Next panel = " + theSheep.panelCursor);
break;
case ',': // Prev
theSheep.moveCursor(-1);
println("Prev panel = " + theSheep.panelCursor);
break;
case '<': // Prev
theSheep.moveCursor(-10);
println("Prev panel = " + theSheep.panelCursor);
break;
// The logical cursor
case ']': // Next
theSheep.moveLogicalCursor(1);
updateHighlightNumber();
break;
case '}': // Next
theSheep.moveLogicalCursor(10);
updateHighlightNumber();
break;
case '[': // Prev
theSheep.moveLogicalCursor(-1);
updateHighlightNumber();
break;
case '{': // Prev
theSheep.moveLogicalCursor(-10);
updateHighlightNumber();
break;
case 'p': // Assign to P side
assignPanel(true);
break;
case 'b': // Assignt to B side
assignPanel(false);
break;
case 's': // Save to a file
savePanelMap();
break;
default:
if (key >= '0' && key <= '9') {
panelValue.append(key);
}
}
}
// Assign the current surface to the logical panel in panelValue, assuming it can
// be parsed as an int
void assignPanel(boolean isParty) {
if (panelValue.length()==0) {
println("Type some digits. Nothing to assign");
return;
}
int logicalNum = -1;
String toParse = panelValue.toString();
panelValue.setLength(0);
try {
logicalNum = Integer.parseInt(toParse);
} catch (NumberFormatException nfe) {
println("Could not parse '"+toParse+"' as a number");
return;
}
// First remove this from any logical assignment it might have. THis is slow and bad
// and brute force, but that's not important to us right now
clearAssignment(partySideMap);
clearAssignment(boringSideMap);
TreeMap<Integer, ArrayList<Integer>> map = isParty ? partySideMap : boringSideMap;
ArrayList<Integer> list = map.get(logicalNum);
if (list==null) {
list = new ArrayList<Integer>();
map.put(logicalNum, list);
}
list.add(theSheep.panelCursor);
println("Assigned surface "+theSheep.panelCursor+" to logical panel "+logicalNum+" on "+(isParty?"party":"business")+" side");
}
void clearAssignment(TreeMap<Integer, ArrayList<Integer>> map) {
Integer toKill = new Integer(theSheep.panelCursor);
for(ArrayList<Integer> list: map.values()) {
while(list.remove(toKill));
}
}
void savePanelMap() {
String filename = "saved-panel-map.csv";
PrintWriter out = createWriter(filename);
for(Map.Entry e: partySideMap.entrySet()) {
writeLine(out, e, "p");
}
out.println();
for(Map.Entry e: boringSideMap.entrySet()) {
writeLine(out, e, "b");
}
out.flush();
out.close();
println("Saved current map to "+filename);
}
void writeLine(PrintWriter out, Map.Entry e, String side) {
out.print((Integer)e.getKey());
out.print(",");
for(Integer i: (ArrayList<Integer>)e.getValue()) {
out.print(i);
out.print(",");
}
out.print(side);
out.print("\n");
}
/**
* Handle events from GButton buttons. Mostly we reset the display
*/
void handleButtonEvents(GButton button, GEvent event) {
if (button == btnGroundFront) {
camera.jump(SHEEP_SCALE * -500, SHEEP_SCALE * -50, SHEEP_SCALE * 400);
camera.aim(0,SHEEP_SCALE * -200,0);
} else if(button == btnGroundRear) {
camera.jump(SHEEP_SCALE * 500, SHEEP_SCALE * -60, SHEEP_SCALE * 400);
camera.aim(0,SHEEP_SCALE * -100,0);
} else if (button == btnGroundSide) {
camera.jump(0, SHEEP_SCALE * -300, SHEEP_SCALE * 500);
camera.aim(0,0,0);
} else if (button == btnHighNext) {
theSheep.moveLogicalCursor(1);
updateHighlightNumber();
} else if (button == btnHighPrev) {
theSheep.moveLogicalCursor(-1);
updateHighlightNumber();
} else if (button == btnColors) {
theSheep.setContrastingColors();
}
}
public void handleToggleControlEvents(GToggleControl cb, GEvent event) {
if (cb == cbShowLabels) {
theSheep.setShowLabels(cb.isSelected());
} else if (cb == cbLogicalHighlight) {
theSheep.showLogicalHighlight = cb.isSelected();
}
}
public void updateHighlightNumber() {
lblHighNum.setText(Integer.toString(theSheep.logicalCursor));
}
/**
* Check the network server for waiting data and make any necessary
* state changes based on pending commands.
*/
void pollServer() {
try {
// Get the current client (if any)
Client c = _server.available();
// append any available bytes to the buffer
if (c != null) {
// Put all we have into this buffer not worrying at this point
// about it potentially being a partial line or multiple lines.
// Just bytes into buffer.
_buf.append(c.readString());
}
// process as many lines as we can find in the buffer. Lines are
// terminated with \n characters so we break the string apart into
// lines first, and then call processCommand to deal with each line,
// until there are no more complete lines - although there might be
// some partial part of a line hanging out in the buffer waiting
// to get completed by new data on the next time through here.
int ix = _buf.indexOf("\n");
while (ix > -1) {
String msg = _buf.substring(0, ix);
msg = msg.trim();
//println(msg);
processCommand(msg);
_buf.delete(0, ix+1);
ix = _buf.indexOf("\n");
}
}
catch (Exception e) {
println("exception handling network command");
e.printStackTrace();
}
}
// This regular expression is used to parse anything that doesn't appear to be a DMX
// command. The DMX syntax is handled by the second expression
Pattern cmd_pattern = Pattern.compile("^\\s*(p|b|a|f )\\s+(\\d+)\\s+(\\d+),(\\d+),(\\d+)\\s*$");
Pattern dmx_pattern = Pattern.compile("^\\s*dmx\\s+(\\d+)\\s+(\\d+)\\s*$");
void processCommand(String cmd) {
Matcher m = cmd_pattern.matcher(cmd);
if (!m.find()) {
m = dmx_pattern.matcher(cmd);
if (m.find()) {
handleDMXCommand(m);
return;
}
println("Input was malformed, please conform to \"[a,p,b,f] panelId r,g,b\" where a=all sides, b=boring, p=party, f=flood, panelId = numeric value, r,g, and b are red green and blue values between 0 and 255");
return;
}
String side = m.group(1);
int panel = Integer.valueOf(m.group(2));
int r = Integer.valueOf(m.group(3));
int g = Integer.valueOf(m.group(4));
int b = Integer.valueOf(m.group(5));
//System.out.println("side:"+side+" "+String.format("panel:%d to r:%d g:%d b:%d", panel, r, g, b));
if (r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255 ) {
System.out.println("Bypassing last entry because an r,g,b value was not between 0 and 255:"+r+","+g+","+b);
return;
}
theSheep.setPanelColor(side, panel, color(r, g, b));
}
void handleDMXCommand(Matcher m) {
try {
int channel = Integer.valueOf(m.group(1));
int value = Integer.valueOf(m.group(2));
// TODO: Make this handle more than one DMX value per line.
// TODO: Make this map DMX for the panel colors back onto panels. - low priority
// DMX is inherently broadcast, so just do that and let each eye be responsible for
// deciding if it cares or not.
theSheep.leftEye.handleDMX(channel, value);
theSheep.rightEye.handleDMX(channel, value);
} catch (Exception e) {
// Don't care much about handling integer parsing errors, but they are no reason to crash
println(e);
}
}
/**
* Loads the mapping file to go from logical panel identifiers to integer indexes
* of surfaces in the 3D mesh. The file is filtered to only load one side at a time
* as specified by the last parameter.
*
* The format of the file is a CSV file where each row has 3 sections. The first
* element of a row is the logical id, which is an integer. The last element of
* a row is a string that must match the filter string (so that means it's either
* a p or a b character). Everything inbetween the first and last elements is an
* integer index of a surface in the sheep mesh which is part of this panel. -1
* may be given as an index and will be ignored.
*
* Something like:
*
* 43,73,p
* 60,159,160,161,162,163,164,165,166,167,168,169,170,172,173,174,175,p
*
* Any row not conforming to the above should be safely ignored, so comments that
* don't match that ordering shouldn't cause problems. Probably.
*/
TreeMap<Integer, ArrayList<Integer>> loadPolyMap(String labelFile, String sidePorB) {
TreeMap<Integer, ArrayList<Integer>> polyMap = new TreeMap<Integer, ArrayList<Integer>>();
Table table = loadTable(labelFile);
println(table.getRowCount() + " total rows in table");
int cols = table.getColumnCount();
ArrayList<Integer> rowInts = new ArrayList<Integer>();
for (TableRow row : table.rows ()) {
try {
rowInts.clear();
String last = null;
for(int i=0; i<cols; i++) {
last = row.getString(i);
try {
rowInts.add(Integer.parseInt(last));
} catch (NumberFormatException nfe) {
break;
}
}
// Minimum number of elements for a row is 3
if (rowInts.size() < 2) continue;
if (sidePorB.equals(last)) {
ArrayList<Integer> temp = new ArrayList<Integer>();
int panel = rowInts.get(0);
for(int i=1; i<rowInts.size(); i++) {
temp.add(rowInts.get(i));
}
polyMap.put(panel, temp);
}
} catch (Exception ex) {
println(ex);
}
}
return polyMap;
}
/**
* Both inverts the Z axis and scales the shape. The .obj uses a slightly
* different coordinate system, hence the need for the inversion. The scaling
* is for better font rendering on the labels.
*/
void invertShape(PShape shape) {
if (shape==null) return;
for(int i=0; i<shape.getVertexCount(); i++) {
shape.setVertex(i, SHEEP_SCALE * shape.getVertexX(i), SHEEP_SCALE * shape.getVertexY(i), SHEEP_SCALE * -1 * shape.getVertexZ(i));
}
}
/*******************************************************************************/
/*******************************************************************************/
/*******************************************************************************/
/**
* The sheep holds the model and it's surfaces. It further provides access to
* the Eyes.
*/
class Sheep {
PShape sheepModel;
PShape[] sheepPanelArray;
PVector[] panelCenters;
Eye leftEye;
Eye rightEye;
int panelCursor;
boolean showLabels = false;
boolean showLogicalHighlight = false;
int logicalCursor = 1;
public Sheep(PApplet app, String fileName) {
sheepModel = loadShape(fileName);
sheepPanelArray = sheepModel.getChildren();
// Darken unmapped surfaces
HashSet<Integer> all = new HashSet<Integer>();
for(Map.Entry entry: partySideMap.entrySet()) {
ArrayList<Integer> list = (ArrayList<Integer>)entry.getValue();
for(int i: list) {
all.add(i);
}
}
for(Map.Entry entry: boringSideMap.entrySet()) {
ArrayList<Integer> list = (ArrayList<Integer>)entry.getValue();
for(int i: list) {
all.add(i);
}
}
// These are the same transforms that we baked into the sheep model. We
// apply them again so that these direct text calls we are about to issue
// will align with the sheep sides
PMatrix3D matrix = new PMatrix3D();
matrix.rotateX(PI);
matrix.rotateY(PI*0.5);
matrix.translate(SHEEP_SCALE * 30, SHEEP_SCALE * 10, SHEEP_SCALE * 550); // Shit, still in model coord space. Lame!
panelCenters = new PVector[sheepPanelArray.length];
for(int i=0; i<sheepPanelArray.length; i++) {
PShape shape = sheepPanelArray[i];
if (shape == null) {
panelCenters[i] = new PVector(0.0f, 0.0f, 0.0f);
continue;
}
// Must invert all shapes
invertShape(shape);
// Calculate the center by averaging all vertexes
float x = 0.0f;
float y = 0.0f;
float z = 0.0f;
int count = shape.getVertexCount();
for(int j=0; j<count; j++) {
x += shape.getVertexX(j);
y += shape.getVertexY(j);
z += shape.getVertexZ(j);
}
x = x / count;
y = y / count;
z = z / count;
panelCenters[i] = matrix.mult(new PVector(x,y,z), null);
// If it's an unmapped shape, make it dark
if (!all.contains(i)) {
shape.setFill(#303030);
}
// shape.setStroke(true);
shape.setStroke(#303030);
shape.setStrokeWeight(2f);
}
sheepModel.rotateY(PI*1.5);
sheepModel.rotateZ(PI);
//sheepModel.translate(SHEEP_SCALE * 30, SHEEP_SCALE * 10, SHEEP_SCALE * 550); // Shit, still in model coord space. Lame!
sheepModel.translate(SHEEP_SCALE * 550, SHEEP_SCALE * 0, SHEEP_SCALE * 30); // Shit, still in model coord space. Lame!
leftEye = new Eye(app, "Left eye", 400,
new PVector(SHEEP_SCALE * -135, SHEEP_SCALE * -215, SHEEP_SCALE * 27),
22, 0, 160);
rightEye = new Eye(app, "Right eye", 416,
new PVector(SHEEP_SCALE * -135, SHEEP_SCALE * -215, SHEEP_SCALE * -27),
-22, 150, 160);
}
void setPanelColor(String side, int panel, color c) {
ArrayList<Integer> polygons = new ArrayList<Integer>();
if (side.equals("p") && partySideMap.get(panel) != null) {
polygons.addAll(partySideMap.get(panel));
} else if (side.equals("b") && boringSideMap.get(panel) != null) {
polygons.addAll(boringSideMap.get(panel));
} else if (side.equals("a")) {
if (partySideMap.get(panel) != null) {
polygons.addAll(partySideMap.get(panel));
}
if (boringSideMap.get(panel) != null) {
polygons.addAll(boringSideMap.get(panel));
}
} else if (side.equals("f")) {
Iterator partyItr = partySideMap.keySet().iterator();
while (partyItr.hasNext()) {
polygons.addAll(partySideMap.get(partyItr.next()));
}
Iterator boringItr = boringSideMap.keySet().iterator();
while (boringItr.hasNext()) {
polygons.addAll(boringSideMap.get(boringItr.next()));
}
}
if (polygons != null && polygons.size() > 0) {
for (Integer polygon : polygons) {
if (polygon != null && polygon != -1) {
sheepPanelArray[polygon].setFill(c);
}
}
} else {
System.out.println("Panel "+panel+" side="+side+" number was not found in map. Bypassing command.");
}
} // end setPanelColor
public void moveCursor(int direction) {
int prev = panelCursor;
panelCursor += direction;
if (panelCursor >= sheepPanelArray.length) {
panelCursor -= sheepPanelArray.length;
} else if (panelCursor < 0) {
panelCursor += sheepPanelArray.length;
}
}
public void moveLogicalCursor(int direction) {
if (logicalCursor < 0) {
logicalCursor = 1;
return;
}
int max = partySideMap.lastKey();
logicalCursor += direction;
if (logicalCursor < 1) {
logicalCursor += max;
} else if (logicalCursor > max) {
logicalCursor -= max;
}
}
void setShowLabels(boolean shouldDraw) {
showLabels = shouldDraw;
// Set all the surfaces to stroke
for(PShape shape: sheepPanelArray) {
if (shape==null) continue;
shape.setStroke(showLabels);
if (showLabels) {
shape.setStroke(#303030);
shape.setStrokeWeight(2f);
}
}
}
/**
* Set all of the panels (including non-mapped ones) to colors from a contrasting list.
* This isn't pretty, but it lets you see where the panels are instead of just
* the surfaces (which you can see with showLabels mode)
*/
public void setContrastingColors() {
int[] divColors = {
color(141,211,199),color(255,255,179),color(190,186,218),color(251,128,114),color(128,177,211),color(253,180,98),color(179,222,105),color(252,205,229),color(217,217,217),color(188,128,189),color(204,235,197),color(255,237,111)
};
int cIx = 0;
for(Map.Entry e: partySideMap.entrySet()) {
int logIx = (Integer)e.getKey();
ArrayList<Integer> list;
// First the party side
list = (ArrayList<Integer>)e.getValue();
for(int sIx: list) {
try {
PShape panel = sheepPanelArray[sIx];
panel.setFill(divColors[cIx]);
} catch (Exception ex) {
// ignore
}
}
// Then the bidness
list = (ArrayList<Integer>)boringSideMap.get(logIx);
if (list != null) {
for(int sIx: list) {
try {
PShape panel = sheepPanelArray[sIx];
panel.setFill(divColors[cIx]);
} catch (Exception ex) {
// ignore
}
}
}
cIx++;
if (cIx == divColors.length) {
cIx = 0;
}
}
}
public void draw() {
// Wrapping this draw in push & pop matrix means that any
// visual changes we make here (such as camera position,
// rotation, etc.) won't be left hanging around when this
// function returns.
pushMatrix();
// To draw the sheep using the current style of every panel,
// we only need a single shape call.
shape(sheepModel);
// The eyes also have to get rendered. They are not part of
// the loaded model but were added later
leftEye.draw();
rightEye.draw();
// Finally (for the sheep at least) we re-render one surface
// in a different color. Do this by re-rendering rather than
// changing the style so that we don't loose the style for this
// surface just because the cursor traversed it.
PShape curPanel = sheepPanelArray[panelCursor];
if (curPanel != null) {
sheepPanelArray[panelCursor].disableStyle();
fill(0xff00ff00);
shape(sheepPanelArray[panelCursor]);
sheepPanelArray[panelCursor].enableStyle();
}
// Logical panel cursor
if (logicalCursor != -1 && showLogicalHighlight) {
paintList(partySideMap.get(logicalCursor), true);
paintList(boringSideMap.get(logicalCursor), false);
}
// For all panels, draw their logical name on the first element of them
if (showLabels) {
pushMatrix();
fill(#ff0000);
textAlign(CENTER, CENTER);
textSize(14);
drawLabels(true);
drawLabels(false);
popMatrix();
}
// This restores the matrix that was pushed at the beginning of
// this function.
popMatrix();
}
void paintList(ArrayList<Integer> list, boolean isParty) {
if (list==null) return;
for(int i=0; i<list.size(); i++) {
int surfaceIx = list.get(i);
if (surfaceIx < 0 || surfaceIx > sheepPanelArray.length - 1) continue;
PShape shape = sheepPanelArray[surfaceIx];
if (shape == null) continue;
shape.disableStyle();
fill(isParty ? 0xFFFF0000 : 0xFF0000FF);
shape(shape);
shape.enableStyle();
}
}
void drawLabels(boolean isParty) {
colorMode(HSB, 255);
StringBuffer buf = new StringBuffer();
for(Map.Entry e: (isParty ? partySideMap.entrySet() : boringSideMap.entrySet()) ) {
int logical = (Integer)e.getKey();
ArrayList<Integer> list = (ArrayList<Integer>)e.getValue();
if (list.size() < 1) continue;
buf.setLength(0);
buf.append(logical);
buf.append(isParty ? "p" : "b");
for(int j=0; j<list.size(); j++) {
int sIx = list.get(j);
if (j==0) {
textSize(14);
} else {
textSize(8);
}
PVector center = panelCenters[sIx];
if (center != null) {
PShape shape = sheepPanelArray[sIx];
if (shape!=null) {
color fc = shape.getFill(0);
float h = hue(fc) + 0.5f;
if (h > 1.0f) h-=0.5f;
color nc = color(255 * h, 255 * saturation(fc), 255 * brightness(fc));
fill(nc);
}
float x = center.x;
if (logical > 49 && logical < 60) {
// Tail move right
x += 20;
} else if (logical > 60) {
// Head move left
x -= 20;
} else if (logical > 0 && logical < 4) {
// front shoulder region a little left
x -= 10;
} else if (logical > 31) {
// Rump section, go right
x += 20;
}
float z = center.z + (isParty ? 10 : -10);
text(buf.toString(), x, center.y, z);
}
}
}
colorMode(RGB, 255);
}
/*******************************************************************************/
/*******************************************************************************/
/*******************************************************************************/
/**
* An instance of Eye represents one of the DMX controlled sharpies. Using 16 channel
* mode.
*/
class Eye {
// These are all byte values, but because of not having unsigned ints
// in java it is easiest to make them ints and then treat them as byte range (0-255)
// instead of trying to deal with signed bytes.
//
// There are only 16 channels, but DMX is 1 based, not 0 based, in all the manuals, so
// to preserve numbering we effectively make these 1-based indexes by expanding their
// size.
int[] channelValues = new int[17];
// We mostly need these for PAN that can wrap at 540 degrees of movement, but just
// keep them for all
int[] currentValues = new int[17];
boolean dmxDirty;
int dmxOffset;
Cone cone;
StopWatch watch;
final float ANIMATION_TIME = 10.0f;
final float BEAM_LENGTH = SHEEP_SCALE * 5000;
final float BEAM_MAX_SPOT = SHEEP_SCALE * 50;
boolean nextIsPan;
final int DMX_PAN = 3;
final int DMX_PAN_FINE = 13;
final int DMX_TILT = 4;
final int DMX_TILT_FINE = 14;
final int DMX_COLOR_WHEEL = 6;
final int DMX_STROBE = 2;
final int DMX_DIMMER = 1;
final int DMX_GOBO = 7;
final int DMX_EFFECT = 8;
final int DMX_LADDER_PRISM = 10;
final int DMX_8_FACET_PRISM = 0;
final int DMX_3_FACET_PRISM = 0;
final int DMX_FOCUS = 11;
final int DMX_FROST = 12;
final int DMX_PAN_TILT_SPEED = 5;
final int DMX_RESET_AND_LAMP = 16;
String me;
PVector position;
float offsetDegrees;
Anchor anchor;
GLabel lblColor;
GLabel lblStrobe;
GLabel lblGobo;
GLabel lblEffect;
GLabel lblLadder;
GLabel lbl8Facet;
GLabel lbl3Facet;
GLabel lblFocus;
GLabel lblFrost;
GLabel lblPNTSpeed;
GLabel lblReset;
Eye(PApplet app, String me, int dmx, PVector position, float offsetDegrees, int labelX, int labelY) {
this.me = me;
this.dmxOffset = dmx;
this.position = position;
this.offsetDegrees = offsetDegrees;
///////
// The labels for things we don't otherwise simulate visually
final int LBL_HEIGHT=15;
final int LBL_WIDTH = 150;
final int ROW_HEIGHT = 20;
int y = labelY;
lblColor = new GLabel(app, labelX, y, LBL_WIDTH, LBL_HEIGHT, "color");
y += ROW_HEIGHT;