とおる。:Log
Amazon Web Services on Gauche
なんとなく思い付いて ID を取得してやってみたんですが、 XML のパースで失敗するっぽい。
gosh> (use rfc.http) gosh> (define-values (status header body) (http-get "webservices.amazon.co.jp" "/onca/xml?Service=AWSECommerceService&SubscriptionId=0D8NM7HNWSQBRZTMGY02&AssociateTag=torusjp-22&Operation=ItemLookup&ItemId=4101183147"))
HTTP のリクエスト自体は正常に動いて、 ちゃんとレスポンスが返ってきます。 あ、でも、body が不完全文字列になってます。
gosh> body #*"<?xml version=\"1.0\" encoding=\"UTF-8\"?> ...
そして、
gosh> (use sxml.ssax) gosh> (use srfi-6) gosh> (define sxml (ssax:xml->sxml (open-input-string body) '()))
こうすると、エラーがでます。
*** ERROR: "(input string port)":line 1: reading char data Stack Trace: _______________________________________ 0 (ssax:xml->sxml (open-input-string body) '()) At line 11 of "(stdin)"
だれかうまくいった人いないかなぁ。
- skimu(2006/07/09 09:07:48 PDT): Gauche scheme interpreter, version 0.8.7 [utf-8,pthreads] だときちんと SXML に変換されるようです。
- とおる。(2006/07/09 17:47:26 PDT): およ? ぼくのは euc-jp で、それ以外は違いがないから、そこかな? 情報ありがとうございます。
open-input-conversion-port を使って内部エンコーディングに変換したらうまくいきました!
MEPHISTO 0.0.1
はずかしいけどとりあえずリリース。 あとでメーリングリストにもだしてみよう。 http://sourceforge.jp/projects/mephisto/
まだドキュメントがぜんぜんありませんが、 Gauche-gl があれば動きます。 アーカイブを展開して、 examples のところの live-human.scm というのを実行すると、 IPAX でやったクレイジーなデモがあらわれます。
MEPHISTO のデモムービー
実行中に動的に制約をどんどん追加して、 動作が変わっていくところのデモです。 本当は成果報告会の時にこれをやりたかったんだけど, へんなところにはまってしまって全然間に合わなかった……。
Vnc2swf を使って録画しました。 実際の画面は 40 fps 以上でけっこう滑らかに動いているんですが、 この映像はガクガクです。 これは VNC の限界かなぁ。 いろいろ加工すればもっと滑らかな映像になりそうだけど。
cut
さいきん cut というのを覚えたので、 いままで lambda をたくさん書いていたのをちょこちょこ直しているんですが、 <> はどこにでもかけるって訳じゃないんですね。 ここでだいぶ長いことはまってしまった。
gosh> ((cut not (null? <>)) 1) *** ERROR: Compile Error: wrong number of arguments: #f requires 0, but got 1 "(stdin)":10:((cut not (null? <>)) 1)
「unbound variable: <>」とかいわれていればすぐ気付くんだろうけど。 でも、マクロの仕組みを考えれば当り前だなぁ。
あとうっかり (cut #?=<>) とかやってはまりそう。
GaucheFest 9th
Wavefront OBJ
テキスト形式の 3 次元モデルのファイルフォーマットで、 よく使われているっぽい。 フリーの素材もいろいろ出てきました。
フォーマットの仕様はここで見つけました。 Wavefront OBJ: Summary from the Encyclopedia of Graphics File Formats
なので、これを読み取って Gauche-gl の手続きに変換する関数を書いてみた。
(define (read-obj-model path) (let ((vertices '(v)) (polygons '(p)) ) (define (add-vertex v)(append! vertices (list v))) (define (add-polygon p) (append! polygons (list p))) (with-input-from-file path (lambda () (port-for-each (lambda (line) (let1 line (string-trim-right (or (string-scan line #?# 'before) line)) (unless (string=? line "") (match (string-tokenize line) (("v" coord ...) (add-vertex (list->point4f (map obj-value->number coord)))) (("f" vtx ...) (add-polygon (map obj-value->number vtx))) ((sym args ...) (print #`"unknown symbol ,|sym|: ,args")) )) )) read-line)) :encoding "shift_jis") (lambda () (map (lambda (poly) (gl-normal (vector4f-normalize (vector4f-cross (point4f-sub (ref vertices (ref poly 2)) (ref vertices (ref poly 1))) (point4f-sub (ref vertices (ref poly 3)) (ref vertices (ref poly 2))) ))) (gl-begin GL_POLYGON) (map (lambda (i) (gl-vertex (ref vertices i))) poly) (gl-end)) (cdr polygons)) ) ) ) (define (obj-value->number x) (with-input-from-string x read))
- Shiro(2006/02/22 03:40:14 PST): 昔よく使った形式です。読み書きが楽なので。 このスクリプトだとテクスチャがついてるとまずいっすね。
- とおる。(2006/02/22 07:57:46 PST): そうなんですよね。とりあえず頂点とポリゴンだけ、って感じです。 あとはテクスチャとか法線とかのデータが付いているのに対応させたら、 それなりに使えそうかなぁと思いました。
The haXe Programming Language
ECMAScript ベースの言語のコンパイラで、 専用の VM 用のバイトコードだけじゃなくて、 Flash(SWF)やウェブブラウザ用の JavaScript のコードも出力できるそうです。
そして OCaml で実装されているらしい。
だれか使ってる人いないかな。
call/cc の練習。
Emacs から gosh を呼び出して、 Gauche-gl のコードを実行するとき、 glbook の例では ESC キーを押すと (exit 0) を実行するので、 Emacs 内の gosh のプロセスもろとも停止してしまいます。
なので、ESC キーを押したら main 手続きから返るようにこんな風に書いてみたら、 だめだった。
(define (make-keyboard-func cont) (lambda (key x y) (cond ((= key 27) (cont 0)) ))) (define (main args) ; ... いろいろと初期化 ... (call/cc (lambda (cont) (glut-keyboard-func (make-keyboard-func cont)) (glut-main-loop))) 0 )
たしかに main 手続きから返ってはくるんだけど、 OpenGL まわりの終了処理が走らないのでへんなことになっちゃいます。 glut-main-loop はそもそも途中でループを抜けないという前提で作られているのであたりまえか。
glut-main-loop が回っている間に、 一部の手続きの定義を変更して実行時に動作を変化させることってできないかな。
- (cont) の替わりに (read-eval-print-loop) はいかがでしょう? MacOSX で shoot.scm で試してみましたが、ESC でプロンプトが現れて、点数インチキしたりして ^D で repl を抜けると glut のメインループが復活してインチキが反映されました。
- スレッドを作って gult-main-loop を新しいスレッド上で動かす事は出来るんですかねぇ、そうするとゲームを止めること無くいろいろいじれるかも。
- Shiro (2006/01/21 13:38:27 PST):
実験的なものなら、私はこんなことをやってます。
もちろん排他制御とかを一切考えてないのでプロダクションコードではまずいですが、
試すだけならそこそこ使えます。
(thread-start! (make-thread (lambda () (read-eval-print-loop)))) (while #t (guard (e (else (report-error e))) (glut-main-loop)))
- とおる。 (2006/01/23 02:13:13 PST): おおなるほど。 これならイベントとかでライブパフォーマンス的なことができるかもしれませんね。
- とおる。 (2006/01/23 23:25:42 PST): 未踏ユースの成果報告会が 2/24-26 に秋葉原のダイビルで行われるそうです。 なので、ここでちょっと使ってみます。
3Dゲームファンのための「ワンダと巨像」グラフィックス講座
■ 逆方向キネマティックスによる巨像の動き制御
最も単純な3Dゲームにおけるキャラクタの移動は、プレーヤーの入力などから次の位置情報を算出して、移動モーション(走る姿とか)を再生しつつそこへ動かす……といった処理系になる。こうした3Dゲームでは、なんというか地面の上を滑るようにして走っているように見えてしまう。
「ワンダと巨像」に登場する巨像はとにかくでかく、プレーヤーは、巨像をクローズアップで見ることになるので、単純な方法で動かしては不自然さが確実に露呈してしまう。巨像の動き制御には文字通り「地に足のついた」踏み出して歩くような動きが必要となる。
あああ、ぼくがやりたいのはまさにこれのことです。 「ワンダと巨像」まだやってないんだけど、買うしかないか。
- Shiro(2005/12/08 19:56:15 PST): その記事で萌えたのはですね、ここですここ。
こういうのはメインプログラマ以下、意志統一がされてないとなかなか難しい。「ワンダと巨像」では、垂直帰線期間を見て余裕があるときに、 バックグラウンドでこうしたメモリ領域の再配置処理を行なっているのだという。
- とおる。(2005/12/09 05:49:13 PST): フレームレートもゲーム中に変わるんですね。すごいですよねぇ。一度でいいからこういうチームで働いてみたいです。
define の構文シュガー
SICM をぱらぱらめくっていて初めて知ったんですが、 手続きを返す手続きってこんなに簡単に書けるんですね。
gosh> (define ((compose f g) x) (f (g x))) compose gosh> (define (square x) (* x x)) square gosh> ((compose square sin) 2) 0.826821810431806
- おおっ?こんなdefineがデフォルトで出来たのしらなんだ! しかも階層何段でも行けそうな雰囲気cut-sea:2005/12/08 05:23:57 PST
- Shiro(2005/12/08 13:03:03 PST): へぇ〜^3、これができる処理系があるのは知ってたけど
Gaucheでもできたんだ。初耳(マジで)。
いや、実際、これはコンパイラをSchemeで書き直した時に意図せず出来た副作用です。 (define (foo . x) y) から (define foo (lambda x y)) への展開を再帰で やってるので… 正式にサポートすべきかどうかまだわかりません。 - あらま。そうなんですか。 Gaucheはもちろん、不審に思ってGuileとかでも試しましたが、できてるくさいですね。 できればこのまま使えると便利だと思います。cut書かなくなるかも。cut-sea:2005/12/08 16:53:43 PST
- とおる。(2005/12/08 19:15:56 PST)うはwwwwwwwww。
続、安直な制約システム
マクロの書き方をちょっと鍛えて、 こんな風に書けるようにしてみた:
(define (make-multiplier) (make-constraint (m1 m2 product) ((m1 m2 => product) (* m1 m2)) ((m1 product => m2) (/ product m1)) ((m2 product => m1) (/ product m2)) ))
前と違うのは、矢印「=>」の向きです。
((m1 m2 => product) (* m1 m2))
というのは、m1 と m2 が値を持っていれば product に m1 と m2 の積の値を割り当てるという意味です。
んー、もうちょっとがんばれば、
((dividend divisor => quotient remainder) (quotient&remainder dividend divisor))
みたいに書けそうだなぁ。 でも多値が出てくると却って面倒になるかな。
プロシンの報告集の原稿
とりあえず書けたので、竹内先生にも送ってみた。 Source Forge に置いてありますので、よかったら見てください。 PDF ファイルです。
http://cvs.sourceforge.jp/cgi-bin/viewcvs.cgi/mephisto/misc/prosym2006/
んー、Scheme の話はぜんぜん出てきてないな。 っていうか、まだちゃんと実装できてない部分があるので、 いまいち具体性に欠けるんだよなぁ。
でもなんとなく当日のデモの内容とかもイメージが出来てきたかも。
- Shiro(2005/11/27 09:50:10 PST): ぱっと読んでのツッコミどころです。
- 与えられた制約だけでは解が一意に定まらない場合 (IKは通常そうですね)や、解が無い場合のシステムの振舞いは? 発散したり振動したりせずに安定させる秘密があるのかな?
- 制約が増えていった場合の計算量の増加は心配ない?
- 回転座標系のツリーで人体を表現することの限界はFF映画で 経験しました。ルート付近のわずかな誤差が末端にゆくにつれ 拡大されてしまうこと、モーションキャプチャしたデータやIKと 組み合わせるのが面倒なこと、などです。ただ、物理モデルを入れたり、 アニメーションのレイヤリングをするには逆に回転座標系でないとやりにくいです。 FF映画では結局、階層的な回転座標系モデルの関節を、キャプチャデータや IKの制約ポイントがスプリングを介して引っ張るようにしてました。
ツッコミありがとうございますー。 発表当日にはきちん答えられるようにしたいですが、 ちょっと未検証の部分があるので以下はいま漠然と考えていることです。
- 解が一意に定まらないときは?: ちょっとずるいですが、 基本的に一意に定まるように制約を与えます。 ただし、制約自体はキャラクタの状態(歩いているか走っているかなど) によって動的に入れ替えるようにするので、 始めからあらゆる場合を想定しておく必要はないはず。
- 解がない場合は?: 実解が存在しないときなどは そのときにエラーになります。
- 制約が増えていった時の計算量:
PC ゲームやテレビゲームで使いたいというのもあって、
計算量のために結構いろいろ犠牲にしています。
- まず、 制約の数が増えても、 値が伝搬していかなければ計算は行われないので、 そこは計算量には響かないと思います。
- 同時に矛盾する値が入ってきた時には、 実解がないとしてエラーになるか、 単に先に入ってきた値を優先します。 具体的には、 制約に接続されている値がすべて決定したら、 その時点でその制約は非活性化されて同じ計算を2度やらないようにします。 なので、 計算量は多くても高々制約の個数程度ということになります。
解が一意に定まらない時のために、 バネを入れたり物理計算をたくさんやるというのも一応考えてはいるんですが、 これらの対応の仕方は、 たぶんアプリケーションの性質やデザイナーの意向によって、 要求が変わってくると思うんですよね。 なので、 この辺の振舞はあとからいくらでも追加できるような作りにしたいと思っています。
とくに、いちばんやりたくないのがトレランスの値をつかう実数の比較で、 2 つの点を同一とみなすかどうかの判定などは、 アプリケーションの性質(CAD なのか、ゲームなのか、映画なのか、など)によって 全然やりかたが変わって来ますよね。
- Shiro(2005/11/28 16:18:11 PST): なるほどー。伝搬は一方向っていう割り切りは 応用としては有りでしょうね。 制約を全部手付けでやるなら結構ごまかしがきくことが多いでしょう。 生のキャプチャデータは誤差のせいでかなり矛盾だらけ (ボーンが伸び縮みする) で あることが多いんで、そういう外からのデータを扱いたければもう一工夫要りそう ですね。 あと、コリジョンは一時的な制約として表現するのだと思うんですが、 制約の計算が一回きりだとすると、 リアルタイムでコリジョンを計算したい時はどうなるんでしょ (1フレーム進めてみて、コリジョンが起きてたらそこに制約を入れて もう一回そのフレームを計算する必要がありますよね?---あるいは サブフレームに分割するか。)
(2005/11/29 03:33:51 PST): コリジョンに関してはまだうまいやり方があるか分からないんですが、 コリジョンがおきた時だけ活性化して、値を上書きする制約 (もはや制約というよりは神経細胞みたいなイメージなんですが)とか、 そういうやり方ができたら面白いかなぁとか考えています。
マリオネットだとひざに糸がついていて、 糸を引き上げるとひざを上げるようなのが多いんですが (こんなの)、 これは足が地面につくと糸がたるんで、 ちゃんとぴったり足がつくようになっています。 こういう糸がたるむ感じをうまく制約で表現できるといいなぁと思ってます。
COLLADA
次世代の 3D シーンデータフォーマットということで、 SCEI とかが考えたやつですが、 少し前に仕様書をダウンロードして眺めながら様子見中です。 VRML をより現実に即した形で設計しなおしたっていう感じかな。 理想よりもゲーム制作の現場からの視点を重視して作られたっていう感じですかね。
SXML を使ってがんばれば、 COLLADA のデータから Gauche-gl の描画手続きへの変換は出来そう。 問題は安価なモデラがサポートするかどうかだなぁ。 Shade とかでエクスポートできるようになるといいなぁ。
Shade って Python でごにょごにょ出来るみたいだから、 がんばれば自分でエクスポータも書けるのかな。 でも自信ないな。
安直な制約システム
define-syntax の使いかたを覚えて、 だいぶすっきり書けるようになった。 たとえば、かけ算の制約はこんな風に書けます。
(define (make-multiplier) (make-constraint (m1 m2 product) ((product <= m1 m2) (* m1 m2)) ((m2 <= m1 product) (/ product m1)) ((m1 <= m2 product) (/ product m2)) ))
make-constraint で作った制約に、 値を保持するワイヤを接続することで、 制約伝搬を実現します。
あともうちょっといじったら公開しよう。
Scheme:マクロ内でのループ
補助マクロを使った方法がやっと理解できた。 制約伝播を作るときにこの辺が分からなくて、 define-macro でごっちゃごちゃに書いていたので、 あとで define-syntax で書き直そう。
しかし、 define-syntax って分かると便利だなぁ。
座標系についての落とし穴
- 数学や物理の教科書に出てくるのは左手系
- OpenGL など CG で一般的に使うのは右手系
単なる鏡像変換だと思って甘く見ていると、
- 外積(ベクトル積、dot product)の向きが反対になる!!
ということに気づかずにはまりました。 そういえばこういうのを 擬ベクトル (pseudo vector) というのでした。 ベクトルの変換性に従わないベクトルとは似て非なるものです。
外積というとフレミングの左手の手をして考える人は要注意です。
vector4f
gl.math3d に vector4f のスカラー倍の手続きがないのが不思議で、 他の人はどうやっているか気になったので、 そうだ Banyan を見てみようと思ってダウンロードしたら、 やっぱり動かしてみたくなったので、 いま MacOS X 上にいろいろインストール中。
gettext とかからインストールしないといけないのかー。 まだまだ先は長い。
制約とアニメーション
点のアニメーションは、 時刻の集合 T から座標空間 S への写像としてかけます。
T → S
ある 3 次元モデルを N 個の点で表すとします。 つまり、モデルを点列
{P_i | i∈{0, 1, ..., N-1}}
のような形で書きます。
(一般に、形や方向を持つ剛体は、 それに含まれる 3 点の座標さえ与えれば、 状態(位置と向き)が一意に決まります。 なので、関節を持つ人型のモデルとかも、 たくさんの点を使えば状態(姿勢)を決定することが出来ます。)
とすると、 モデルのアニメーションは
T → S^N
という写像として表現できます。
さて、 ある決まった n 個の点の座標を与えると、 残り N-n 個の点の座標も決定するとします。 つまりこんな写像があるとします。
S^n → S^(N-n)
この写像を「制約」と呼ぶことにします。 (本来の意味からは非常に狭い意味になっちゃうけど。)
とすると、この n 個の点のアニメーション
T → S^n
を与えれば、 このモデルのアニメーションが決まります。
あれ、アニメーションと制約ってこんなに簡単な話なんだっけ?
っと思ったんですが、 これだけ簡単になったのは、 どこにも「角度」の話が出て来ないからかな。 角度を持ち出すと、 どうしても各パーツごとの局所座標系を考えざるを得なくなって、 座標変換行列の掛け算とかいろいろめんどくさくなります。 (というか、普通はそうすると思います。)
角度を 2 点を使って「方向」として表現(必要なら長さ 1 に正規化)すれば、 点だけの問題に還元できるので、 そしたら上に書いたような点と点だけの制約しか考えなくても、 モデルのアニメーションに関しては間に合いそう。
その辺に売っているモデリングソフトでも IK とかは普通に出来るけど、 アニメーションシーケンスのデータとして吐き出すときに、 制約についての情報を捨ててしまうので、 話がややこしくなってしまうんじゃないかな。 (昔ちょこっとだけ触ったのを覚えている限りでは、 RenderWare のアニメーションデータとか、 任天堂の NIFF とかいうデータ形式には制約についての情報はなかったと思う。)
安直な制約システムとデモムービー
SICP などに載っている制約解消系よりもだいぶ手抜きです。 ストリームを使ったダイナミックな変化と相性の良い制約の記述方法を考えています。
制約システムについては、 そのうちもう少し詳しい説明をつけたいと思います。
マリオネットの表現
- 胴体→背中に糸
- 首の付け根→首
- のど→頭→両耳に糸
- 右肩→右上腕
- 右肘→右前腕
- 右手首→右手+フォーク→糸
- 右肘→右前腕
- 左肩→左上腕
- 左肘→左前腕
- 左手首→左手
- 左肘→左前腕
- 右脚の付け根→右脚
- 右足首→右足→糸
- 左脚の付け根→左脚
- 左足首→左足→糸
- 首の付け根→首
制約の表現はやっぱりまだ考え中。
MEPHISTO
MEPHISTO という、 Gauche-gl ベースの 3 次元 CG アニメーションの処理系を作ろうとしています。 2005年度未踏ユース に採択していただきました。
とりあえず、 8/27 の LLDN 夜の部でデモをやりますので、 チケットを持っている人はぜひ見てください。 ビール飲みながら見ると吐くかも(嘘)。
Scheme:Schemeプログラマのレベル10 を読んで、 せいぜいレベル 2.5 くらいなのに、 Scheme のプロジェクトを立ち上げてしまっているのに不安を感じますが。 まぁ、いいか。 Scheme の本は SICP(の途中まで)しか読んだことないし。
- Shiro(2005/08/15 14:30:49 PDT): 応援します。Gaucheに足りない機能とかあったら遠慮なく 言って下さい。あと、lazy streamライブラリ(srfi-40) は0.8.6に入ります。
- とおる。(2005/08/15 21:33:38 PDT): ありがとうございます。SRFI-40 ってそのものずばりな感じですね。そのへんもちゃんと覚えなきゃ……。
ルンゲ=クッタ法
- こんなに簡単にかけるのか。
- これでリアルタイムな物理計算もできるかなぁ。
- 物理的な関係(モデル間がバネで結ばれているとか、慣性モーメントとか)も制約ネットワークの形で書いて、 制約解消系がその軌道を計算するようになると素敵だなぁ。
SRFI-40 A Library of Streams
背景
- even style の stream というものらしい。
- SICP のストリームは cdr 部だけを delay する(odd style)のに対して、
even style は、car と cdr をまとめて(cons 手続きごと)delay するみたい。
- odd style
(define-syntax cons1 (syntax-rules () ((cons1 obj strm) (cons obj (delay strm)))))
- even style
(define-syntax cons2 (syntax-rules () ((cons2 obj strm) (delay (cons obj strm)))))
- odd style
- odd style は、かならず要素 1 個分を余分に評価してしまう。
- 1 個くらいいいじゃないかと思うけど、 その最後の 1 個がエラーを引き起こすことがある。
- even style はそういう無駄はない代わりに、
ストリームを返す手続きを定義するときに、
かならず (delay (force ...)) というので全体を囲まないといけない。
- odd
(define (map1 func strm) (if (nil1? strm) nil1 (cons1 (func (car1 strm)) (map1 func (cdr1 strm)))))
- even
(define (map2 func strm) (delay (force (if (nil2? strm) nil2 (cons2 (func (car2 strm)) (map2 func (cdr2 strm)))))))
- これは意味が分かりにくいし、ちょっとめんどくさい。
- odd
- そこで……
stream-define
- 新シンタックス stream-define
promise はしない
- まだ理屈がよく分からないんだけど、普通の delay の代わりに、 promise ではなくてストリームを返す stream-delay という関数が定義される。
- これだと delay を使うよりもメモリが節約できるらしい。
- でもちゃんと計算結果のキャッシングはするらしい。
- MEPHISTO を作っていて、確かに delay の食うメモリとそれに伴う GC にかかる時間がしゃれにならないくらいひどかったので、その問題が改善されるならうれしい。
Shiro: あーっと、Gaucheのsrfi-40実装ではpromise作っちゃいます。 というかsrfi-40のreference implementationがやってることも、 自前でpromiseに相当する機構を作っているだけなので、 「delayを使うよりもメモリが節約できる」ということはないはず。
但し、「R5RS式のdelayとforceだけを使う遅延評価機構でeven styleの streamを書くと、たとえ内部の手続きを末尾再帰で書いていても、 大元のdelayが計算結果を握りっぱなしになってメモリが食いつぶされる」 という問題がありました。それがsrfi-40の議論で明らかになって、 srfi-45においてlazyという機構を導入することで解決が図られました。 現在のsrfi-40のreference implementationは自前でlazyに相当する 機構を実装しています。
Gauche 0.8.6ではネイティブでsrfi-45のlazyをサポートしていて、 srfi-40はそれを使って実装されます。ので、srfi-45で議論されてる space leakは起こらないはず。でもdelayやlazyはその性質上、呼ばれた 時点の環境をセーブしなくちゃならないんで、クロージャを作るのと 同じくらいのメモリ/時間は喰います。
- なるほど。(まだあまりよく分かってないですが(汗)。)でもクロージャを作る程度なら、多分問題ないですね。いま、とりあえず作っているのは、cons-stream で promise の代わりに単なる lambda を返していて、それだと一応動いているので。もうちょっとちゃんと読んでみます。ありがとうございます。
- (2005/11/22 08:35:41 PST) srfi-45 を読んで、問題の本質が分かった気がします。この辺、よく読んでみると面白いですねー。