今年萬聖節來的很快。 去年我用這個樹莓派項目享受一夜的娛樂。 今年我使用了 JMP 和一個阿杜諾納米. 有關 nano 的更多信息,請參見下文。
因為我一直在玩將地球的圖片包裹到一個球體上,我想知道我是否可以找到類似的頭骨紋理來代替包裹。 我發現了更好的方法:這個地點有3D模型。 該項目使用鏈接指向的模型。 感謝有人使用這個名字可打印模型。
附件是完整的 JSL;此博客中的片段不完整,可能不完全匹配。
在 zip 文件中(使用 JMP 的 zip 存檔打開它):
</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>這是三個文件和一個目錄來保存它們。 我不確定 .mtl 文件的作用,這篇文章主要是關於 .obj 文件的。 .jpg 很有意思,後面用到了,長這樣:</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 size頭骨紋理貼圖,1/4 尺寸
OBJ 文件包含其餘數據:3D 點及其 2D 紋理貼圖坐標。 有文件格式的解釋這裡. 該文件包含v- 3D坐標的部分, vt-紋理貼圖坐標部分, vn-法向量的部分, F-部分面(3 或 4 個邊多邊形)和其他一些項目。
# 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
#
...
上面是一個非常精簡的文件副本,顯示#comments、空白行、一個帶三個點的三角形面和一堆四邊形面,以及一些我將忽略的 g 和 s 記錄。
JSL 可以通過模式匹配有效地讀取這些內容。 首先,將 OBJ 轉化為字符串:
obj=za<<READ></READ>
String( "# 3ds Max ... 6171025 個字符 ...lygons
") 分配。
然後解析它。 設置一些列表以在解析時保存數據值,並製作一些輔助模式:
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;有些面是三角形的,有些是四邊形的。 這三元組和四倍下面用於有效地解析人臉並將其存儲在適當的列表中:四邊形或三角形。
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"));對象,如上定義,持有一個模式;尚未解析任何內容。 對象告訴模式匹配器(如下)如何匹配 OBJ 文件中的一行。 下面是對模式匹配器的調用,使用更多的模式匹配 JSL 使匹配器循環遍歷文件中的所有記錄。
它說:使用先前加載到的文本對象, 從左邊位置 0 開始重複使用對象模式直到正確的位置 0。 每次成功後對象, 將已經完成的工作隔離開來,這樣 JMP 就不必進行額外的記錄保存。
rc = patmatch(obj,
patpos(0)+
patrepeat(objpat+patfence())+
patrpos(0)
);
show(rc);
99(未使用的數據)mtllib 12140_Skull_v3_L2.mtl
487834(未使用的數據)usemtl 12140_Skull_v3
487857(未使用的數據)s 2
1305132(未使用的數據)usemtl 12140_Skull_v3
1305155(未使用的數據)s 1
2038681(未使用的數據)usemtl 12140_Skull_v3
2038704(未使用的數據)s 1
4326305(未使用的數據)usemtl 12140_Skull_v3
4326328(未使用的數據)s 2
RC = 1;
rc==1 是個好東西,模式從頭到尾都匹配。 log() 函數在對象正在創建關於未使用數據的行;看起來那些數據線不會有用。 在此過程中,匹配器為每個執行此 JSL vn記錄(和其他記錄類似):
pattest(insertinto(normal,evallist({evallist({num(n1),num(n2),num(n3)})}));1)填滿了 JSL 列表普通的與所有的頂點法線。 (法向量告訴 OpenGL 光將從表面反射的方向。)末尾的 ;1 告訴 pattest() 它應該成功,以便匹配器繼續向前移動。
頂點列表是 3 項列表的列表;這些項目是 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},
...Faces 列表分為三角形和四邊形。 三角形列表也是列表的列表:
{{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}, 這三個三元組是頂點、紋理和法線列表的索引。 紋理列表使用 0..1 坐標從上面的紋理圖中選擇顏色。 JSL列表分段有四個項目,每個項目都包含一個片段名稱(頭骨、下頜、上牙、下牙)和起始位置的列表三角形和四邊形列出。
此 JSL 將數據加載到 JSL 中場景顯示列表對於每個分段項目:
// 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;
);這是同樣的邏輯,兩次,一次做三角形,一次做四邊形。
JSL從四個部分中製作了四個顯示列表;附加文件會將它們組合起來並製作動畫,並添加一些萬聖節風格的眼球。 拍照的時間...
Halloween early evening, model in window萬聖節傍晚,櫥窗裡的模特
看起來眼睛是不是跟著攝影師走? 窗口底部的縫隙是怎麼回事?
Arduino Nano 和 PIR 傳感器
為了讓眼睛跟踪人,需要某種傳感器。被動紅外傳感器有一個單比特接口(有些東西被感動了!)和響應時間的一些限制。 考慮到萬聖節的低投票率,他們是正確的選擇。 Raspberry PI 相機可以提供更好的性能。 窗口下方的間隙允許五個 PIR 傳感器與准直器觀察人們可能行走的區域。 這是設置:
They do like like eyes. Those collimator tubes are re-purposed.他們確實喜歡眼睛。 那些准直管被重新利用。
這是組裝件的樣子,用膠帶粘好,用熱膠粘好,並用跳線連接到 Arduino Nano。 納米很小。
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 在左邊的拉鍊下面。 右側的 PIR 傳感器熱粘在准直器上。 每個 PIR 上有兩個橙色調整。
Nano 克隆帶有可以焊接的插頭引腳,或者可以將電線焊接到 Nano 上。 將跳線切成兩半並焊接一端可以降低它散開的可能性,並且更有可能在另一個項目中重複使用。
Nano has wires soldered on, orange is +5, green is ground, purple/gray/blue are data linesnano上面焊有線,橙色是+5,綠色是地線,紫色/灰色/藍色是數據線
這是在 Nano 上運行的代碼;使用 Arduino IDE 通過 USB 連接編譯和下載。 IDE 可以輕鬆發現分配給 USB 端口的 COM 端口號。 您在 JSL 中也需要它。 附件有一個額外的 .txt 擴展名,請將其重命名為僅具有 .ino 擴展名。
Arduino IDE showing code to read sensors and send to JMPArduino IDE 顯示讀取傳感器並發送到 JMP 的代碼
安裝例程運行一次;循環例程被一次又一次地調用,直到永遠。 Delay(100) 在循環再次運行之前等待 1/10 秒。 2、3、4、5、6 是連接到傳感器的引腳。 JMP 可以使用 DataFeed 讀取打印到串行端口的任何內容。 這是 JSL 的另一個片段,使用單詞() 函數從數據線上獲取五個傳感器值:
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>數據饋送需要正確關閉,否則下次會發現端口仍然繁忙;這就是 OnClose 邏輯所做的。 其他一切只是保持傳感器位如果傳感器位打開則矩陣通過遞增更新,如果傳感器位關閉則通過衰減更新。 傳感器保持開啟狀態至少三秒鐘,計數上升到大約 10,然後很快回落到幾乎為零。
僅此而已; JSL(附件)中有一個循環,靠近末尾,可以旋轉眼睛和模型以響應傳感器……並檢測頭骨何時感到無聊並運行一些動畫來吸引搗蛋鬼。 即使沒有任何連接,JSL 也可能會與數據饋送一起運行;它可能對 com 端口不滿意,如果運行第二次可能真的不滿意。 應該很容易註釋掉 com 端口。
跟踪眼球確實有效,但有幾秒鐘的滯後。 它有助於了解站在哪裡。 動畫讓觀眾很難知道他們正在被觀看。
此外,JMP 目前並不真正支持紋理映射 (JMP 15),因此速度有點慢。 使用較舊的筆記本電腦,我仍然可以獲得 15 FPS,這對於萬聖節來說已經足夠好了。
8pm, about to rain. Candy Corn half gone, glow sticks popular, vampire teeth and bubbles too!晚上八點,要下雨了。 玉米糖不見了一半,熒光棒很受歡迎,吸血鬼牙齒和泡泡也很受歡迎!
那台投影儀很亮! 這是特殊的投影儀支架,投影到半透明的窗簾上:
Not recommended if you have a cat.養貓的話不推薦。
和,以前的萬聖節三部曲帖子。
short loop of animation possibility動畫可能性的短循環