Skip to content

forge3d.animation — Skeletal Animation

forge3d's animation system provides skeletal animation with keyframe blending, blend trees, and FABRIK inverse kinematics.


Core classes

Bone dataclass

골격 트리의 단일 링크.

local_matrix: 부모 좌표계에서의 (4,4) 변환 행렬 (바인드 포즈). parent_idx: 부모 본 인덱스. 루트는 None.

Skeleton dataclass

본 계층 집합 — 순방향 기구학(FK) 계산.

world_matrices

world_matrices(local_overrides: dict[str, ndarray] | None = None) -> np.ndarray

(N, 4, 4) 월드 행렬 배열을 반환한다.

Parameters:

Name Type Description Default
local_overrides dict[str, ndarray] | None

{bone_name: (4,4) 로컬 행렬} — AnimationClip sample() 결과. None이면 바인드 포즈 사용.

None

joint_positions

joint_positions(local_overrides: dict[str, ndarray] | None = None) -> np.ndarray

(N, 3) 각 본의 월드 공간 위치.

chain classmethod

chain(positions: list[ndarray], names: list[str] | None = None) -> Skeleton

단일 체인(링크 1개씩 연결) 골격 생성 헬퍼.

positions: [(3,)] 각 관절의 월드 위치 리스트

AnimationClip dataclass

키프레임 기반 애니메이션 클립.

{bone_name: (T, 10) float64}

각 행: [time, pos_x, pos_y, pos_z, quat_w, quat_x, quat_y, quat_z, scale_x, scale_y, scale_z]

sample

sample(t: float) -> dict[str, np.ndarray]

시각 t에서 각 본의 (4,4) 로컬 행렬을 반환한다.

constant classmethod

constant(name: str, duration: float, poses: dict[str, tuple[ndarray, ndarray, ndarray]]) -> AnimationClip

단일 정적 포즈를 상수 클립으로 생성 (테스트용).

poses: {bone_name: (pos(3), quat(4), scale(3))}

AnimationPlayer dataclass

Bases: Component

골격 애니메이션 재생기 ECS 컴포넌트.

world_matrices property

world_matrices: ndarray

현재 _time 기준 (N, 4, 4) 월드 행렬. advance()를 호출해야 갱신된다.

advance

advance(dt: float) -> dict[str, np.ndarray]

시간을 dt 만큼 전진시키고 현재 포즈(본 로컬 행렬 딕셔너리)를 반환한다.

BlendTree dataclass

1D 파라미터 기반 두 클립 가중 블렌딩.

parameter in [0.0, 1.0]: 0→clip_a, 1→clip_b

sample

sample(t: float) -> dict[str, np.ndarray]

시각 t에서 두 클립 간 가중 보간 결과를 반환한다.

IKTarget dataclass

Bases: Component

FABRIK IK의 목표 위치를 나타내는 ECS 컴포넌트.

FABRIKSolver

N링크 체인 FABRIK IK.

체인은 [(3,)] 링크 위치 리스트로 표현된다. 링크 길이는 초기 체인에서 자동으로 계산된다.

solve

solve(chain: list[ndarray], target: ndarray, max_iterations: int = 20, tolerance: float = 0.0001) -> list[np.ndarray]

수렴된 링크 위치 리스트를 반환한다.

Parameters:

Name Type Description Default
chain list[ndarray]

[(3,)] 관절 위치. chain[0]이 루트(고정), chain[-1]이 끝단.

required
target ndarray

(3,) 목표 위치.

required
max_iterations int

최대 반복 횟수.

20
tolerance float

끝단과 목표 간 허용 오차 (m).

0.0001

Returns:

Type Description
list[ndarray]

수렴된 관절 위치 리스트 (chain과 같은 길이).

AnimationSystem

Bases: System

AnimationPlayer 컴포넌트를 매 프레임 전진시키고 Transform을 동기화한다.


Usage examples

Define a skeleton

import numpy as np
from forge3d import Bone, Skeleton

# Each Bone needs a name, a local bind-pose matrix (4×4), and an optional parent index.
def _translate(x, y, z):
    m = np.eye(4)
    m[:3, 3] = [x, y, z]
    return m

bones = [
    Bone(name="root",       local_matrix=np.eye(4),          parent_idx=None),
    Bone(name="hip",        local_matrix=_translate(0,0,0.9), parent_idx=0),
    Bone(name="spine",      local_matrix=_translate(0,0,0.3), parent_idx=1),
    Bone(name="l_shoulder", local_matrix=_translate(-0.3,0,0.2), parent_idx=2),
    Bone(name="l_elbow",    local_matrix=_translate(-0.28,0,0), parent_idx=3),
    Bone(name="l_wrist",    local_matrix=_translate(-0.25,0,0), parent_idx=4),
]
skeleton = Skeleton(bones=bones)

Load and play an animation clip

import numpy as np
from forge3d import AnimationClip, AnimationPlayer

# Create a clip with keyframes
clip = AnimationClip(
    name="wave",
    duration=1.0,
    keyframes={
        "l_shoulder": [(0.0, np.zeros(3), np.array([1,0,0,0])),   # (time, pos, quat)
                        (0.5, np.zeros(3), np.array([0.707,0,0,0.707])),
                        (1.0, np.zeros(3), np.array([1,0,0,0]))],
    }
)

player = AnimationPlayer(skeleton=skeleton)
player.play(clip, loop=True)

# Advance by one frame
player.update(dt=1/60)
pose = player.current_pose()   # dict[bone_name, (pos, quat, scale)]

Blend tree (locomotion)

from forge3d import BlendTree

# Blend between idle and walk clips based on speed
blend = BlendTree(clip_a=idle_clip, clip_b=walk_clip)
blend.parameter = 0.6    # 0=idle, 1=walk

player = AnimationPlayer(skeleton=skeleton, blend_tree=blend)
player.update(dt=1/60)

FABRIK inverse kinematics

from forge3d import FABRIKSolver, IKTarget

solver = FABRIKSolver(
    skeleton=skeleton,
    chain=["l_shoulder", "l_elbow", "l_wrist"],   # bone chain
    iterations=10,
    tolerance=0.001,   # 1 mm
)

target = IKTarget(
    position=np.array([0.4, 0.1, 1.2]),   # world-space target
    weight=1.0,
)

pose = player.current_pose()
solved_pose = solver.solve(pose, target)

AnimationSystem (ECS integration)

import forge3d as f3d

ew = f3d.EntityWorld()
anim_system = f3d.AnimationSystem()
ew.add_system(anim_system)

# Attach player to an entity
entity = ew.create_entity(player)

ew.step(dt=1/60)    # AnimationSystem.update() called automatically