了解。
ここは 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 仕様 を詰めに行こうか。