Halloween llegó rápido este año. El año pasado usé este proyecto Raspberry Pi para obtener el valor de una noche de entretenimiento. Este año usé JMP y un arduino nano . Más sobre el nano, muy por debajo.
Desde que he estado jugando con envolviendo la imagen de la tierra en una esfera , Me preguntaba si podría encontrar una textura de calavera similar para envolver en su lugar. Encontré algo mucho mejor: esto sitio tiene modelos 3D. Este proyecto utiliza el modelo al que apunta el vínculo. Gracias a alguien que usa el nombre modelos_imprimibles.
El archivo adjunto es el JSL completo; los fragmentos de este blog están incompletos y es posible que no coincidan.
Dentro del archivo zip (use el archivo zip de JMP para abrirlo):
</CODE></DIR></PRE><P><STRONG>{"Skull_v3_L2.123c1407fc1e-ea5c-4cb9-9072-d28b8aba4c36/",</STRONG><BR /><STRONG> "Cráneo_v3_L2.123c1407fc1e-ea5c-4cb9-9072-d28b8aba4c36/Cráneo.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>Son tres archivos y un directorio para guardarlos. No estoy seguro de qué hace el archivo .mtl, y esta publicación trata principalmente sobre el archivo .obj. El .jpg es realmente interesante, se usa más tarde y se ve así:</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 sizeMapa de texturas para calavera, tamaño 1/4
El archivo OBJ contiene el resto de los datos: puntos 3D y sus coordenadas de mapa de textura 2D. Hay una explicación del formato de archivo. aquí . El archivo contiene v- secciones de coordenadas 3D, Vermont- secciones de coordenadas del mapa de textura, vn- secciones de vectores normales, F- secciones de caras (polígonos de 3 o 4 lados) y algunos otros elementos.
# 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
#
...
Arriba, una copia muy abreviada del archivo que muestra #comentarios, líneas en blanco, una cara triangular con tres puntos y un montón de caras cuádruples, y algunos registros g y s que ignoraré.
JSL puede leerlos de manera eficiente con una coincidencia de patrón. Primero, coloque el OBJ en una cadena:
obj=za<<READ></READ>
String( "# 3ds Max ... 6171025 caracteres totales ... lygons
" ) asignado.
Luego analízalo. Configure algunas listas para contener los valores de los datos a medida que se analizan y cree algunos patrones auxiliares:
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;algunas de las caras son triángulos, algunas son quads. El tres triples y cuartotriple se utilizan a continuación para analizar y almacenar de manera eficiente la cara en la lista adecuada: cuadrante o triángulo.
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"));Objeto , definido anteriormente, tiene un patrón; nada ha sido analizado todavía. Objeto le dice al comparador de patrones (abajo) cómo hacer coincidir una sola línea en el archivo OBJ. Aquí está la llamada al comparador de patrones, con un poco más de JSL de coincidencia de patrones para hacer que el comparador recorra todos los registros del archivo.
Dice: usando el texto previamente cargado en objeto , comience en la posición izquierda 0 y utilice repetidamente el objeto patrón hasta la posición derecha 0. Después de cada éxito objeto , limite el trabajo que ya se hizo para que JMP no tenga que llevar registros adicionales.
rc = patmatch(obj,
patpos(0)+
patrepeat(objpat+patfence())+
patrpos(0)
);
show(rc);
99 (datos no utilizados) mtllib 12140_Skull_v3_L2.mtl
487834 (datos no utilizados) usemtl 12140_Skull_v3
487857 (datos no utilizados) s 2
1305132 (datos no utilizados) usemtl 12140_Skull_v3
1305155 (datos no utilizados) s 1
2038681 (datos no utilizados) usemtl 12140_Skull_v3
2038704 (datos no utilizados) s 1
4326305 (datos no utilizados) usemtl 12140_Skull_v3
4326328 (datos no utilizados) s 2
rc = 1;
rc==1 es algo bueno, el patrón coincide de principio a fin. La función log() en objeto está creando las líneas sobre los datos no utilizados; parece que esas líneas de datos no serán útiles. Y en el proceso, el comparador ejecutó este JSL para cada v.n. registro (y similar para los demás registros):
pattest(insertinto(normal,evallist({evallist({num(n1),num(n2),num(n3)})}));1)que llenó la lista JSL normal con todas las normales de vértice. (Los vectores normales le dicen a OpenGL en qué dirección rebotará la luz en una superficie). El ;1 al final le dice a pattest() que debería tener éxito para que el comparador continúe avanzando.
La lista de vértices es una lista de listas de 3 elementos; los elementos son coordenadas 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 lista de Caras está separada en triángulos y cuadrantes. La lista de triángulos es también una lista de listas:
{{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}, esos tres triples son índices en las listas de vértices, texturas y normales. La lista de texturas usa las coordenadas 0..1 para elegir un color del mapa de texturas, muy arriba. La lista JSL segmento tiene cuatro elementos, cada uno de los cuales es una lista que contiene un nombre de segmento (cráneo, mandíbula, dientes superiores, dientes inferiores) y ubicaciones iniciales en el triángulo y patio liza.
Este JSL carga los datos en JSL Listas de visualización de escenas para cada uno de los segmento elementos:
// 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;
);Esa es la misma lógica, dos veces, una para hacer triángulos y otra para hacer cuadriláteros.
Ese JSL hizo cuatro listas de visualización de las cuatro partes; el archivo adjunto los combinará y los animará y agregará algunos globos oculares al estilo de Halloween. Hora de una foto...
Halloween early evening, model in windowTarde de Halloween, modelo en ventana
¿Parece que los ojos están siguiendo al fotógrafo? ¿Qué pasa con el espacio en la parte inferior de la ventana?
Arduino Nano y sensores PIR
Para que los ojos sigan a las personas, se necesita algún tipo de sensor.sensores PIR tener una interfaz de un solo bit (¡algo movido!) y algunas limitaciones en el tiempo de respuesta. Dada la baja participación en Halloween, fueron la elección correcta. Una cámara Raspberry PI podría proporcionar un mejor rendimiento. El espacio debajo de la ventana permite cinco sensores PIR con colimadores para observar las regiones donde la gente podría caminar. Aquí está la configuración:
They do like like eyes. Those collimator tubes are re-purposed.Les gustan los ojos. Esos tubos colimadores se reutilizan.
Y así es como se ve el ensamblaje, pegado con cinta adhesiva, pegado en caliente y conectado al Arduino Nano. El Nano es pequeño.
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 la izquierda debajo de las bridas. Sensores PIR a la derecha pegados en caliente a los colimadores. Hay dos ajustes naranjas en cada PIR.
El clon Nano viene con pines de cabecera que se pueden soldar, o se pueden soldar cables al Nano. Cortar los cables de puente por la mitad y soldar un extremo hace que sea un poco menos probable que se deshaga y un poco más probable que se reutilice en otro proyecto.
Nano has wires soldered on, orange is +5, green is ground, purple/gray/blue are data linesNano tiene cables soldados, naranja es +5, verde está conectado a tierra, púrpura/gris/azul son líneas de datos
Aquí está el código que se ejecuta en el Nano; use el IDE de Arduino para compilar y descargar a través de la conexión USB. El IDE facilita descubrir el número de puerto COM asignado al puerto USB. También lo necesitará en el JSL. El archivo adjunto tiene una extensión .txt adicional, cámbiele el nombre para que solo tenga la extensión .ino.
Arduino IDE showing code to read sensors and send to JMPArduino IDE mostrando código para leer sensores y enviar a JMP
La rutina de configuración se ejecuta una vez; la rutina de bucle se llama una y otra vez, para siempre. Delay(100) espera 1/10 de segundo antes de que el bucle vuelva a ejecutarse. 2,3,4,5,6 son los pines conectados a los sensores. JMP puede leer todo lo que se imprima en el puerto serie mediante DataFeed. Aquí hay otro fragmento de JSL para eso, usando el Palabra () función para obtener los cinco valores del sensor de la línea de datos:
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>El suministro de datos debe cerrarse correctamente o encontrará que el puerto aún está ocupado la próxima vez; eso es lo que está haciendo la lógica OnClose. Todo lo demás es simplemente mantener el bits de sensor matriz actualizada incrementando si un bit de sensor está encendido y desvaneciéndose si está apagado. Los sensores permanecen encendidos durante al menos tres segundos y los recuentos aumentan hasta aproximadamente 10 y se desvanecen con bastante rapidez hasta casi cero.
Eso es todo; hay un bucle en el JSL (archivo adjunto), cerca del final, que gira los ojos y el modelo en respuesta a los sensores... y detecta cuando el cráneo está aburrido y ejecuta algunas animaciones para atraer a los que piden dulces. El JSL probablemente se ejecutará con el suministro de datos incluso sin nada conectado; podría estar descontento con el puerto COM, y podría estar realmente descontento si se ejecuta por segunda vez. Debería ser fácil comentar el puerto com.
Los globos oculares de seguimiento funcionan, con varios segundos de retraso. Ayuda a saber dónde pararse. Las animaciones dificultaron que la audiencia supiera que estaban siendo observados.
Además, JMP realmente no admite el mapeo de texturas actualmente (JMP 15), por lo que es un poco lento. Usando una computadora portátil más vieja, todavía obtenía 15 FPS, lo suficientemente bueno para Halloween.
8pm, about to rain. Candy Corn half gone, glow sticks popular, vampire teeth and bubbles too!20:00, a punto de llover. ¡Candy Corn medio desaparecido, barras luminosas populares, dientes de vampiro y burbujas también!
¡Y ese proyector es brillante! Aquí está el soporte especial del proyector, proyectando sobre una cortina de ventana translúcida:
Not recommended if you have a cat.No recomendado si tienes un gato.
Y, Publicación anterior de la trilogía de Halloween.
short loop of animation possibilitybucle corto de posibilidad de animación