Babylon.js Physics V2 Havok でモーターを使う

これまで何度か、Babylon.js 物理エンジンHavokが使用された Physics V2 を試してきたが、できないことがあった。
それは『モーター』。

Physics V1 には、モーターがあったが、Physics V2 にはない。
Babylon.js v7 が公開されても様子が変わらない様子だった。

しかし、正確にはドキュメントにそれがあるように書かれていないということが分かった。

コミュニティに聞いてみたりなどして、状況が分かったので一通りまとめておく。

参考

事の経緯

実装だけ知りたい人は読み飛ばしてください。

2023年10月

「Physics V2 で ロボットを歩かせられないか」という意図で、物体の位置を任意に変更できないか質問。

その際に共有したのがこの画像。

参考として、ゾイドの動画など共有したりした。
結論はここでは出ず、Babylon.jsのフォーラムで質問することになった。

https://forum.babylonjs.com/t/havok-physics-on-walk/45184

ここでは、制約(Constraint)をうまく使うことできるらしいということ答えがあった。
この時直接的に答えてくれた方はこのトピックでは応答が無くなってしまった。

同じ時期にモーターが、Physics V1 にはあるが、Physics V2 では紹介されていないということが分かった。

この頃はどうにもならなそうなのが分かったので、一旦放置。

以降しばらく、サーバー上での物理演算を先に処理してしまうパターンなんかをやっていた。

2024年2月

モータが導入されるまでまだまだ時間を要しそうであることが分かったので、試験的にモーターのような1方向に物体を動かすことを試したりしていた。

https://playground.babylonjs.com/#Z8HTUN#852

この時は、setAngularVelocity で物体を動かすことで、実装した。

この時、スライダクランク機構など作った。(今プレイグラウンドを見ると、吹き飛ぶようになってしまっているけども)

https://playground.babylonjs.com/#Z8HTUN#855

この時もフォーラムに投稿して、実装方法を伺ったりしている。
https://forum.babylonjs.com/t/want-to-reproduce-the-movement-like-a-motor/47801

2024年4月13日

Babylon.js v7 がリリースされた。
しかし、ドキュメント(紹介)を見るにモーターの仕様はまだない。

なので、リポジトリの方でIssueを立ててみた。

https://github.com/BabylonJS/Babylon.js/issues/14979

結論からすると、一旦フォーラムで現状をまとめてくれということだった。
なので、フォーラムへ再投稿。

https://forum.babylonjs.com/t/are-motorized-constraints-still-being-introduced/49617

ここでの答えは、Physics6DoFConstraint.setAxisMotor があるということ。

機能紹介には書いていなかったりする。

https://doc.babylonjs.com/features/featuresDeepDive/physics/constraints

検索ボックスに、setAxisMotor と入れても出てこないが、Class Physics6DoFConstraint のドキュメントには記載がある。
(10月の時点で記載があったのかは確認していない。)

https://doc.babylonjs.com/typedoc/classes/BABYLON.Physics6DoFConstraint

そしてこのAPI正直使い方よくわからなくて(というか設定が面倒すぎる)とあまり触ってこなかったものだった。

(そして、HingeConstraint などよく使いそうなものには、メソッドが生えていなかったりする。)

といろいろありつつ、モーターを使えることは分かった。
親切に教えてくれたフォーラムの型に感謝したい。

しかし、問題はまだあり、モーターの回転方法を拾う方法がわからない。
これは同じトピックでそのまま聞いてみたが、Physics6DoFConstraint.setAxisMotor を教えてくれた人も知らなかった。

2024年4月20日

この辺りで、Babylon.jsを主題において質問する/調べるのはもうおそらく無理なのが見えてきていた。
なので、クオータニオンとかの数学レベルとかUnity関連の計算処理の方法を参考に調査をしていた。
おそらくできるだろうとは思いつつ、4日ほど調べることになってしまった。
途中CopilotChatとChatGPTも駆使したけど、いいとこ行くんだけど正解は出ない感じが続いてた。

でいろいろあって、Qiitaの記事を見つけた。

Qiita - 【THREE.js】Quaternionでローカル座標の回転差分を求める

これで、2物体間がなす角度の処理方法が分かった。
少し手直ししてBabylon.jsに適応させた。

実装

モーターの使い方

以下のようにモーターが使える。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
 // 物体1 親
const boxMesh1 = BABYLON.MeshBuilder.CreateBox(
`box1`,
{ height: 3, width: 3, depth: 3 },
scene,
);
boxMesh1.position = new BABYLON.Vector3(0, 1.5, 0);
boxMesh1.material = baseMaterial;

const physicsBox1 = new BABYLON.PhysicsAggregate(
boxMesh1,
BABYLON.PhysicsShapeType.BOX,
{ mass: 1, friction: 0.5, restitution: 0.5 },
scene,
);

 // 物体2 子
const boxMesh2 = BABYLON.MeshBuilder.CreateBox(
`box2`,
{ height: 3, width: 3, depth: 3 },
scene,
);
boxMesh2.position = new BABYLON.Vector3(0, 4.5, 0);
boxMesh2.material = baseMaterial;

const physicsBox2 = new BABYLON.PhysicsAggregate(
boxMesh2,
BABYLON.PhysicsShapeType.BOX,
{ mass: 1, friction: 0.5, restitution: 0.5 },
scene,
);

// 6DoFConstraint( = モーター) の定義
// Z軸回転に制限をしていないので、Z軸は自由に回転する
const box1Motor = new BABYLON.Physics6DoFConstraint(
{
pivotA: new BABYLON.Vector3(0, 1.5, 0),
pivotB: new BABYLON.Vector3(0, -1.5, 0),
},
[
{
axis: BABYLON.PhysicsConstraintAxis.LINEAR_X,
minLimit: 0,
maxLimit: 0,
},
{
axis: BABYLON.PhysicsConstraintAxis.LINEAR_Y,
minLimit: 0,
maxLimit: 0,
},
{
axis: BABYLON.PhysicsConstraintAxis.LINEAR_Z,
minLimit: 0,
maxLimit: 0,
},
{
axis: BABYLON.PhysicsConstraintAxis.ANGULAR_Y,
minLimit: 0,
maxLimit: 0,
},
{
axis: BABYLON.PhysicsConstraintAxis.ANGULAR_X,
minLimit: 0,
maxLimit: 0,
},
],
scene,
);
// 物体1 物体2を6DoFConstraint( = モーター)でつなぐ
physicsBox1.body.addConstraint(physicsBox2.body, box1Motor);

// 回転方向やトルクの設定
box1Motor.setAxisMotorType(BABYLON.PhysicsConstraintAxis.ANGULAR_Z, BABYLON.PhysicsConstraintMotorType.VELOCITY);
box1Motor.setAxisMotorMaxForce(BABYLON.PhysicsConstraintAxis.ANGULAR_Z, 200);
box1Motor.setAxisMotorTarget(BABYLON.PhysicsConstraintAxis.ANGULAR_Z, Math.PI);

こんな具合で、Physics6DoFConstraint を使って、モーターを作成できる。
setAxisMotorMaxForce を下げると自重で回転してしまったり、外力で動いてしまったりする。
トルクを0付近にしてあげると「ある瞬間からラグドールみたいになる」ということもできるだろう。
Physics6DoFConstraint での制限は、プロパティは多数あるがこのくらいが使いやすいのではないだろうか。

2物体のなす角度を取得する

以下の処理をかませると、2物体のクオータニオンの差分から、角度(ラジアン)を取得できる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function getAegleFromQuaternions(
q1: BABYLON.Quaternion,
q2: BABYLON.Quaternion,
axis: BABYLON.Vector3,
) {
const rotationQuaternion1 = q1.clone();
const rotationQuaternion2 = q2.clone();

const diffRotation = rotationQuaternion1.invert().multiply(
rotationQuaternion2,
);

if (diffRotation.length() == 1) {
return 0;
}

const halfThetas = [
(axis.x) ? Math.atan2(diffRotation.x / axis.x, diffRotation.w) : null,
(axis.y) ? Math.atan2(diffRotation.y / axis.y, diffRotation.w) : null,
(axis.z) ? Math.atan2(diffRotation.z / axis.z, diffRotation.w) : null,
];

const halfTheta = halfThetas.find(function (halfTheta) {
return halfTheta;
});

if (Math.abs(halfTheta * 2) > Math.PI) {
const theta = 2 * Math.PI - Math.abs(halfTheta * 2);
return halfTheta > 0 ? -theta : theta;
}
return halfTheta * 2;
}

使う側はこんな感じ。

1
2
3
4
5
6
7
8
9
const box1To2ConstraintAxis = new BABYLON.Vector3(0, 0, 1);

const box1To2Angle = getAegleFromQuaternions(
boxMesh1.rotationQuaternion,
boxMesh2.rotationQuaternion,
box1To2ConstraintAxis,
);

const box1to2AngleDeg = Math.round(box1To2Angle / Math.PI * 180 * 100) /100;

この場合、物体2つのZ軸を基準にした角度が取得できる。

これを使ってプレイグラウンドでサンプルを作った。

落ちてくる球体の中で、常に90度保ちながら左右に回転させている。
物理エンジンが動いているので、重いもの(大きいもの)が当たると、首が下がるが、フィードバックが効いているのでもとに戻る動きをする。

https://playground.babylonjs.com/#XTFBWG


というわけで、Babylon.js Physics V2 Havok でモーターを使う方法がわかった。
さらに物体間のなす角まで取得する方法もわかったので、いろいろとこの半年くらい時間を使っていたことが解決した。
途中チェビシェフリンク機構なども作ったりしていたが、これはこれで限界が見えていたので、これからはモーターを使っていく。

今回作ったデモで、落ちてくる球を振り払って飛んでいく姿を見ていたら、昔トミーから出ていたヤキューマンのことを思い出したので、気がむいたら作る。(投手だけやもしれないけど)

では。