animation

This commit is contained in:
2025-12-13 01:09:15 -06:00
parent 863bc7fe9b
commit 86641e31a3
6 changed files with 1149 additions and 56 deletions

270
core.cm
View File

@@ -10,6 +10,8 @@ var gltf = use('mload/gltf')
var obj_loader = use('mload/obj')
var model_c = use('model')
var png = use('cell-image/png')
var anim_mod = use('animation')
var skin_mod = use('skin')
// Internal state
var _state = {
@@ -28,6 +30,7 @@ var _state = {
gpu: null,
pipeline_lit: null,
pipeline_unlit: null,
pipeline_skinned: null,
sampler_nearest: null,
sampler_linear: null,
depth_texture: null,
@@ -199,8 +202,9 @@ function load_model(path) {
root_nodes: [],
textures: [],
materials: g.materials || [],
animations: g.animations || [],
animations: [],
animation_count: g.animations ? g.animations.length : 0,
skins: [],
_gltf: g
}
@@ -280,6 +284,13 @@ function load_model(path) {
}
}
// Prepare animations
model.animations = anim_mod.prepare_animations(model)
model.animation_count = model.animations.length
// Prepare skins
model.skins = skin_mod.prepare_skins(model)
return model
}
@@ -299,6 +310,8 @@ function _process_gltf_primitive(g, buffer_blob, prim, textures) {
var pos_acc = g.accessors[attrs.POSITION]
var norm_acc = attrs.NORMAL != null ? g.accessors[attrs.NORMAL] : null
var uv_acc = attrs.TEXCOORD_0 != null ? g.accessors[attrs.TEXCOORD_0] : null
var joints_acc = attrs.JOINTS_0 != null ? g.accessors[attrs.JOINTS_0] : null
var weights_acc = attrs.WEIGHTS_0 != null ? g.accessors[attrs.WEIGHTS_0] : null
var idx_acc = prim.indices != null ? g.accessors[prim.indices] : null
var vertex_count = pos_acc.count
@@ -345,6 +358,36 @@ function _process_gltf_primitive(g, buffer_blob, prim, textures) {
)
}
// Extract joints (for skinned meshes)
var joints = null
if (joints_acc) {
var joints_view = g.views[joints_acc.view]
joints = model_c.extract_accessor(
buffer_blob,
joints_view.byte_offset || 0,
joints_view.byte_stride || 0,
joints_acc.byte_offset || 0,
joints_acc.count,
joints_acc.component_type,
joints_acc.type
)
}
// Extract weights (for skinned meshes)
var weights = null
if (weights_acc) {
var weights_view = g.views[weights_acc.view]
weights = model_c.extract_accessor(
buffer_blob,
weights_view.byte_offset || 0,
weights_view.byte_stride || 0,
weights_acc.byte_offset || 0,
weights_acc.count,
weights_acc.component_type,
weights_acc.type
)
}
// Extract indices
var indices = null
var index_count = 0
@@ -368,7 +411,9 @@ function _process_gltf_primitive(g, buffer_blob, prim, textures) {
positions: positions,
normals: normals,
uvs: uvs,
colors: null
colors: null,
joints: joints,
weights: weights
}
var packed = model_c.pack_vertices(mesh_data)
@@ -396,7 +441,9 @@ function _process_gltf_primitive(g, buffer_blob, prim, textures) {
index_type: index_type,
vertex_count: vertex_count,
material_index: prim.material,
texture: texture
texture: texture,
skinned: packed.skinned || false,
stride: packed.stride
}
}
@@ -854,6 +901,33 @@ function draw_model(model, transform, anim_instance) {
// If transform is provided, use it as an additional parent for the model's root nodes
var extra_transform = transform ? transform_get_world_matrix(transform) : null
// Build joint palettes for all skins (if any)
var skin_palettes = []
if (model.skins && model.skins.length > 0) {
for (var si = 0; si < model.skins.length; si++) {
var skin = model.skins[si]
// Collect world matrices for each joint
var world_matrices = []
for (var j = 0; j < skin.joints.length; j++) {
var node_idx = skin.joints[j]
var jnode = model.nodes[node_idx]
if (jnode) {
var jworld = transform_get_world_matrix(jnode)
// Apply extra transform if provided
if (extra_transform) {
jworld = model_c.mat4_mul(extra_transform, jworld)
}
world_matrices.push(jworld)
} else {
world_matrices.push(model_c.mat4_identity())
}
}
// Build palette using C function
var palette = model_c.build_joint_palette(world_matrices, skin.inv_bind, skin.joint_count)
skin_palettes.push(palette)
}
}
// Draw each node that has a mesh
for (var ni = 0; ni < model.nodes.length; ni++) {
var node = model.nodes[ni]
@@ -880,7 +954,13 @@ function draw_model(model, transform, anim_instance) {
// Build uniforms in cell script
var uniforms = _build_uniforms(world_matrix, view_matrix, proj_matrix, tint)
_draw_mesh(mesh, uniforms, tex, mat ? mat.kind : "lit")
// Get palette for skinned mesh (use first skin for now)
var palette = null
if (mesh.skinned && skin_palettes.length > 0) {
palette = skin_palettes[0]
}
_draw_mesh(mesh, uniforms, tex, mat ? mat.kind : "lit", palette)
_state.draw_calls++
_state.triangles += mesh.index_count / 3
}
@@ -896,7 +976,14 @@ function draw_model(model, transform, anim_instance) {
var mesh = model.meshes[i]
var tex = mesh.texture || (mat && mat.texture ? mat.texture : _state.white_texture)
var uniforms = _build_uniforms(world_matrix, view_matrix, proj_matrix, tint)
_draw_mesh(mesh, uniforms, tex, mat ? mat.kind : "lit")
// Get palette for skinned mesh
var palette = null
if (mesh.skinned && skin_palettes.length > 0) {
palette = skin_palettes[0]
}
_draw_mesh(mesh, uniforms, tex, mat ? mat.kind : "lit", palette)
_state.draw_calls++
_state.triangles += mesh.index_count / 3
}
@@ -953,64 +1040,90 @@ function end() {
// ============================================================================
function anim_instance(model) {
return {
model: model,
clip_index: 0,
time: 0,
speed: 1,
loop: true,
playing: false
return anim_mod.create_instance(model)
}
function anim_clip_count(model_or_instance) {
if (model_or_instance.animations) {
// It's an instance
return anim_mod.clip_count(model_or_instance)
}
// It's a model
return model_or_instance.animation_count || 0
}
function anim_clip_count(model) {
return model.animation_count || 0
function anim_clip_duration(model_or_instance, clip_index) {
if (model_or_instance.animations && model_or_instance.model) {
// It's an instance
return anim_mod.clip_duration(model_or_instance, clip_index)
}
// It's a model - create temp instance
if (!model_or_instance.animations || clip_index >= model_or_instance.animations.length) return 0
return model_or_instance.animations[clip_index].duration || 0
}
function anim_clip_duration(model, clip_index) {
if (!model.animations || clip_index >= model.animations.length) return 0
return model.animations[clip_index].duration || 0
function anim_clip_name(instance, clip_index) {
return anim_mod.clip_name(instance, clip_index)
}
function anim_find_clip(instance, name) {
return anim_mod.find_clip(instance, name)
}
function anim_play(anim, clip_index, loop) {
anim.clip_index = clip_index
anim.loop = loop != null ? loop : true
anim.playing = true
anim.time = 0
anim_mod.play(anim, clip_index, loop)
}
function anim_stop(anim) {
anim.playing = false
anim_mod.stop(anim)
}
function anim_set_time(anim, seconds) {
anim.time = seconds
anim_mod.set_time(anim, seconds)
}
function anim_set_speed(anim, speed) {
anim.speed = speed
anim_mod.set_speed(anim, speed)
}
function anim_update(anim, dt_val) {
if (!anim.playing) return
dt_val = dt_val != null ? dt_val : _state.dt
anim.time += dt_val * anim.speed
var duration = anim_clip_duration(anim.model, anim.clip_index)
if (duration > 0 && anim.time > duration) {
if (anim.loop) {
anim.time = anim.time % duration
} else {
anim.time = duration
anim.playing = false
}
}
anim_mod.update(anim, dt_val)
}
function anim_apply(anim) {
anim_mod.apply(anim)
}
function anim_pop_events(anim) {
return []
}
// ============================================================================
// 8b) Skinning
// ============================================================================
function skin_get(model, skin_index) {
if (!model.skins || skin_index >= model.skins.length) return null
return model.skins[skin_index]
}
function skin_build_palette(skin, model) {
return skin_mod.build_palette(skin, model, { transform_get_world_matrix: transform_get_world_matrix })
}
function skin_get_joint_world(skin, joint_index, model) {
return skin_mod.get_joint_world(skin, joint_index, model, { transform_get_world_matrix: transform_get_world_matrix })
}
function skin_find_joint(skin, name, model) {
return skin_mod.find_joint(skin, name, model)
}
function skin_attachment_transform(skin, joint_index, model, offset_mat) {
return skin_mod.attachment_transform(skin, joint_index, model, { transform_get_world_matrix: transform_get_world_matrix }, offset_mat)
}
// ============================================================================
// 9) Collision (stubs for v1)
// ============================================================================
@@ -1200,6 +1313,49 @@ function _init_gpu() {
compare: "less"
}
})
// Create skinned pipeline (for animated meshes with joints/weights)
var skinned_vert_code = io.slurp("shaders/retro3d_skinned.vert.msl")
if (skinned_vert_code) {
var skinned_vert_shader = new gpu_mod.shader(_state.gpu, {
code: skinned_vert_code,
stage: "vertex",
format: "msl",
entrypoint: "vertex_main",
num_uniform_buffers: 3
})
_state.pipeline_skinned = new gpu_mod.graphics_pipeline(_state.gpu, {
vertex: skinned_vert_shader,
fragment: frag_shader,
primitive: "triangle",
cull: "back",
face: "counter_clockwise",
fill: "fill",
vertex_buffer_descriptions: [{
slot: 0,
pitch: 80,
input_rate: "vertex"
}],
vertex_attributes: [
{ location: 0, buffer_slot: 0, format: "float3", offset: 0 },
{ location: 1, buffer_slot: 0, format: "float3", offset: 12 },
{ location: 2, buffer_slot: 0, format: "float2", offset: 24 },
{ location: 3, buffer_slot: 0, format: "float4", offset: 32 },
{ location: 4, buffer_slot: 0, format: "float4", offset: 48 },
{ location: 5, buffer_slot: 0, format: "float4", offset: 64 }
],
target: {
color_targets: [{ format: swapchain_format, blend: { enabled: false } }],
depth: "d32 float s8"
},
depth: {
test: true,
write: true,
compare: "less"
}
})
}
// Create samplers
var style = _styles[_state.style]
@@ -1381,14 +1537,15 @@ function _compute_projection_matrix() {
}
}
function _draw_mesh(mesh, uniforms, texture, kind) {
function _draw_mesh(mesh, uniforms, texture, kind, palette) {
// This will be called during render pass
_state._pending_draws = _state._pending_draws || []
_state._pending_draws.push({
mesh: mesh,
uniforms: uniforms,
texture: texture,
kind: kind
kind: kind,
palette: palette
})
}
@@ -1493,14 +1650,26 @@ function _end_frame() {
var draws = _state._pending_draws || []
for (var i = 0; i < draws.length; i++) {
var d = draws[i]
swap_pass.bind_pipeline(_state.pipeline_lit)
swap_pass.bind_vertex_buffers(0, [{ buffer: d.mesh.vertex_buffer, offset: 0 }])
swap_pass.bind_index_buffer({ buffer: d.mesh.index_buffer, offset: 0 }, d.mesh.index_type == "uint32" ? 32 : 16)
// Shaders use [[buffer(1)]] for uniforms (buffer(0) is vertex data)
cmd.push_vertex_uniform_data(1, d.uniforms)
cmd.push_fragment_uniform_data(1, d.uniforms)
// Choose pipeline based on whether mesh is skinned
if (d.mesh.skinned && d.palette && _state.pipeline_skinned) {
swap_pass.bind_pipeline(_state.pipeline_skinned)
swap_pass.bind_vertex_buffers(0, [{ buffer: d.mesh.vertex_buffer, offset: 0 }])
swap_pass.bind_index_buffer({ buffer: d.mesh.index_buffer, offset: 0 }, d.mesh.index_type == "uint32" ? 32 : 16)
// Shaders use [[buffer(1)]] for uniforms, [[buffer(2)]] for joint palette
cmd.push_vertex_uniform_data(1, d.uniforms)
cmd.push_vertex_uniform_data(2, d.palette)
cmd.push_fragment_uniform_data(1, d.uniforms)
} else {
swap_pass.bind_pipeline(_state.pipeline_lit)
swap_pass.bind_vertex_buffers(0, [{ buffer: d.mesh.vertex_buffer, offset: 0 }])
swap_pass.bind_index_buffer({ buffer: d.mesh.index_buffer, offset: 0 }, d.mesh.index_type == "uint32" ? 32 : 16)
// Shaders use [[buffer(1)]] for uniforms (buffer(0) is vertex data)
cmd.push_vertex_uniform_data(1, d.uniforms)
cmd.push_fragment_uniform_data(1, d.uniforms)
}
var sampler = _state.style_id == 1 ? _state.sampler_linear : _state.sampler_nearest
swap_pass.bind_fragment_samplers(0, [{ texture: d.texture, sampler: sampler }])
@@ -1577,12 +1746,21 @@ return {
anim_instance: anim_instance,
anim_clip_count: anim_clip_count,
anim_clip_duration: anim_clip_duration,
anim_clip_name: anim_clip_name,
anim_find_clip: anim_find_clip,
anim_play: anim_play,
anim_stop: anim_stop,
anim_set_time: anim_set_time,
anim_set_speed: anim_set_speed,
anim_update: anim_update,
anim_apply: anim_apply,
anim_pop_events: anim_pop_events,
skin_get: skin_get,
skin_build_palette: skin_build_palette,
skin_get_joint_world: skin_get_joint_world,
skin_find_joint: skin_find_joint,
skin_attachment_transform: skin_attachment_transform,
add_collider_sphere: add_collider_sphere,
add_collider_box: add_collider_box,