Halloween kam dieses Jahr schnell. Letztes Jahr habe ich verwendet dieses Raspberry Pi-Projekt um einen Abend voller Unterhaltung zu verbringen. Dieses Jahr habe ich JMP und ein verwendet Arduino Nano . Mehr über den Nano weiter unten.
Seitdem ich mit spiele das Bild der Erde auf eine Kugel wickeln Ich fragte mich, ob ich stattdessen eine ähnliche Totenkopftextur zum Einwickeln finden könnte. Ich habe etwas viel Besseres gefunden: das hier Grundstück verfügt über 3D-Modelle. Dieses Projekt verwendet das Modell, auf das der Link verweist. Vielen Dank an jemanden, der diesen Namen verwendet druckbare_Modelle.
Die angehängte Datei ist die vollständige JSL; Die Ausschnitte in diesem Blog sind unvollständig und stimmen möglicherweise nicht ganz überein.
In der ZIP-Datei (zum Öffnen das ZIP-Archiv von JMP verwenden):
</CODE></DIR></PRE><P><STRONG>{"Skull_v3_L2.123c1407fc1e-ea5c-4cb9-9072-d28b8aba4c36/",</STRONG><BR /><STRONG> „Skull_v3_L2.123c1407fc1e-ea5c-4cb9-9072-d28b8aba4c36/Skull.jpg“,</STRONG><BR /><STRONG> „Skull_v3_L2.123c1407fc1e-ea5c-4cb9-9072-d28b8aba4c36/12140_Skull_v3_L2.obj“,</STRONG><BR /><STRONG> „Skull_v3_L2.123c1407fc1e-ea5c-4cb9-9072-d28b8aba4c36/12140_Skull_v3_L2.mtl“}</STRONG></P><P> </P><P>Das sind drei Dateien und ein Verzeichnis, in dem sie gespeichert sind. Ich bin nicht sicher, was die .mtl-Datei bewirkt, und in diesem Beitrag geht es hauptsächlich um die .obj-Datei. Das .jpg ist wirklich interessant, wird später verwendet und sieht so aus:</P><PRE><CODE class=" language-jsl">jpgblob=za<<READ></READ>za = open("e:/Skull_v3_L2.123c1407fc1e-ea5c-4cb9-9072-d28b8aba4c36.zip",zip);
members = za<
Texture map for skull, 1/4 sizeTexturkarte für Schädel, 1/4 Größe
Die OBJ-Datei enthält den Rest der Daten: 3D-Punkte und ihre 2D-Texturkartenkoordinaten. Es gibt eine Erklärung des Dateiformats Hier . Die Datei enthält v- Abschnitte von 3D-Koordinaten, vt- Abschnitte von Texturkartenkoordinaten, vn- Abschnitte von Normalenvektoren, F- Abschnitte von Gesichtern (3- oder 4-Seiten-Polygone) und ein paar andere Elemente.
# 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware
# File Created: 21.12.2011 14:29:19
mtllib 12140_Skull_v3_L2.mtl
#
# object 12140_Skull_v3_jaw
#
v -1.6458 -14.7959 1.0976
v -1.2548 -14.9644 1.0679
...
v 0.8637 -15.1231 0.7422
v 0.4355 -15.2145 0.7175
# 6114 vertices
vn -0.4113 -0.8998 0.1457
vn -0.3415 -0.9275 0.1520
...
vn 0.2578 -0.9662 0.0017
vn 0.1450 -0.9890 -0.0288
# 6114 vertex normals
vt 0.2309 0.7002 0.0000
vt 0.2363 0.7000 0.0000
...
vt 0.2632 0.6958 0.0000
vt 0.2578 0.6955 0.0000
# 6434 texture coords
g 12140_Skull_v3_jaw
usemtl 12140_Skull_v3
s 2
f 1/1/1 2/2/2 3/3/3 4/4/4
f 2/2/2 5/5/5 6/6/6 3/3/3
f 7/7/7 8/8/8 3/3/3 6/6/6
f 8/8/8 9/9/9 4/4/4 3/3/3
f 5/5/5 10/10/10 11/11/11 6/6/6
f 10/10/10 12/12/12 13/13/13 11/11/11
f 14/14/14 15/15/15 11/11/11 13/13/13
...
f 6109/6429/6109 6108/6428/6108 6113/6433/6113 6114/6434/6114
f 3086/3247/3086 3097/3258/3097 6114/6434/6114 6113/6433/6113
f 3097/3258/3097 12/12/12 3072/3233/3072 6114/6434/6114
# 6112 polygons
#
# object 12140_Skull_v3_teeth
#
...
Oben eine sehr gekürzte Kopie der Datei mit #Kommentaren, Leerzeilen, einer dreieckigen Fläche mit drei Punkten und einer Reihe von viereckigen Flächen sowie einigen g- und s-Datensätzen, die ich ignoriere.
JSL kann diese mithilfe eines Mustervergleichs effizient lesen. Fügen Sie zunächst das OBJ in einen String ein:
obj=za<<READ></READ>
String( "# 3ds Max ... 6171025 Gesamtzeichen ...lygons
" ) zugewiesen.
Dann analysieren Sie es. Richten Sie einige Listen ein, um die Datenwerte beim Parsen zu speichern, und erstellen Sie einige Hilfsmuster:
vertex = {};
normal = {};
texture = {};
quad = {};
triangle = {};
seg={};
number=patspan("0123456789-.");
threenums = number>>n1+" "+number>>n2+" "+number>>n3+patspan(" \!n\!r");
integer = patspan("0123456789");
threetriples =
integer>>n1+"/"+integer>>n2+"/"+integer>>n3+patspan(" ")+
integer>>n4+"/"+integer>>n5+"/"+integer>>n6+patspan(" ")+
integer>>n7+"/"+integer>>n8+"/"+integer>>n9;
fourthtriple =
integer>>n10+"/"+integer>>n11+"/"+integer>>n12;Einige der Gesichter sind Dreiecke, andere sind Vierecke. Der dreidreifach Und viertes Dreifach werden unten verwendet, um das Gesicht effizient zu analysieren und in der richtigen Liste zu speichern: Quad oder Dreieck.
objpat =
("f"+patspan(" ")+
// in this file, the "f" records all look like one of these:
// f 10714/11419/10714 10713/11418/10713 10658/11363/10658 <<< trailing space
// or
// f 39694/42282/39694 39692/42679/39692 40060/42677/40060 39722/42283/39722 <<< trailing space
threetriples+
(
(patspan(" ")+fourthtriple+patspan(" \!n\!r")+ //
pattest(insertinto(quad,evallist({evallist({num(n1),num(n2),num(n3),num(n4),num(n5),num(n6),num(n7),num(n8),num(n9),num(n10),num(n11),num(n12)})}));1))
| //
(patspan(" \!n\!r")+ //
pattest(insertinto(triangle,evallist({evallist({num(n1),num(n2),num(n3),num(n4),num(n5),num(n6),num(n7),num(n8),num(n9)})}));1))
)
) | // in this file, the v, vt, vn records have x,y,z values
("v"+patspan(" ")+threenums+pattest(insertinto(vertex,evallist({evallist({num(n1),num(n2),num(n3)})}));1))|
("vt"+patspan(" ")+threenums+pattest(insertinto(texture,evallist({evallist({num(n1),num(n2),num(n3)})}));1))|
("vn"+patspan(" ")+threenums+pattest(insertinto(normal,evallist({evallist({num(n1),num(n2),num(n3)})}));1))|
("g"+patspan(" ")+
patbreak("\!n\!r")>>SegName+
pattest(insertinto(seg,// 2 is quads, 3 is triangles, 1 is name (mostly unused)
evallist({evallist({segname,nitems(quad),nitems(triangle)})}));
1)+
patspan("\!n\!r")
) |
("#"+patbreak("\!n\!r")+patspan("\!n\!r")) |
(patbreak("\!n\!r")>>log("unused data")+patspan("\!n\!r"));Objpat , oben definiert, enthält ein Muster; Es wurde noch nichts analysiert. Objpat teilt dem Mustervergleicher (unten) mit, wie eine einzelne Zeile in der OBJ-Datei abgeglichen werden soll. Hier ist der Aufruf des Mustervergleichs mit etwas mehr Mustervergleichs-JSL, damit der Vergleich alle Datensätze in der Datei durchläuft.
Es heißt: Verwendung des zuvor geladenen Textes obj , beginnen Sie an der linken Position 0 und verwenden Sie wiederholt die objpat Muster bis zur rechten Position 0. Nach jedem Erfolg objpat , grenzen Sie die bereits erledigte Arbeit ein, damit JMP keine zusätzlichen Aufzeichnungen durchführen muss.
rc = patmatch(obj,
patpos(0)+
patrepeat(objpat+patfence())+
patrpos(0)
);
show(rc);
99 (unbenutzte Daten) mtllib 12140_Skull_v3_L2.mtl
487834 (unbenutzte Daten) usemtl 12140_Skull_v3
487857(unbenutzte Daten) s 2
1305132 (unbenutzte Daten) usemtl 12140_Skull_v3
1305155(unbenutzte Daten) s 1
2038681 (unbenutzte Daten) usemtl 12140_Skull_v3
2038704(unbenutzte Daten) s 1
4326305 (unbenutzte Daten) usemtl 12140_Skull_v3
4326328(unbenutzte Daten) s 2
rc = 1;
rc==1 ist eine gute Sache, das Muster stimmte von Anfang bis Ende überein. Die Funktion log() in objpat erstellt die Zeilen über die nicht verwendeten Daten; Es sieht so aus, als wären diese Datenleitungen nicht nützlich. Und dabei hat der Matcher diese JSL für jeden ausgeführt vn Datensatz (und ähnliches für die anderen Datensätze):
pattest(insertinto(normal,evallist({evallist({num(n1),num(n2),num(n3)})}));1)was die JSL-Liste füllte normal mit allen Scheitelpunktnormalen. (Normale Vektoren teilen OpenGL mit, in welche Richtung Licht von einer Oberfläche reflektiert wird.) Das ;1 am Ende teilt pattest() mit, dass es erfolgreich sein sollte, damit der Matcher sich weiter vorwärts bewegt.
Die Scheitelpunktliste ist eine Liste von Listen mit drei Elementen. Die Elemente sind x-, y- und z-Koordinaten:
{{-1.6458, -14.7959, 1.0976},
{-1.2548, -14.9644, 1.0679},
{-1.2202, -14.8974, 1.4306},
{-1.6069, -14.7373, 1.4505},
{-0.8477, -15.1013, 1.036},
{-0.8223, -15.0288, 1.403},
{-0.7966, -14.9354, 1.8162},
{-1.1896, -14.811, 1.8424},
{-1.5761, -14.6593, 1.8511},
{-0.4272, -15.1961, 1.01},
{-0.4138, -15.1209, 1.3778},
{-0.0001, -15.2327, 0.9984},
{-0.0001, -15.1568, 1.3658},
{0, -15.0543, 1.7793},
{-0.3993, -15.0212, 1.7914},
...Die Liste der Gesichter ist in Dreiecke und Quader unterteilt. Die Dreiecksliste ist auch eine Liste von Listen:
{{6148, 6468, 6148, 6150, 6470, 6150, 6151, 6471, 6151},
{6149, 6469, 6149, 6148, 6468, 6148, 6151, 6471, 6151},
{6174, 6494, 6174, 6175, 6495, 6175, 6151, 6471, 6151},
{6150, 6470, 6150, 6174, 6494, 6174, 6151, 6471, 6151},
{6198, 6518, 6198, 6199, 6519, 6199, 6151, 6471, 6151},
{6175, 6495, 6175, 6198, 6518, 6198, 6151, 6471, 6151}, Diese drei Tripel sind Indizes für die Vertex-, Textur- und Normallisten. Die Texturliste verwendet Koordinaten von 0 bis 1, um eine Farbe aus der Texturkarte oben auszuwählen. Die JSL-Liste Seg hat vier Elemente, jedes eine Liste mit einem Segmentnamen (Schädel, Kiefer, obere Zähne, untere Zähne) und Startpositionen in der Dreieck Und Quad Listen.
Diese JSL lädt die Daten in JSL SceneDisplayLists für jeden der Seg Artikel:
// load model into OpenGL
// seg describes 4 segments (jaw, top teeth, bot teeth, skull)
// for Halloween I want two segments, skull and jaw, with the
// teeth welded on already.
// so, load the segs, then recombine them...
displaylists = {};
for(iDisplayList=1,iDisplayList<=nitems(seg),iDisplayList +=1,
partial = Scene Display List();
partial << BEGIN( QUADS );
// the seg notes its startinq quad/triangle, use the next seg
// to find this segs end. or the rest, for the last seg.
// 2 is quads, 3 is triangles
lastquad = if(iDisplayList < nitems(seg),seg[iDisplayList+1][2],nitems(quad));
for(i=seg[iDisplayList][2]+1,i<=lastquad,i+=1,
for(q=0,q<12,q+=3,
partial << Normal( normal[quad[i][q+3]][1], normal[quad[i][q+3]][2], normal[quad[i][q+3]][3] );
tx=texture[quad[i][q+2]][1];
ty=texture[quad[i][q+2]][2];
c=jpg[(1-ty)*(nrows(jpg)-1)+1,(tx)*(ncols(jpg)-1)+1];
partial << Color( c );
partial << Vertex( vertex[quad[i][q+1]][1], vertex[quad[i][q+1]][2], vertex[quad[i][q+1]][3] );
);
);
partial << END;
partial << BEGIN( TRIANGLES );
lasttriangle = if(iDisplayList < nitems(seg),seg[iDisplayList+1][3],nitems(triangle));
for(i=seg[iDisplayList][3]+1,i<=lasttriangle,i+=1,
for(t=0,t<9,t+=3,
partial << Normal( normal[triangle[i][t+3]][1], normal[triangle[i][t+3]][2], normal[triangle[i][t+3]][3] );
tx=texture[quad[i][t+2]][1];
ty=texture[quad[i][t+2]][2];
c=jpg[(1-ty)*(nrows(jpg)-1)+1,(tx)*(ncols(jpg)-1)+1];
partial << Color( c );
partial << Vertex( vertex[triangle[i][t+1]][1], vertex[triangle[i][t+1]][2], vertex[triangle[i][t+1]][3] );
);
);
partial << END;
displaylists[iDisplayList] = partial;
);Das ist die gleiche Logik, zweimal, einmal um Dreiecke zu machen und einmal um Quads zu machen.
Diese JSL hat aus den vier Teilen vier Anzeigelisten erstellt; Die angehängte Datei kombiniert und animiert sie und fügt einige Augäpfel im Halloween-Stil hinzu. Zeit für ein Bild...
Halloween early evening, model in windowHalloween am frühen Abend, Modell im Fenster
Sieht es so aus, als würden die Augen dem Fotografen folgen? Was ist mit der Lücke am unteren Rand des Fensters?
Arduino Nano- und PIR-Sensoren
Damit die Augen Menschen verfolgen können, ist eine Art Sensor erforderlich.PIR-Sensoren haben eine Single-Bit-Schnittstelle (etwas bewegt!) und einige Einschränkungen bei der Reaktionszeit. Angesichts der geringen Wahlbeteiligung an Halloween waren sie die richtige Wahl. Eine Raspberry PI-Kamera könnte eine bessere Leistung bieten. Der Spalt unterhalb des Fensters ermöglicht den Einbau von fünf PIR-Sensoren Kollimatoren um Regionen zu beobachten, in denen Menschen laufen könnten. Hier ist das Setup:
They do like like eyes. Those collimator tubes are re-purposed.Sie mögen solche Augen. Diese Kollimatorrohre werden einem neuen Zweck zugeführt.
Und so sieht die Baugruppe aus, geklebt, heißgeklebt und mit dem Arduino Nano verbunden. Der Nano ist klein.
Arduino Nano on left under zip-ties. PIR sensors on right hot-glued to collimators. There are two orange adjustments on each PIR.Arduino Nano links unter Kabelbindern. PIR-Sensoren rechts auf Kollimatoren heißgeklebt. An jedem PIR gibt es zwei orangefarbene Anpassungen.
Der Nano-Klon wurde mit Stiftleisten geliefert, die angelötet werden können, oder es können Drähte an den Nano angelötet werden. Das Schneiden von Überbrückungsdrähten in zwei Hälften und das Löten eines Endes macht es etwas unwahrscheinlicher, dass sie auseinander fallen und ein wenig wahrscheinlicher, dass sie in einem anderen Projekt wiederverwendet werden.
Nano has wires soldered on, orange is +5, green is ground, purple/gray/blue are data linesNano hat Drähte angelötet, Orange ist +5, Grün ist Masse, Lila/Grau/Blau sind Datenleitungen
Hier ist der Code, der auf dem Nano läuft; Verwenden Sie die Arduino IDE zum Kompilieren und Herunterladen über die USB-Verbindung. Die IDE macht es einfach, die COM-Anschlussnummer zu ermitteln, die dem USB-Anschluss zugewiesen ist. Das brauchen Sie auch in der JSL. Der Anhang hat eine zusätzliche Erweiterung „.txt“. Benennen Sie ihn um, sodass er nur die Erweiterung „.ino“ erhält.
Arduino IDE showing code to read sensors and send to JMPArduino IDE zeigt Code zum Lesen von Sensoren und zum Senden an JMP
Die Setup-Routine wird einmal ausgeführt; die Schleifenroutine wird immer wieder aufgerufen, für immer. Delay(100) wartet 1/10 Sekunde, bevor die Schleife erneut ausgeführt wird. 2,3,4,5,6 sind die Pins, die mit den Sensoren verbunden sind. Alles, was über die serielle Schnittstelle gedruckt wird, kann von JMP mithilfe des DataFeed gelesen werden. Hier ist ein weiteres Snippet der JSL dafür, mit der Wort () Funktion, um die fünf Sensorwerte aus der Datenzeile zu erhalten:
sensorBits = [0, 0, 0, 0, 0];
feed = Open Datafeed(
Connect( Port( "com4" ), Baud rate( 115200 ), Parity( none ), DataBits( 8 ) ),
Set Script(
Local( {i, txt, temp = sensorBits},
txt = feed << getLines;
for(iline=1,iline<=nitems(txt),iline+=1,
For( i = 1, i <= N Rows( sensorBits ), i += 1,
temp[i] += Num( Word( i, txt[iline], "," ) )
);
If( Any( Is Missing( temp ) ),
Show( temp,chartoblob(txt[iline]) ),
sensorBits = temp * .9;
try(tb<<SETTEXT></SETTEXT>Der Datafeed muss korrekt geschlossen werden oder er findet den Port beim nächsten Mal immer noch belegt; Das ist es, was die OnClose-Logik tut. Alles andere besteht nur darin, das zu behalten sensorBits Matrix wird aktualisiert, indem sie erhöht wird, wenn ein Sensorbit eingeschaltet ist, und ausgeblendet wird, wenn es ausgeschaltet ist. Die Sensoren bleiben mindestens drei Sekunden lang eingeschaltet und die Zählwerte steigen auf etwa 10 und sinken ziemlich schnell wieder auf nahezu Null ab.
Das ist alles; Es gibt eine Schleife in der JSL (Anhang) gegen Ende, die die Augen und das Modell als Reaktion auf die Sensoren dreht ... und erkennt, wenn der Schädel gelangweilt ist, und einige Animationen ausführt, um Süßes oder Saures anzulocken. Die JSL wird wahrscheinlich auch ohne Verbindung mit dem Datenfeed laufen; Es könnte mit dem COM-Port unzufrieden sein, und wenn es ein zweites Mal ausgeführt wird, könnte es wirklich unzufrieden sein. Es sollte einfach sein, den COM-Port auszukommentieren.
Die Tracking-Augäpfel funktionieren mit mehreren Sekunden Verzögerung. Es hilft zu wissen, wo man stehen muss. Die Animationen machten es dem Publikum schwer zu erkennen, dass sie gesehen wurden.
Außerdem unterstützt JMP derzeit keine Texturzuordnung (JMP 15), daher ist dies etwas langsam. Mit einem älteren Laptop erreichte ich immer noch 15 FPS, gut genug für Halloween.
8pm, about to rain. Candy Corn half gone, glow sticks popular, vampire teeth and bubbles too!20 Uhr, es regnet gleich. Candy Corn ist zur Hälfte verschwunden, Leuchtstäbe beliebt, Vampirzähne und Blasen auch!
Und dieser Projektor ist hell! Hier ist der spezielle Projektorständer, der auf einen durchscheinenden Fenstervorhang projiziert:
Not recommended if you have a cat.Nicht empfohlen, wenn Sie eine Katze haben.
Und, Vorheriger Beitrag zur Halloween-Trilogie.
short loop of animation possibilitykurze Animationsschleife möglich