skeleton
stableCreate and manipulate skeletal rigs with named bones and parent-child hierarchy, and play back animation clips with keyframe interpolation and blending.
use plugin skeleton::{Skeleton.new, Skeleton.add_bone, Skeleton.get_bone, …} Functions (17)
- Skeleton.new Create a new empty skeleton rig
- Skeleton.add_bone Add a bone to the skeleton
- Skeleton.get_bone Get bone data by index
- Skeleton.set_bone_position Set a bone's local position
- Skeleton.set_bone_rotation Set a bone's local rotation (Euler)
- Skeleton.bone_count Get the number of bones
- Skeleton.bone_names Get all bone names as a table
- Skeleton.find_bone Find a bone index by name
- Skeleton.set_parent Reparent a bone
- Skeleton.remove_bone Remove a bone and fix parent references
- Skeleton.get_world_transform Get accumulated world position of a bone
- AnimClip.new Create a new animation clip with a duration
- AnimClip.add_keyframe Add a positional keyframe for a bone
- AnimClip.sample Sample all bone transforms at a time
- AnimClip.duration Get the clip's total duration
- AnimClip.keyframe_count Get the total number of keyframes
- AnimClip.blend Blend two clips together at a given weight
Overview
skeleton models a character rig as a flat array of named bones, each carrying a
local position, a local Euler rotation, and an integer parent index (-1 marks a
root). A skeleton is an opaque Skeleton handle: you create one with
Skeleton.new(), append bones with add_bone, and address every bone by the
integer index that add_bone returns. World-space position is derived on demand
by walking the parent chain, so reparenting or moving a bone automatically affects
its descendants.
Animation lives in a separate AnimClip handle. A clip has a duration and a list
of positional keyframes, each tied to a bone index, kept sorted by time. Sampling
a clip linearly interpolates between surrounding keyframes, and two clips can be
cross-faded with blend. Reach for this plugin whenever you need lightweight
hierarchical transforms plus keyframed playback without pulling in a full
animation engine.
Common patterns
Build a small rig and read a child bone's accumulated world position:
use plugin skeleton::{Skeleton}
let rig = Skeleton.new()
let hip = rig.add_bone("hip", -1, 0.0, 1.0, 0.0)
let spine = rig.add_bone("spine", hip, 0.0, 0.5, 0.0)
let head = rig.add_bone("head", spine, 0.0, 0.3, 0.0)
let world = rig.get_world_transform(head)
print("head world y: {world["y"]}")
Author a clip for a bone, then sample it midway through:
use plugin skeleton::{Skeleton, AnimClip}
let rig = Skeleton.new()
let hip = rig.add_bone("hip", -1, 0.0, 0.0, 0.0)
let clip = AnimClip.new(1.0)
clip.add_keyframe(0.0, hip, 0.0, 0.0, 0.0)
clip.add_keyframe(1.0, hip, 0.0, 2.0, 0.0)
let pose = clip.sample(0.5)
for _, t in pose {
print("bone {t["bone_index"]}: y={t["y"]}")
}
Cross-fade a walk and a run clip to drive a locomotion blend:
use plugin skeleton::{Skeleton, AnimClip}
let rig = Skeleton.new()
let spine = rig.add_bone("spine", -1, 0.0, 0.0, 0.0)
let walk = AnimClip.new(1.0)
walk.add_keyframe(0.0, spine, 0.0, 0.0, 0.0)
walk.add_keyframe(1.0, spine, 0.0, 0.2, 0.0)
let run = AnimClip.new(1.0)
run.add_keyframe(0.0, spine, 0.0, 0.0, 0.0)
run.add_keyframe(1.0, spine, 0.0, 0.5, 0.0)
let blended = walk.blend(run, 1.0, 0.75)
for _, t in blended {
print("blended y: {t["y"]}")
}
Create a new empty skeleton rig
Creates a new empty skeleton handle with no bones. Add bones with add_bone.
use plugin skeleton::{Skeleton}
let rig = Skeleton.new()
Add a bone to the skeleton
Adds a bone named name to the skeleton with a local position (x, y, z). Use parent_index = -1 for root bones. Returns the new bone's index.
use plugin skeleton::{Skeleton}
let rig = Skeleton.new()
let root = rig.add_bone("hip", -1, 0.0, 0.0, 0.0)
let spine = rig.add_bone("spine", root, 0.0, 1.0, 0.0)
let head = rig.add_bone("head", spine, 0.0, 0.5, 0.0)
Get bone data by index
Returns a table with bone data: name, parent, x, y, z, rx, ry, rz.
use plugin skeleton::{Skeleton}
let rig = Skeleton.new()
rig.add_bone("hip", -1, 0.0, 0.0, 0.0)
let bone = rig.get_bone(0)
print("bone: {bone["name"]} at ({bone["x"]}, {bone["y"]})")
Set a bone's local position
Sets the local position of the bone at index.
use plugin skeleton::{Skeleton}
let rig = Skeleton.new()
let idx = rig.add_bone("hip", -1, 0.0, 0.0, 0.0)
rig.set_bone_position(idx, 0.0, 1.0, 0.0)
Set a bone's local rotation (Euler)
Sets the local rotation (Euler angles) of the bone at index.
use plugin skeleton::{Skeleton}
let rig = Skeleton.new()
let idx = rig.add_bone("arm", -1, 1.0, 0.0, 0.0)
rig.set_bone_rotation(idx, 0.0, 0.0, 1.5707)
Get the number of bones
Returns the total number of bones in the skeleton.
use plugin skeleton::{Skeleton}
let rig = Skeleton.new()
rig.add_bone("hip", -1, 0.0, 0.0, 0.0)
rig.add_bone("spine", 0, 0.0, 1.0, 0.0)
print("bones: {rig.bone_count()}")
Get all bone names as a table
Returns a table mapping index to bone name for all bones.
use plugin skeleton::{Skeleton}
let rig = Skeleton.new()
rig.add_bone("hip", -1, 0.0, 0.0, 0.0)
rig.add_bone("spine", 0, 0.0, 1.0, 0.0)
let names = rig.bone_names()
for i, name in names {
print("{i}: {name}")
}
Find a bone index by name
Returns the index of the bone with name, or -1 if not found.
use plugin skeleton::{Skeleton}
let rig = Skeleton.new()
rig.add_bone("head", -1, 0.0, 1.8, 0.0)
let idx = rig.find_bone("head")
print("head is at index {idx}")
Reparent a bone
Changes the parent of the bone at index to parent_index. Use -1 to make it a root bone.
use plugin skeleton::{Skeleton}
let rig = Skeleton.new()
rig.add_bone("root", -1, 0.0, 0.0, 0.0)
rig.add_bone("child", -1, 1.0, 0.0, 0.0)
rig.set_parent(1, 0)
Remove a bone and fix parent references
Removes the bone at index. Parent indices of remaining bones that referenced it or were after it are automatically adjusted.
use plugin skeleton::{Skeleton}
let rig = Skeleton.new()
rig.add_bone("temp", -1, 0.0, 0.0, 0.0)
rig.remove_bone(0)
Get accumulated world position of a bone
Returns the accumulated world-space position {x, y, z} of the bone at index by summing positions up the parent chain.
use plugin skeleton::{Skeleton}
let rig = Skeleton.new()
let hip = rig.add_bone("hip", -1, 0.0, 1.0, 0.0)
let spine = rig.add_bone("spine", hip, 0.0, 0.5, 0.0)
let world = rig.get_world_transform(spine)
print("spine world y: {world["y"]}")
Because positions accumulate up the chain, moving a parent shifts every descendant's world transform without touching the child's local position:
use plugin skeleton::{Skeleton}
let rig = Skeleton.new()
let hip = rig.add_bone("hip", -1, 0.0, 1.0, 0.0)
let knee = rig.add_bone("knee", hip, 0.0, -0.5, 0.0)
rig.set_bone_position(hip, 0.0, 2.0, 0.0)
let world = rig.get_world_transform(knee)
print("knee world y: {world["y"]}")
Create a new animation clip with a duration
Creates a new animation clip lasting duration seconds with no keyframes.
use plugin skeleton::{AnimClip}
let clip = AnimClip.new(2.0)
Add a positional keyframe for a bone
Adds a positional keyframe for bone_index at time seconds with position (x, y, z). Keyframes are kept sorted by time automatically.
use plugin skeleton::{Skeleton, AnimClip}
let rig = Skeleton.new()
let hip = rig.add_bone("hip", -1, 0.0, 0.0, 0.0)
let clip = AnimClip.new(1.0)
clip.add_keyframe(0.0, hip, 0.0, 0.0, 0.0)
clip.add_keyframe(0.5, hip, 0.0, 1.0, 0.0)
clip.add_keyframe(1.0, hip, 0.0, 0.0, 0.0)
Sample all bone transforms at a time
Samples the clip at time seconds and returns a table of bone transforms, each with bone_index, x, y, z. Values are linearly interpolated between keyframes.
use plugin skeleton::{Skeleton, AnimClip}
let rig = Skeleton.new()
let hip = rig.add_bone("hip", -1, 0.0, 0.0, 0.0)
let clip = AnimClip.new(1.0)
clip.add_keyframe(0.0, hip, 0.0, 0.0, 0.0)
clip.add_keyframe(1.0, hip, 0.0, 2.0, 0.0)
let transforms = clip.sample(0.5)
for _, t in transforms {
print("bone {t["bone_index"]}: y={t["y"]}")
}
Sampling several bones at once returns one entry per animated bone, so you can drive a whole rig from a single call:
use plugin skeleton::{Skeleton, AnimClip}
let rig = Skeleton.new()
let arm = rig.add_bone("arm", -1, 1.0, 0.0, 0.0)
let leg = rig.add_bone("leg", -1, -1.0, 0.0, 0.0)
let clip = AnimClip.new(2.0)
clip.add_keyframe(0.0, arm, 1.0, 0.0, 0.0)
clip.add_keyframe(2.0, arm, 1.0, 1.0, 0.0)
clip.add_keyframe(0.0, leg, -1.0, 0.0, 0.0)
clip.add_keyframe(2.0, leg, -1.0, -1.0, 0.0)
for _, t in clip.sample(1.0) {
print("bone {t["bone_index"]} -> ({t["x"]}, {t["y"]})")
}
Get the clip's total duration
Returns the total duration of the clip in seconds.
use plugin skeleton::{AnimClip}
let clip = AnimClip.new(3.5)
print("duration: {clip.duration()}")
Get the total number of keyframes
Returns the total number of keyframes stored in the clip.
use plugin skeleton::{Skeleton, AnimClip}
let rig = Skeleton.new()
let idx = rig.add_bone("bone", -1, 0.0, 0.0, 0.0)
let clip = AnimClip.new(1.0)
clip.add_keyframe(0.0, idx, 0.0, 0.0, 0.0)
clip.add_keyframe(1.0, idx, 1.0, 0.0, 0.0)
print("keyframes: {clip.keyframe_count()}")
Blend two clips together at a given weight
Blends this clip with other at time, where factor is 0.0 (fully this clip) to 1.0 (fully other). Returns a table of blended bone transforms.
use plugin skeleton::{Skeleton, AnimClip}
let rig = Skeleton.new()
let b = rig.add_bone("spine", -1, 0.0, 0.0, 0.0)
let walk = AnimClip.new(1.0)
walk.add_keyframe(0.0, b, 0.0, 0.0, 0.0)
walk.add_keyframe(1.0, b, 0.0, 0.2, 0.0)
let run = AnimClip.new(1.0)
run.add_keyframe(0.0, b, 0.0, 0.0, 0.0)
run.add_keyframe(1.0, b, 0.0, 0.5, 0.0)
let blended = walk.blend(run, 0.5, 0.75)
for _, t in blended {
print("y={t["y"]}")
}