Halloween è arrivato velocemente quest'anno. L'anno scorso ho usato questo progetto Raspberry Pi per ottenere una notte di divertimento. Quest'anno ho utilizzato JMP e un file Arduino Nano . Maggiori informazioni sul nano, molto sotto.
Da quando gioco con avvolgendo l'immagine della terra su una sfera , mi chiedevo se avrei potuto trovare invece una trama del cranio simile da avvolgere. Ho trovato qualcosa di molto meglio: questo luogo ha modelli 3D. Questo progetto utilizza il modello a cui punta il collegamento. Grazie a qualcuno che usa il nome modelli_stampabili.
Il file allegato è il JSL completo; i frammenti in questo blog sono incompleti e potrebbero non corrispondere del tutto.
All'interno del file zip (usa l'archivio zip di JMP per aprirlo):
</CODE></DIR></PRE><P><STRONG>{"Teschio_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>Sono tre file e una directory per contenerli. Non sono sicuro di cosa faccia il file .mtl e questo post riguarda principalmente il file .obj. Il .jpg è davvero interessante e viene utilizzato in seguito e ha questo aspetto:</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 sizeTexture map for skull, 1/4 sizeTexture map for skull, 1/4 sizeTexture map for skull, 1/4 sizeMappa delle texture per il cranio, dimensione 1/4
Il file OBJ contiene il resto dei dati: punti 3D e le loro coordinate della mappa texture 2D. C'è una spiegazione del formato del file Qui . Il file contiene v- sezioni di coordinate 3D, vt- sezioni di coordinate della mappa delle texture, vn- sezioni di vettori normali, F- sezioni di facce (3 o 4 poligoni laterali) e pochi altri elementi.
# 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
#
...
Sopra, una copia molto ridotta del file che mostra #commenti, righe vuote, una faccia triangolare con tre punte e un mucchio di facce quadruple, e alcuni record g e s che ignorerò.
JSL può leggerli in modo efficiente con una corrispondenza di pattern. Innanzitutto, inserisci l'OBJ in una stringa:
obj=za<<READ></READ>
String( "# 3ds Max ... 6171025 caratteri totali ...lygons
" ) assegnato.
Quindi analizzalo. Imposta alcuni elenchi per contenere i valori dei dati mentre vengono analizzati e crea alcuni modelli di supporto:
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;alcune delle facce sono triangoli, altre sono quad. IL tretriple E quartotriplo sono usati di seguito per analizzare e memorizzare in modo efficiente la faccia nell'elenco corretto: quad o triangolo.
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"));Oggetto , definito sopra, contiene un pattern; non è stato ancora analizzato nulla. Oggetto dice al pattern matcher (sotto) come abbinare una singola riga nel file OBJ. Ecco la chiamata al pattern matcher, con un po' più JSL di pattern matching per fare in modo che il matcher scorra tutti i record nel file.
Dice: usando il testo precedentemente caricato in ogg , inizia dalla posizione sinistra 0 e usa ripetutamente il objpat motivo fino alla posizione destra 0. Dopo ogni successo objpat , recintare il lavoro già svolto in modo che JMP non debba tenere registri aggiuntivi.
rc = patmatch(obj,
patpos(0)+
patrepeat(objpat+patfence())+
patrpos(0)
);
show(rc);
99 (dati non utilizzati) mtllib 12140_Skull_v3_L2.mtl
487834(dati non utilizzati) usemtl 12140_Skull_v3
487857(dati non utilizzati) s 2
1305132(dati non utilizzati) usemtl 12140_Skull_v3
1305155(dati non utilizzati) s 1
2038681(dati non utilizzati) usemtl 12140_Skull_v3
2038704(dati non utilizzati) s 1
4326305(dati non utilizzati) usemtl 12140_Skull_v3
4326328(dati non utilizzati) s 2
cr = 1;
rc==1 è una buona cosa, lo schema corrisponde dall'inizio alla fine. La funzione log() in objpat sta creando le linee sui dati inutilizzati; sembra che quelle linee di dati non saranno utili. E nel processo, il matcher ha eseguito questo JSL per ciascuno vn record (e simili per gli altri record):
pattest(insertinto(normal,evallist({evallist({num(n1),num(n2),num(n3)})}));1)che ha riempito l'elenco JSL normale con tutte le normali ai vertici. (I vettori normali dicono a OpenGL in quale direzione la luce rimbalzerà su una superficie.) Il ;1 alla fine dice a pattest() che dovrebbe avere successo in modo che il matcher continui ad andare avanti.
L'elenco dei vertici è un elenco di elenchi di 3 elementi; gli elementi sono le coordinate x, y, z:
{{-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},
...L'elenco Volti è suddiviso in triangoli e quadretti. L'elenco dei triangoli è anche un elenco di elenchi:
{{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}, quelle tre triple sono indici negli elenchi di vertici, texture e normali. L'elenco delle texture utilizza le coordinate 0..1 per scegliere un colore dalla mappa delle texture, molto sopra. L'elenco JSL seg ha quattro elementi, ciascuno un elenco contenente un nome di segmento (cranio, mascella, denti superiori, denti inferiori) e posizioni di partenza nel triangolo E quadrilatero elenchi.
Questo JSL carica i dati in JSL SceneDisplay Liste per ciascuno dei seg elementi:
// 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;
);È la stessa logica, due volte, una volta per fare triangoli e una volta per fare quad.
Quella JSL ha creato quattro elenchi di visualizzazione dalle quattro parti; il file allegato li combinerà e li animerà e aggiungerà alcuni bulbi oculari in stile Halloween. Tempo per una foto...
Halloween early evening, model in windowHalloween early evening, model in windowHalloween early evening, model in windowHalloween early evening, model in windowHalloween in prima serata, modello in finestra
Sembra che gli occhi seguano il fotografo? Cos'è lo spazio vuoto nella parte inferiore della finestra?
Sensori Arduino Nano e PIR
Per fare in modo che gli occhi seguano le persone, è necessaria una sorta di sensore.Sensori PIR hanno un'interfaccia a bit singolo (qualcosa si è mosso!) e alcune limitazioni sui tempi di risposta. Data la scarsa affluenza ad Halloween, sono stati la scelta giusta. Una fotocamera Raspberry PI potrebbe fornire prestazioni migliori. Lo spazio sotto la finestra consente cinque sensori PIR con collimatori per osservare le regioni in cui le persone potrebbero camminare. Ecco la configurazione:
They do like like eyes. Those collimator tubes are re-purposed.They do like like eyes. Those collimator tubes are re-purposed.They do like like eyes. Those collimator tubes are re-purposed.They do like like eyes. Those collimator tubes are re-purposed.A loro piacciono gli occhi. Quei tubi collimatori sono stati riutilizzati.
Ed ecco come appare l'assembley, registrato, incollato a caldo e cablato con un ponticello all'Arduino Nano. Il Nano è piccolo.
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 on left under zip-ties. PIR sensors on right hot-glued to collimators. There are two orange adjustments on each PIR.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 on left under zip-ties. PIR sensors on right hot-glued to collimators. There are two orange adjustments on each PIR.Arduino Nano a sinistra sotto le fascette. Sensori PIR sulla destra incollati a caldo ai collimatori. Ci sono due regolazioni arancioni su ciascun PIR.
Il clone Nano è dotato di pin di intestazione che possono essere saldati o fili possono essere saldati al Nano. Tagliare a metà i fili dei ponticelli e saldare un'estremità rende un po 'meno probabile che cadano a pezzi e un po' più probabilità che vengano riutilizzati in un altro progetto.
Nano has wires soldered on, orange is +5, green is ground, purple/gray/blue are data linesNano has wires soldered on, orange is +5, green is ground, purple/gray/blue are data linesNano has wires soldered on, orange is +5, green is ground, purple/gray/blue are data linesNano has wires soldered on, orange is +5, green is ground, purple/gray/blue are data linesNano ha fili saldati, arancione è +5, verde è terra, viola/grigio/blu sono linee dati
Ecco il codice che gira sul Nano; utilizzare l'IDE di Arduino per compilare e scaricare tramite la connessione USB. L'IDE semplifica l'individuazione del numero di porta COM assegnato alla porta USB. Ne avrai bisogno anche nel JSL. L'allegato ha un'estensione .txt aggiuntiva, rinominalo per avere solo l'estensione .ino.
Arduino IDE showing code to read sensors and send to JMPArduino IDE showing code to read sensors and send to JMPArduino IDE showing code to read sensors and send to JMPArduino IDE showing code to read sensors and send to JMPArduino IDE che mostra il codice per leggere i sensori e inviarlo a JMP
La routine di installazione viene eseguita una volta; la routine del ciclo viene chiamata ancora e ancora, per sempre. Delay(100) attende 1/10 di secondo prima che il ciclo venga eseguito di nuovo. 2,3,4,5,6 sono i pin collegati ai sensori. Qualsiasi cosa stampata sulla porta seriale può essere letta da JMP utilizzando il DataFeed. Ecco un altro frammento di JSL per questo, usando il file Parola () funzione per ottenere i cinque valori del sensore dalla linea dati:
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>Il datafeed deve essere chiuso correttamente o troverà la porta ancora occupata la prossima volta; questo è ciò che sta facendo la logica OnClose. Tutto il resto è solo mantenere il sensorBits matrice aggiornata incrementando se un bit del sensore è attivo e sfumando se è spento. I sensori rimangono accesi per almeno tre secondi e i conteggi salgono a circa 10 e tornano abbastanza rapidamente a quasi zero.
Questo è tutto; c'è un loop nel JSL (allegato), vicino alla fine, che ruota gli occhi e il modello in risposta ai sensori... e rileva quando il cranio è annoiato ed esegue alcune animazioni per attirare dolcetti o scherzetti. Il JSL probabilmente funzionerà con il datafeed anche senza nulla connesso; potrebbe non essere soddisfatto della porta com e potrebbe essere davvero insoddisfatto se eseguito una seconda volta. Dovrebbe essere facile commentare la porta com.
I bulbi oculari di tracciamento funzionano, con diversi secondi di ritardo. Aiuta a sapere dove stare. Le animazioni rendevano difficile per il pubblico sapere di essere guardato.
Inoltre, JMP attualmente non supporta realmente la mappatura delle texture (JMP 15), quindi è un po' lento. Usando un laptop più vecchio stavo ancora ottenendo 15 FPS, abbastanza buono per Halloween.
8pm, about to rain. Candy Corn half gone, glow sticks popular, vampire teeth and bubbles too!8pm, about to rain. Candy Corn half gone, glow sticks popular, vampire teeth and bubbles too!8pm, about to rain. Candy Corn half gone, glow sticks popular, vampire teeth and bubbles too!8pm, about to rain. Candy Corn half gone, glow sticks popular, vampire teeth and bubbles too!20:00, sta per piovere. Candy Corn mezzo andato, bastoncini luminosi popolari, denti da vampiro e anche bolle!
E quel proiettore è luminoso! Ecco lo speciale supporto per proiettore, che proietta su un paralume traslucido:
Not recommended if you have a cat.Not recommended if you have a cat.Not recommended if you have a cat.Not recommended if you have a cat.Non consigliato se hai un gatto.
E, Precedente post sulla trilogia di Halloween.
short loop of animation possibilityshort loop of animation possibilityshort loop of animation possibilityshort loop of animation possibilitybreve ciclo di possibilità di animazione