やさいのつくりかた.

目次/第08章

08Havok

盛り付けは、手で置かない

physics engine — 物理エンジンで自然に積む

この章の3行まとめ
  • ざるに野菜を盛るとき、座標を手で決めると「並べました」という嘘くさい絵になります。
  • そこで物理エンジンに落としてもらう。重力と摩擦と反発だけ与えて、あとは任せます。
  • ただし全部いっぺんに出すと山にならないので、0.26秒おきに1本ずつ落とします。

ざるに野菜を8本盛る。座標を手で決めると、どうしても「並べました」という絵になります。 重なり方に嘘があるからです。そこで物理エンジンに落としてもらう。 重力と摩擦と反発だけ与えて、あとは転がるに任せます。Babylon.js では Havok を使います。

ただし、全部いっぺんに出すとうまくいきません。生成した瞬間、 野菜どうしが互いにめり込んだ状態から始まるからです。 物理エンジンはそのめり込みを押しのけようとしますが、 全員が同時に押し合うので行き場がなく、結果は中央に押し固められた不自然な塊になります (めり込んだまま噛み合って止まる個体も出ます)。 物によっては器の外まで弾き出されます。 だからサンプルは0.26秒おきに1本ずつ、山の頂点のすぐ上から落としています。 1本ぶんのめり込みなら、次が来るまでに必ず解けるからです。

この実験だけは、本物の Havok が動きます

ほかの章の実験は、しくみが分かるように自前で書いた簡易版です。 でもこの章の主役は物理エンジンそのものなので、下のデモは Babylon.js と Havok を本当に読み込んで動かしています。 使っている API も数値も、サンプルからそのまま持ってきたものです。 そのぶんダウンロードが大きい(合計10MBほど)ので、ボタンを押すまで読み込みません。

const havok = await HavokPhysics();          // WASM の読み込みを待つ
// 第1引数 false = 固定タイムステップ。true だとフレーム落ちのたびに
// ステップ幅が変わり、接触が解けたり深く食い込んだりを繰り返す
const hk = new BABYLON.HavokPlugin(false, havok);
scene.enablePhysics(new V3(0, -620, 0), hk);   // 重力は cm/s^2
hk.setTimeStep(1 / 120);
ニンジン.js の物理初期化。下のデモもまったく同じ手順で動いている

実験 — 本物の Havok で盛り付ける

状態:まだ読み込んでいません
落とした数:0 / 8 個

摩擦と反発は、次に落とす1個からではなくすでに転がっている野菜にもその場で効きます。

ドラッグで視点を回せます。ホイールで寄り引き。

器のような「へこんだ形」は、箱や球では代用できない

落ちてくる野菜のほうは、多少ざっくりでも構いません。実際サンプルも 凸包(とつほう)——輪ゴムをかけたときの外形——で当たり判定を作っています。 でもざるはそうはいきません。中がへこんでいるので、箱や球で近似すると 野菜が中に入れず、フタの上に乗ってしまいます。

そこでざるだけは三角形メッシュそのものを当たり判定にします (PhysicsShapeMesh)。計算は重いのですが、ざるは動かないので許されます。 動くものは凸包、動かないものはメッシュ。これは物理エンジン全般の定石です。

つまずきポイント

物理エンジンはメートル単位で作られています。 このプロジェクトはcm単位なので、そのまま実際の重力 981 を入れると、 1ステップあたりのめり込みが許容量を超えて押し戻され続け、野菜がプルプル震えます。 デモの重力スライダーを最大まで上げると、実際に震えはじめます。 サンプルでは重力を 620 まで落として回避しています。 「正しい物理」より「破綻しない物理」を採る、という判断です。

やってみよう
  1. 「1個ずつ落とす」で盛りつけたあと、「全部いっぺんに出す」を押して、山ではなく塊になることを見比べる。
  2. 反発を0.5まで上げてから「やり直す」。実物の野菜は跳ねません。
  3. 摩擦を0にすると、どんな盛り方になるか観察する。
  4. 重力を981まで上げて、野菜が細かく震えはじめるところを探す。
  5. 視点をドラッグして真横から見る。手で並べたのでは作れない重なり方になっているはず。