Sausage Erectos X ブラウザで動くキネマティクス——関節システムの実装
キネマティクス実装──Sausage Erectosの関節システム
Sausage Erectosのカスタマイザーでは、フィギュアのポーズを関節ベースで制御する。この関節システムは、ロボット工学で使われるキネマティクス(運動学)の理論に基づいて設計されている。
フォワードキネマティクス vs インバースキネマティクス
関節の制御には2つの基本的なアプローチがある。
フォワードキネマティクス(FK): 各関節の角度を直接指定し、末端(手先や足先)の位置を計算する。
入力: 肩の角度=30°、肘の角度=45°、手首の角度=10°
出力: 手先の位置(x, y, z)
インバースキネマティクス(IK): 末端の目標位置を指定し、各関節の角度を逆算する。
入力: 手先の目標位置(x, y, z)
出力: 肩の角度、肘の角度、手首の角度
Sausage Erectosでは、基本操作にはFKを、ドラッグ操作にはIKを使用するハイブリッド方式を採用している。
FKは実装が単純で直感的だ。各関節のスライダーを動かすと、連鎖的に子関節が動く。しかし、「手をこの位置に持っていきたい」という操作にはIKのほうが自然だ。
DH(デナビット=ハーテンベルグ)パラメータ
ロボット工学では、関節チェーンの幾何学的関係をDHパラメータで定義する。各関節は4つのパラメータで記述される:
θ (theta): 関節角度(回転関節の変数)
d: リンクオフセット
a: リンク長さ
α (alpha): リンクひねり角度
Sausage Erectosの右腕の例:
関節 θ(可変) d a α
------------------------------------------
肩(回転) θ1 0 0 -π/2
肩(屈伸) θ2 0 40mm 0
肘 θ3 0 35mm 0
手首 θ4 0 20mm 0
各関節間の変換行列は:
function dhMatrix(theta, d, a, alpha) {
const ct = Math.cos(theta), st = Math.sin(theta);
const ca = Math.cos(alpha), sa = Math.sin(alpha);
return new THREE.Matrix4().set(
ct, -st*ca, st*sa, a*ct,
st, ct*ca, -ct*sa, a*st,
0, sa, ca, d,
0, 0, 0, 1
);
}
角度制限の実装
現実の関節と同様に、各関節には可動範囲の制限がある。肘は逆方向に曲がらないし、首は360度回転しない。
class Joint {
constructor(name, axis, minAngle, maxAngle) {
this.name = name;
this.axis = axis; // 回転軸 ('x', 'y', 'z')
this.minAngle = minAngle; // 最小角度(ラジアン)
this.maxAngle = maxAngle; // 最大角度(ラジアン)
this.currentAngle = 0;
this.children = [];
}
setAngle(angle) {
// 制限範囲内にクランプ
this.currentAngle = Math.max(
this.minAngle,
Math.min(this.maxAngle, angle)
);
this.updateTransform();
}
updateTransform() {
// 自身の回転を更新
this.bone.rotation[this.axis] = this.currentAngle;
// 子関節も連鎖的に更新
for (const child of this.children) {
child.updateTransform();
}
}
}
Sausage Erectosの主要関節の可動範囲:
首: 左右 ±60° 前後 ±45°
肩: 全方向 ±150°
肘: 屈曲 0〜145°(過伸展は不可)
手首: 全方向 ±80°
股関節: 全方向 ±120°
膝: 屈曲 0〜140°
足首: 上下 ±45° 左右 ±30°
ドラッグ操作のRaycast実装
ユーザーが3D空間内の関節をマウスでドラッグしてポーズを変更する機能は、Raycasterを使って実装している。
function onMouseMove(event) {
if (!selectedJoint) return;
// マウス位置からレイを計算
raycaster.setFromCamera(mouse, camera);
// ドラッグ平面との交点を計算
const plane = new THREE.Plane(
camera.getWorldDirection(new THREE.Vector3()).negate(),
-selectedJoint.bone.position.dot(
camera.getWorldDirection(new THREE.Vector3())
)
);
const intersection = new THREE.Vector3();
raycaster.ray.intersectPlane(plane, intersection);
// 交点から関節角度を計算(IK)
solveIK(selectedJoint, intersection);
}
関節の親子関係のツリー構造
Sausage Erectosの骨格は、以下のようなツリー構造で管理されている:
root (腰)
├── spine (背骨)
│ ├── neck (首)
│ │ └── head (頭)
│ ├── shoulder_L (左肩)
│ │ ├── elbow_L (左肘)
│ │ │ └── wrist_L (左手首)
│ └── shoulder_R (右肩)
│ ├── elbow_R (右肘)
│ │ └── wrist_R (右手首)
├── hip_L (左股関節)
│ ├── knee_L (左膝)
│ │ └── ankle_L (左足首)
└── hip_R (右股関節)
├── knee_R (右膝)
│ └── ankle_R (右足首)
親関節が回転すると、すべての子関節も連動して動く。これにより、肩を回すと肘も手首も自然に追従する。
キネマティクスは数学の言葉で「動き」を記述する技術だ。ソーセージのフィギュアにロボット工学の理論を適用する。この過剰さこそが、Sausage Erectosの本質なのかもしれない。