お待たせ致しました。諦めかけたところでバグが取れました…
実はそうこうしているうちに公式の方からGrimoire.js×Oimo.jsが出てしまって、二番煎じ感も否めませんが、この記事も物理演算です。
違うことといえば、物理演算ライブラリがOimo.jsではなくCANNON.jsを使ったことです。
理由はパッと見簡単に書けそうだったからです(適当)
能書きはここまでにして、本題に入りましょう。
デモ
まずはモノを見ないと始まりませんから…
次のリンクからご覧ください。
デモページ
マウスでグリグリやると球がコロコロします。
中身
GOMLは私の前回の記事よりも単純ですので、GitHubを参照していただければと思います。(GitHubだとハイライトがなくて見づらいので、本格的に見たい方はお好みのエディタにコピペしてシンタックスをXMLに設定してください…)
今回の主役は物理演算(main.js)です。
実は内部では立方体のrotationは変わっていません。カメラの角度に応じて重力ベクトルを動かしています。
これをやるのに最初はカメラのオイラー角を取って回転行列をゴリゴリ計算していた(このときの名残で重力ベクトルの配列の形が不思議になっています)のですが、そこに致命的なバグがあって重力があらぬ方向に向いていたせいで投稿が年をまたいでしまいました…
それはそうと、各箇所の簡単な解説をコメントで付けておきますので参考にしてください。
const Vector3 = gr.lib.math.Vector3;
const Quaternion = gr.lib.math.Quaternion;
console.log(Vector3);
gr(function(){
var $$ = gr("#main");
var world = new CANNON.World();
var lastGravityVector = [[0], [-9.82], [0], [1]];
world.gravity.set(lastGravityVector[0][0], lastGravityVector[1][0], lastGravityVector[2][0]);
// 箱をつくります。立方体もCANNON.jsで用意されているのですが、それでは中にものが入りませんでした。
// 6つの平面を質量0で用意して座標を描画してある立方体の面に合わせます。
var planes = [];
for(let i = 0; i < 6; i++){
planes[i] = new CANNON.Body({
mass: 0, // mass == 0 makes the body static
});
var planeShape = new CANNON.Plane();
planes[i].addShape(planeShape);
}
// 平面を立方体にあわせて回転しています。
// CANNON.Planeは、向きがあるので注意
planes.forEach(function(v, i){
switch(i){
case 0:
v.position = new CANNON.Vec3(5, 0, 0);
v.quaternion.setFromAxisAngle(new CANNON.Vec3(0, 1, 0), -Math.PI / 2);
break;
case 1:
v.position = new CANNON.Vec3(-5, 0, 0);
v.quaternion.setFromAxisAngle(new CANNON.Vec3(0, 1, 0), Math.PI / 2);
break;
case 2:
v.position = new CANNON.Vec3(0, 5, 0);
v.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), Math.PI / 2);
break;
case 3:
v.position = new CANNON.Vec3(0, -5, 0);
v.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2);
break;
case 4:
v.position = new CANNON.Vec3(0, 0, 5);
v.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), Math.PI);
break;
case 5:
v.position = new CANNON.Vec3(0, 0, -5);
break;
}
world.addBody(v);
});
// 球を描画してある座標に合わせて定義します。
var balls = [];
for(let i = 0; i < 3; i++){
var radius = 1;
balls[i] = new CANNON.Body({
mass: 1.2,
position: new CANNON.Vec3(0, 0, 0),
shape: new CANNON.Sphere(radius),
});
// 面の回転と同じようにswitch文で座標を合わせています。
switch(i){
case 0:
balls[i].position = new CANNON.Vec3(3, 0, 0);
break;
case 1:
balls[i].position = new CANNON.Vec3(0, 3, 0);
break;
case 2:
balls[i].position = new CANNON.Vec3(0, 0, 3);
break;
}
world.addBody(balls[i]);
}
var fixedTimeStep = 1.0 / 60.0; // seconds
var maxSubSteps = 3;
var lastTime;
var lastCameraEularAngles;
physics();
// ほとんどCANNON.jsのサンプル通りです(笑)
// CANNON.jsで座標計算されるたびにmeshの座標に反映しています。
function physics(time){
if(lastTime !== undefined){
var dt = (time - lastTime) / 1000;
world.step(fixedTimeStep, dt, maxSubSteps);
}
balls.forEach(function(v, i){
gr("#main")("#sphere" + i).setAttribute("position", v.position.x + "," + v.position.y + "," + v.position.z);
});
lastTime = time;
// 「cameraから見て上向きのベクトル」なるものをGrimoire.jsは標準搭載しているそうです。
// そのベクトルを逆にして重力加速度倍しています。
var camera = gr("#main")("#camera").get(0);
var t = camera.getComponent("Transform");
var d = t.up.negateThis().multiplyWith(9.82);
world.gravity.set(d.X,d.Y,d.Z);
requestAnimationFrame(physics);
}
});
いやあ。描画も演算もライブラリがやってくれて相当簡単になっているとはいえ、3次元の物理演算って難しいですね。ちょっとナメてました。
Grimoire.js×物理演算。
ゲームだったり、物理法則に従ったWebページの装飾なんかもおもしろそうですね。
Grimoireの可能性を少し広げられたかな〜
興味を持たれた方はぜひ公式サイトからチュートリアルをご覧になって、試してみてください。
おもしろいものができたらQiitaに載せてくださいね!
あなたの記事とGrimoireタグの繁栄を楽しみにしています(笑)