animation
This commit is contained in:
270
core.cm
270
core.cm
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user