ジョイント
Jointコンポーネントはアルファ版のため、その挙動やAPIは変わる可能性があります。すべての環境でコードから利用でき、Web Componentsではタグとしても利用できますが、エディターにはまだ専用のインスペクターがなく、PlayCanvas Reactには対応するコンポーネントがありません。
ジョイントは2つのRigidBodyの相対的な動きを拘束します。ヒンジで回るドア、レールの上を滑る引き出し、リンクの連なった鎖、スプリングで上下に揺れる木箱、あるいは何かが強くぶつかって溶接が壊れるまで一体になっているブロックの塔などです。
ジョイントの仕組み
ジョイントは、独自のエンティティに付いたJointComponentです。そのエンティティ自身は拘束されません。そのワールドトランスフォームがジョイントフレームを定義します。位置は拘束が作用するアンカーポイントで、ローカルX軸が主軸です。ヒンジはXを中心に回転し、スライダーはXに沿って移動し、ボールジョイントはXを中心にひねります。ジョイントの向きを決めるには、ジョイントのエンティティを回転させます。
拘束する2つのボディはentityAとentityBで設定し、どちらにもrigidbodyコンポーネントが必要です。entityBを空にすると、entityAはワールド空間の固定点に拘束されます。これが何もないところに物を吊り下げる方法です。よくある構成は、ジョイントのエンティティをピボット位置でentityAの子にすることで、ピボットが属する部品と一緒に動くようになります。
つまずきやすい点が2つあります。
- フレームは一度だけ取得されます。 取得は拘束が作成されるときで、通常はコンポーネントが有効になり、両方のボディがシミュレーションに存在した時点です。その後にジョイントのエンティティを動かしても、コンポーネントの
refreshFrames()を呼び出して現在のトランスフォームからフレームを再取得するまで何も変わりません。エンティティのスケールはRigidBodyと同様、全体を通して無視されます。 - リミットは開始時の姿勢を基準に測られます。 ジョイントのエンティティが基準ではありません。拘束が作成された瞬間に2つのジョイントフレームは一致するため、すべての自由度はそこで0になります。ヒンジのリミット
0から110度は「ボディの開始位置からさらに110度まで」という意味であり、絶対的な角度ではありません。
ジョイントのタイプ
typeプロパティで拘束の種類を選び、それによって他のどのプロパティが意味を持つかが決まります。
| タイプ | 定数 | 拘束する内容 | 使用するプロパティ |
|---|---|---|---|
fixed | JOINTTYPE_FIXED | ボディ同士を剛体的に固定する | なし |
ball | JOINTTYPE_BALL | ボールソケット:アンカーを中心に自由に回転し、オプションでスイングとツイストのリミットを持つ | swingLimitY、swingLimitZ、twistLimit |
hinge | JOINTTYPE_HINGE | フレームのX軸を中心とした回転。オプションでリミットとモーターを持つ | limits、motorSpeed、maxMotorForce |
slider | JOINTTYPE_SLIDER | フレームのX軸に沿った移動。オプションでリミットとモーターを持つ | limits、motorSpeed、maxMotorForce |
6dof | JOINTTYPE_6DOF | 各直線軸と回転軸を個別にロック、制限、自由に設定し、オプションでスプリングを持つ | linearMotionX/Y/Z、angularMotionX/Y/Z、linearLimitsX/Y/Z、angularLimitsX/Y/Z、linearStiffness、angularStiffness、linearDamping、angularDamping、linearEquilibrium、angularEquilibrium |
すべてのタイプは、2つのボディが互いに衝突し続けるかどうかを決めるenableCollision(デフォルトはオフ)と、breakImpulseも受け付けます。
6dofジョイントでは各軸がMOTION_LOCKEDから始まるため、他に何も設定しなければfixedジョイントと同じように振る舞います。動かしたい軸をMOTION_FREEまたはMOTION_LIMITEDで開放し、制限した軸には対応するリミットを設定します。ある軸に0より大きい剛性を設定すると、その軸は平衡値に向かって引き戻すスプリングになります。
ジョイントの追加
このドアヒンジは、Dynamicなドアを Staticなドア枠に拘束します。ジョイントのエンティティはヒンジの線上に置き、ローカルX軸が上を向くように回転させ、0から110度のリミットでドアが一方向にだけ開くようにします。
- Engine
- Editor
- React
- Web Components
const hinge = new pc.Entity('hinge');
hinge.setPosition(1, 1, 0);
hinge.setEulerAngles(0, 0, 90); // ローカルX軸が上を向く
hinge.addComponent('joint', {
type: pc.JOINTTYPE_HINGE,
entityA: door,
entityB: doorFrame,
enableLimits: true,
limits: new pc.Vec2(0, 110)
});
app.root.addChild(hinge);
pc.ApplicationはJointコンポーネントシステムを登録します。pc.AppBaseから構築する場合は、CollisionとRigidBodyのシステムと共にpc.JointComponentSystemをAppOptions.componentSystemsに追加してください。
Jointのインスペクターはまだないため、ヒンジのエンティティにアタッチしたスクリプトからコンポーネントを追加します。エンティティの位置と回転はいつもどおりビューポートで設定し、2つのボディをスクリプトのアトリビュートに割り当てます。
import { Script, Vec2, JOINTTYPE_HINGE } from 'playcanvas';
export class DoorHinge extends Script {
static scriptName = 'doorHinge';
/**
* ドア。DynamicなRigidBody。
*
* @attribute
* @type {Entity}
*/
door;
/**
* ドアを吊るドア枠。StaticなRigidBody。
*
* @attribute
* @type {Entity}
*/
frame;
initialize() {
this.entity.addComponent('joint', {
type: JOINTTYPE_HINGE,
entityA: this.door,
entityB: this.frame,
enableLimits: true,
limits: new Vec2(0, 110)
});
}
}
<Joint>コンポーネントはまだありません。物理演算の読み込み後に、ヒンジの<Entity>の中のエフェクトからエンジンのコンポーネントを追加し、refで2つのボディにアクセスします。
import { useEffect, useRef } from 'react';
import { JOINTTYPE_HINGE, Vec2 } from 'playcanvas';
import { Entity } from '@playcanvas/react';
import { useParent, usePhysics } from '@playcanvas/react/hooks';
function Hinge({ door, frame }) {
const entity = useParent();
const { isPhysicsLoaded } = usePhysics();
useEffect(() => {
if (!isPhysicsLoaded || !door.current || !frame.current) return;
entity.addComponent('joint', {
type: JOINTTYPE_HINGE,
entityA: door.current,
entityB: frame.current,
enableLimits: true,
limits: new Vec2(0, 110)
});
return () => entity.removeComponent('joint');
}, [entity, isPhysicsLoaded, door, frame]);
return null;
}
function Door() {
const door = useRef();
const frame = useRef();
return (
<>
<Entity ref={frame} name="frame" position={[1, 1, 0]}>{/* コリジョンとStaticなRigidBody */}</Entity>
<Entity ref={door} name="door" position={[1.9, 1, 0]}>{/* コリジョンとDynamicなRigidBody */}</Entity>
<Entity name="hinge" position={[1, 1, 0]} rotation={[0, 0, 90]}>
<Hinge door={door} frame={frame} />
</Entity>
</>
);
}
<pc-entity name="hinge" position="1 1 0" rotation="0 0 90">
<pc-joint type="hinge" entity-a="#door" entity-b="#door-frame"
enable-limits limits="0 110"></pc-joint>
</pc-entity>
すべての属性と各ジョイントタイプのライブ例は<pc-joint>リファレンスを参照してください。
モーター、リミット、破断
リミットは二重にオプトインです。enableLimitsを設定し、かつリミット値を指定する必要があります。limitsを設定してもenableLimitsがfalseのままのヒンジは自由に回ります。
ヒンジとスライダーにはモーターがあります。モーターはmaxMotorForceが0より大きい間だけ作動し、motorSpeedの速さでジョイントを駆動します。単位はヒンジでは度/秒、スライダーではユニット/秒です。リミットが有効なモーターはストッパーで止まります。
// 風車:ヒンジ軸を中心にローターを毎秒90度で回す
hinge.joint.motorSpeed = 90;
hinge.joint.maxMotorForce = 100;
どのジョイントも壊れることがあります。breakImpulseに拘束が壊れるインパルスのしきい値を設定します。デフォルトはInfinityで、決して壊れません。壊れるとコンポーネントはbreakイベントを発火し、isBrokenがtrueになり、ボディは独立して動くようになります。refreshFrames()を呼び出すと、ボディの現在のトランスフォームからジョイントが再接続されます。
const weld = jointEntity.joint;
weld.breakImpulse = 60;
weld.on('break', () => {
console.log('The weld snapped');
});
拘束の状態を公開していないammo.jsのビルドでは、6dofジョイントの破断を検出できないため、これらのジョイントではbreakイベントが発火しません。他のジョイントタイプには影響しません。
関連情報
- JointComponent - すべてのジョイントプロパティのAPIリファレンス
<pc-joint>- Web Componentsでの同じジョイント。各タイプのライブ例付き- Ragdoll - ボールジョイントとヒンジジョイントでキャラクターを組み立てるエンジンの例
- RigidBody - すべてのジョイントが拘束するボディ