Skeleton & Skinning
Skins define how a mesh deforms based on a skeleton of joints. Skins are typically loaded from 3D files (glTF, FBX) — each joint is a node that can be manipulated.
Accessing a Skin
Section titled “Accessing a Skin”Skins are attached to nodes:
local character = scene:find_node("Character")local skin = character:get_skin()Querying Joints
Section titled “Querying Joints”skin:get_joint_count() -- number of jointsskin:get_joint_names() -- array of strings: { "Hips", "Spine", "LeftArm", ... }
-- Get a joint's node by index (0-based)local joint = skin:get_joint_node(0)
-- Get a joint's node by namelocal left_arm = skin:get_joint_node_by_name("LeftArm") -- Node or nilEach joint is a regular Node, so you can read and write its transform properties.
Manual Posing
Section titled “Manual Posing”Manipulate joint rotations for procedural animation:
local math3d = pl.math3d
-- Bend the left arm 45° around Zlocal arm = skin:get_joint_node_by_name("LeftArm")arm.rotation = math3d.quat_from_euler(0, 0, 45)
-- Nod the headlocal head = skin:get_joint_node_by_name("Head")head.rotation = math3d.quat_from_euler(15, 0, 0) -- pitch down 15°Joint transforms are local (relative to the parent joint). The mesh deformation updates automatically when joint nodes change.
Inverse Bind Matrices
Section titled “Inverse Bind Matrices”The inverse bind matrix for each joint transforms vertices from model space to the joint’s local space. These are read-only and set during import.
local ibm = skin:get_inverse_bind_matrix(0) -- mat4 (16 floats, column-major)Example: Procedural Wave
Section titled “Example: Procedural Wave”local skin = scene:find_node("Character"):get_skin()local math3d = pl.math3d
local shoulder = skin:get_joint_node_by_name("RightShoulder")local elbow = skin:get_joint_node_by_name("RightElbow")local wrist = skin:get_joint_node_by_name("RightWrist")
-- Raise armshoulder.rotation = math3d.quat_from_euler(0, 0, -150)elbow.rotation = math3d.quat_from_euler(0, 0, -30)
-- Animate wrist wave using timeline positionlocal t = pl.timeline.positionlocal wave_angle = math.sin(t * 6) * 20wrist.rotation = math3d.quat_from_euler(0, wave_angle, 0)