Files
retro3d/skin.cm
2026-01-18 11:23:32 -06:00

133 lines
3.6 KiB
Plaintext

// Skinning module for retro3d
// Handles skeleton/joint management and joint palette building for GPU skinning
var model_c = use('model')
// Prepare skin data from a loaded model
// Extracts inverse bind matrices and joint indices
function prepare_skins(model) {
if (!model._gltf || !model._gltf.skins) return []
var g = model._gltf
var buffer_blob = g.buffers[0] ? g.buffers[0].blob : null
if (!buffer_blob) return []
var prepared = []
for (var si = 0; si < length(g.skins); si++) {
var skin = g.skins[si]
// Extract inverse bind matrices
var inv_bind_blob = null
if (skin.inverse_bind_matrices != null) {
var acc = g.accessors[skin.inverse_bind_matrices]
if (acc) {
var view = g.views[acc.view]
inv_bind_blob = model_c.extract_accessor(
buffer_blob,
view.byte_offset || 0,
view.byte_stride || 0,
acc.byte_offset || 0,
acc.count,
acc.component_type,
acc.type
)
}
}
prepared.push({
name: skin.name,
joints: skin.joints,
joint_count: length(skin.joints),
inv_bind: inv_bind_blob,
skeleton: skin.skeleton,
palette: null // Will be built each frame
})
}
return prepared
}
// Build joint palette for a skin
// This computes world * inv_bind for each joint
function build_palette(skin, model, retro3d) {
if (!skin || !skin.joints || length(skin.joints) == 0) return null
// Collect world matrices for each joint
var world_matrices = []
for (var j = 0; j < length(skin.joints); j++) {
var node_idx = skin.joints[j]
var node = model.nodes[node_idx]
if (node) {
var world_mat = retro3d.transform_get_world_matrix(node)
world_matrices.push(world_mat)
} else {
world_matrices.push(model_c.mat4_identity())
}
}
// Build palette using C function
return model_c.build_joint_palette(world_matrices, skin.inv_bind, skin.joint_count)
}
// Get a joint's world matrix (for attachments)
function get_joint_world(skin, joint_index, model, retro3d) {
if (!skin || joint_index >= length(skin.joints)) {
return model_c.mat4_identity()
}
var node_idx = skin.joints[joint_index]
var node = model.nodes[node_idx]
if (!node) return model_c.mat4_identity()
return retro3d.transform_get_world_matrix(node)
}
// Find joint index by name
function find_joint(skin, name, model) {
if (!skin || !skin.joints) return -1
for (var j = 0; j < length(skin.joints); j++) {
var node_idx = skin.joints[j]
var node = model.nodes[node_idx]
if (node && node.name == name) return j
}
return -1
}
// Find joint index by node index
function joint_from_node(skin, node_idx) {
if (!skin || !skin.joints) return -1
for (var j = 0; j < length(skin.joints); j++) {
if (skin.joints[j] == node_idx) return j
}
return -1
}
// Get node index from joint index
function node_from_joint(skin, joint_idx) {
if (!skin || !skin.joints || joint_idx >= length(skin.joints)) return -1
return skin.joints[joint_idx]
}
// Compute attachment transform
// Returns world matrix for an object attached to a joint with an offset
function attachment_transform(skin, joint_index, model, retro3d, offset_mat) {
var joint_world = get_joint_world(skin, joint_index, model, retro3d)
if (offset_mat) {
return model_c.mat4_mul(joint_world, offset_mat)
}
return joint_world
}
return {
prepare_skins: prepare_skins,
build_palette: build_palette,
get_joint_world: get_joint_world,
find_joint: find_joint,
joint_from_node: joint_from_node,
node_from_joint: node_from_joint,
attachment_transform: attachment_transform
}