了解。
ここは OS の“関節ごとの局所座標系(local joint axes)”を定義するという、
PoseSpace → joint の数学の中でも最重要の部分だね。
いまから示す Python の例は:
- 各 joint が「固有の bend 軸」「固有の twist 軸」を持つ
- 親 joint の回転を継承しつつ、ローカル軸で回転する
- OS の責務分離(意味 → 構造 → 2.5D)を壊さない
という条件を満たした、OS 仕様に耐える最小実装。
🧩 A-1:joint ごとに bend / twist の軸を定義する Python 実装
🔧 1. 各 joint に「ローカル軸」を持たせる
- bend_axis:曲げの軸(例:肘は x 軸)
- twist_axis:ひねりの軸(例:腕は y 軸)
- base:T-pose のベース方向
import numpy as np
# 単位ベクトル
def norm(v):
return v / np.linalg.norm(v)
# 任意軸まわりの回転行列(Rodrigues)
def rot_axis(axis, theta):
axis = norm(axis)
x, y, z = axis
c, s = np.cos(theta), np.sin(theta)
C = 1 - c
return np.array([
[c + x*x*C, x*y*C - z*s, x*z*C + y*s],
[y*x*C + z*s, c + y*y*C, y*z*C - x*s],
[z*x*C - y*s, z*y*C + x*s, c + z*z*C]
])
🔧 2. joint のローカル軸を定義する
例として:
- 肩:bend=前後、twist=腕の軸まわり
- 肘:bend=曲げのみ、twist=0
- 首:bend=前後、twist=左右
skeleton = {
"root": {
"parent": None,
"base": np.array([0, 1, 0]),
"bend_axis": np.array([1, 0, 0]),
"twist_axis": np.array([0, 1, 0])
},
"shoulder": {
"parent": "root",
"base": np.array([1, 0, 0]),
"bend_axis": np.array([0, 0, 1]), # 前後に上げる
"twist_axis": np.array([1, 0, 0]) # 腕の軸まわり
},
"elbow": {
"parent": "shoulder",
"base": np.array([1, 0, 0]),
"bend_axis": np.array([0, 0, 1]), # 肘は曲げのみ
"twist_axis": np.array([1, 0, 0]) # ひねりは基本ゼロ
}
}
🔧 3. PoseVector をローカル軸で適用する
def apply_pose_local(base_vec, bend, twist, bend_axis, twist_axis):
R_bend = rot_axis(bend_axis, bend)
R_twist = rot_axis(twist_axis, twist)
return R_twist @ (R_bend @ base_vec)
🔧 4. 親子構造で joint の絶対座標を計算する
親の回転を継承しつつ、
各 joint のローカル軸で回転する。
def compute_joint_positions(pose_dict, skeleton):
positions = {}
rotations = {} # 親から継承する回転
def solve(joint):
if joint in positions:
return positions[joint]
info = skeleton[joint]
parent = info["parent"]
bend = pose_dict[joint]["bend"]
twist = pose_dict[joint]["twist"]
# ローカル軸
bend_axis = info["bend_axis"]
twist_axis = info["twist_axis"]
# T-pose ベース方向
base = info["base"]
# 親の回転を取得
if parent is None:
R_parent = np.eye(3)
p_parent = np.zeros(3)
else:
solve(parent)
R_parent = rotations[parent]
p_parent = positions[parent]
# ローカル回転
R_local = rot_axis(twist_axis, twist) @ rot_axis(bend_axis, bend)
# 累積回転
R = R_parent @ R_local
rotations[joint] = R
# 絶対座標
positions[joint] = p_parent + R @ base
return positions[joint]
for j in skeleton.keys():
solve(j)
return positions
🧠 これで OS の「関節ごとの局所座標系」が完成する
この実装のポイントは:
✔ 1. 各 joint が「固有の bend / twist 軸」を持つ
→ 肩・肘・首・腰で軸が違う
→ 人体の自然な動きに近づく
✔ 2. 親 joint の回転を継承する
→ 肩を上げると肘の向きも変わる
→ OS の階層構造が正しく働く
✔ 3. ローカル軸 → 親軸 → ワールド軸 の三層構造
これはあなたの OS の
- 意味レイヤ(PoseVector)
- 構造レイヤ(joint)
- 表示レイヤ(SVG)
の責務分離と完全に一致している。
✨ 次に進めるなら…
A-1 の次は A-2:回転順序(肩・首の複合回転) を固めると OS が一気に安定する。
- bend → twist の順か
- twist → bend の順か
- joint ごとに順序を変えるか
ここを決めると、
キャラが「斜め向き」になったときの破綻が消える。
次は 回転順序の OS 仕様 を詰めに行こうか。