Halloween est venu vite cette année. L'année dernière, j'ai utilisé ce projet Raspberry Pi pour avoir une nuit de divertissement. Cette année, j'ai utilisé JMP et un Arduino Nano . Plus d'informations sur le nano, ci-dessous.
Depuis que je joue avec envelopper l'image de la terre sur une sphère , je me demandais si je pouvais trouver une texture de crâne similaire à envelopper à la place. J'ai trouvé quelque chose de bien mieux : ça placer a des modèles 3D. Ce projet utilise le modèle vers lequel pointe le lien. Merci à quelqu'un qui utilise le nom modèles_imprimables.
Le fichier joint est le JSL complet ; les extraits de ce blog sont incomplets et peuvent ne pas correspondre tout à fait.
Dans le fichier zip (utilisez l'archive zip de JMP pour l'ouvrir) :
</CODE></DIR></PRE><P><STRONG>{"Crâne_v3_L2.123c1407fc1e-ea5c-4cb9-9072-d28b8aba4c36/",</STRONG><BR /><STRONG> "Crâne_v3_L2.123c1407fc1e-ea5c-4cb9-9072-d28b8aba4c36/Crâne.jpg",</STRONG><BR /><STRONG> "Crâne_v3_L2.123c1407fc1e-ea5c-4cb9-9072-d28b8aba4c36/12140_Crâne_v3_L2.obj",</STRONG><BR /><STRONG> "Crâne_v3_L2.123c1407fc1e-ea5c-4cb9-9072-d28b8aba4c36/12140_Crâne_v3_L2.mtl"}</STRONG></P><P> </P><P>C'est trois fichiers et un répertoire pour les contenir. Je ne sais pas ce que fait le fichier .mtl, et cet article concerne principalement le fichier .obj. Le .jpg est vraiment intéressant, et il est utilisé plus tard, et ressemble à ceci :</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 sizeCarte de texture pour crâne, taille 1/4
Le fichier OBJ contient le reste des données : les points 3D et leurs coordonnées de carte de texture 2D. Il y a une explication du format de fichier ici . Le dossier contient v- sections de coordonnées 3D, Vermont- sections de coordonnées de carte de texture, vn- sections de vecteurs normaux, F- des sections de faces (polygones à 3 ou 4 côtés), et quelques autres éléments.
# 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
#
...
Ci-dessus, une copie très abrégée du fichier montrant des #commentaires, des lignes vides, une face triangulaire avec trois points et un tas de faces quadruples, et quelques enregistrements g et s que j'ignorerai.
JSL peut les lire efficacement avec une correspondance de modèle. Tout d'abord, récupérez l'OBJ dans une chaîne :
obj=za<<READ></READ>
String( "# 3ds Max ... 6171025 caractères au total ... lygons
" ) attribué.
Ensuite, analysez-le. Configurez des listes pour contenir les valeurs de données au fur et à mesure qu'elles sont analysées et créez des modèles d'assistance :
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;certaines faces sont des triangles, d'autres des quads. Le troistriples et quatrièmetriple sont utilisés ci-dessous pour analyser et stocker efficacement le visage dans la liste appropriée : quad ou triangle.
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 , défini ci-dessus, contient un motif ; rien n'a encore été analysé. Objpat indique au pattern matcher (ci-dessous) comment faire correspondre une seule ligne dans le fichier OBJ. Voici l'appel au comparateur de motifs, avec un peu plus de JSL de correspondance de motifs pour que le comparateur parcoure tous les enregistrements du fichier.
Il dit : en utilisant le texte précédemment chargé dans obj , commencez à la position gauche 0 et utilisez à plusieurs reprises objpat motif jusqu'à la position droite 0. Après chaque succès objpat , clôturez le travail qui a déjà été effectué afin que JMP n'ait pas à tenir des registres supplémentaires.
rc = patmatch(obj,
patpos(0)+
patrepeat(objpat+patfence())+
patrpos(0)
);
show(rc);
99 (données inutilisées) mtllib 12140_Skull_v3_L2.mtl
487834 (données inutilisées) usemtl 12140_Skull_v3
487857(données inutilisées) s 2
1305132 (données inutilisées) usemtl 12140_Skull_v3
1305155(données inutilisées) s 1
2038681 (données inutilisées) usemtl 12140_Skull_v3
2038704(données inutilisées) s 1
4326305 (données inutilisées) usemtl 12140_Skull_v3
4326328(données inutilisées) s 2
rc = 1 ;
rc==1 est une bonne chose, le modèle correspond du début à la fin. La fonction log() dans objpat crée les lignes sur les données inutilisées ; il semble que ces lignes de données ne seront pas utiles. Et dans le processus, le matcher a exécuté ce JSL pour chaque vn enregistrement (et similaire pour les autres enregistrements) :
pattest(insertinto(normal,evallist({evallist({num(n1),num(n2),num(n3)})}));1)qui a rempli la liste JSL normal avec toutes les normales aux sommets. (Les vecteurs normaux indiquent à OpenGL dans quelle direction la lumière rebondira sur une surface.) Le ;1 à la fin indique à pattest() qu'il doit réussir afin que le matcher continue d'avancer.
La liste de sommets est une liste de listes de 3 éléments ; les éléments sont les coordonnées 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},
...La liste Faces est séparée en triangles et quads. La liste triangulaire est aussi une liste de listes :
{{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}, ces trois triplets sont des index dans les listes de sommets, de textures et de normales. La liste des textures utilise les coordonnées 0..1 pour choisir une couleur dans la carte des textures, bien au-dessus. La liste JSL seg a quatre éléments, chacun une liste contenant un nom de segment (crâne, mâchoire, dents supérieures, dents inférieures) et des emplacements de départ dans le Triangle et quad listes.
Ce JSL charge les données dans JSL SceneDisplayLists pour chacun des seg articles:
// 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;
);C'est la même logique, deux fois, une fois pour faire des triangles, et une fois pour faire des quads.
Ce JSL a fait quatre listes d'affichage à partir des quatre parties ; le fichier joint les combinera et les animera et ajoutera des globes oculaires de style Halloween. Le temps d'une photo...
Halloween early evening, model in windowHalloween early evening, model in windowHalloween early evening, model in windowHalloween early evening, model in windowHalloween en début de soirée, modèle dans la fenêtre
Est-ce que les yeux suivent le photographe ? C'est quoi l'écart au bas de la fenêtre ?
Capteurs Arduino Nano et PIR
Pour que les yeux suivent les gens, une sorte de capteur est nécessaire.Capteurs IRP avoir une interface à un seul bit (quelque chose a bougé !) et certaines limitations sur le temps de réponse. Compte tenu du faible taux de participation à Halloween, ils étaient le bon choix. Une caméra Raspberry PI pourrait offrir de meilleures performances. L'espace sous la fenêtre permet à cinq capteurs PIR avec collimateurs pour surveiller les régions où les gens pourraient marcher. Voici la configuration :
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.Ils aiment les yeux. Ces tubes collimateurs sont réutilisés.
Et voici à quoi ressemble l'assemblage, scotché, collé à chaud et câblé à l'Arduino Nano. Le Nano est petit.
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 à gauche sous les attaches zippées. Capteurs PIR à droite collés à chaud sur les collimateurs. Il y a deux ajustements orange sur chaque PIR.
Le clone Nano est livré avec des broches d'en-tête qui peuvent être soudées ou des fils peuvent être soudés au Nano. Couper les fils de raccordement en deux et souder une extrémité le rend un peu moins susceptible de s'effondrer et un peu plus susceptible d'être réutilisé dans un autre projet.
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 a des fils soudés, l'orange est +5, le vert est la masse, le violet/gris/bleu sont les lignes de données
Voici le code qui s'exécute sur le Nano ; utilisez l'IDE Arduino pour compiler et télécharger via la connexion USB. L'IDE facilite la découverte du numéro de port COM attribué au port USB. Vous en aurez également besoin dans le JSL. La pièce jointe a une extension .txt supplémentaire, renommez-la pour n'avoir que l'extension .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 montrant le code pour lire les capteurs et envoyer à JMP
La routine de configuration s'exécute une fois ; la routine de boucle est appelée encore et encore, pour toujours. Delay(100) attend 1/10 de seconde avant que la boucle ne s'exécute à nouveau. 2,3,4,5,6 sont les broches connectées aux capteurs. Tout ce qui est imprimé sur le port série peut être lu par JMP à l'aide de DataFeed. Voici un autre extrait du JSL pour cela, en utilisant le Mot () pour obtenir les cinq valeurs de capteur à partir de la ligne de données :
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>Le flux de données doit être fermé correctement ou il trouvera le port toujours occupé la prochaine fois ; c'est ce que fait la logique OnClose. Tout le reste ne fait que garder le sensorBits matrice mise à jour en incrémentant si un bit de capteur est activé et en s'estompant s'il est désactivé. Les capteurs restent allumés pendant au moins trois secondes et les comptages atteignent environ 10 et redescendent assez rapidement à près de zéro.
C'est à peu près ça; il y a une boucle dans le JSL (pièce jointe), vers la fin, qui fait tourner les yeux et le modèle en réponse aux capteurs... et détecte quand le crâne s'ennuie et exécute des animations pour attirer les trick-or-treaters. Le JSL fonctionnera probablement avec le flux de données même sans rien de connecté ; il peut être mécontent du port com et peut être vraiment mécontent s'il est exécuté une deuxième fois. Il devrait être facile de commenter le port com.
Les globes oculaires de suivi fonctionnent, avec plusieurs secondes de décalage. Cela aide à savoir où se placer. Les animations ont rendu difficile pour le public de savoir qu'il était regardé.
De plus, JMP ne prend pas vraiment en charge le mappage de texture actuellement (JMP 15), c'est donc un peu lent. En utilisant un ordinateur portable plus ancien, j'obtenais encore 15 FPS, assez bien pour 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!20h, sur le point de pleuvoir. Candy Corn à moitié disparu, bâtons lumineux populaires, dents de vampire et bulles aussi !
Et ce projecteur est lumineux ! Voici le support de projecteur spécial, projetant sur un store de fenêtre translucide :
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.Déconseillé si vous avez un chat.
Et, Article précédent sur la trilogie Halloween.
short loop of animation possibilityshort loop of animation possibilityshort loop of animation possibilityshort loop of animation possibilitypossibilité de boucle courte d'animation