233 lines
5.9 KiB
Plaintext
233 lines
5.9 KiB
Plaintext
// Animation module for retro3d
|
|
// Handles animation sampling, playback, and node transform updates
|
|
var model_c = use('model')
|
|
|
|
// Prepare animation data from a loaded model
|
|
// Extracts keyframe blobs from accessors for efficient sampling
|
|
function prepare_animations(model) {
|
|
if (!model._gltf || !model._gltf.animations) return []
|
|
|
|
var g = model._gltf
|
|
var buffer_blob = g.buffers[0] ? g.buffers[0].blob : null
|
|
if (!buffer_blob) return []
|
|
|
|
var prepared = []
|
|
|
|
for (var ai = 0; ai < g.animations.length; ai++) {
|
|
var anim = g.animations[ai]
|
|
var channels = []
|
|
var duration = 0
|
|
|
|
for (var ci = 0; ci < anim.channels.length; ci++) {
|
|
var chan = anim.channels[ci]
|
|
var sampler = anim.samplers[chan.sampler]
|
|
if (!sampler) continue
|
|
|
|
var input_acc = g.accessors[sampler.input]
|
|
var output_acc = g.accessors[sampler.output]
|
|
if (!input_acc || !output_acc) continue
|
|
|
|
// Extract times blob
|
|
var input_view = g.views[input_acc.view]
|
|
var times_blob = model_c.extract_accessor(
|
|
buffer_blob,
|
|
input_view.byte_offset || 0,
|
|
input_view.byte_stride || 0,
|
|
input_acc.byte_offset || 0,
|
|
input_acc.count,
|
|
input_acc.component_type,
|
|
input_acc.type
|
|
)
|
|
|
|
// Extract values blob
|
|
var output_view = g.views[output_acc.view]
|
|
var values_blob = model_c.extract_accessor(
|
|
buffer_blob,
|
|
output_view.byte_offset || 0,
|
|
output_view.byte_stride || 0,
|
|
output_acc.byte_offset || 0,
|
|
output_acc.count,
|
|
output_acc.component_type,
|
|
output_acc.type
|
|
)
|
|
|
|
// Track max time for duration
|
|
if (input_acc.max && input_acc.max[0] > duration) {
|
|
duration = input_acc.max[0]
|
|
}
|
|
|
|
channels.push({
|
|
node: chan.target.node,
|
|
path: chan.target.path,
|
|
interpolation: sampler.interpolation,
|
|
times: times_blob,
|
|
values: values_blob,
|
|
count: input_acc.count
|
|
})
|
|
}
|
|
|
|
prepared.push({
|
|
name: anim.name,
|
|
duration: duration,
|
|
channels: channels
|
|
})
|
|
}
|
|
|
|
return prepared
|
|
}
|
|
|
|
// Create an animation instance for playback
|
|
function create_instance(model, prepared_anims) {
|
|
return {
|
|
model: model,
|
|
animations: prepared_anims || prepare_animations(model),
|
|
clip_index: 0,
|
|
time: 0,
|
|
speed: 1,
|
|
loop: true,
|
|
playing: false
|
|
}
|
|
}
|
|
|
|
// Get clip count
|
|
function clip_count(instance) {
|
|
return instance.animations ? instance.animations.length : 0
|
|
}
|
|
|
|
// Get clip duration
|
|
function clip_duration(instance, clip_idx) {
|
|
if (!instance.animations || clip_idx >= instance.animations.length) return 0
|
|
return instance.animations[clip_idx].duration
|
|
}
|
|
|
|
// Get clip name
|
|
function clip_name(instance, clip_idx) {
|
|
if (!instance.animations || clip_idx >= instance.animations.length) return null
|
|
return instance.animations[clip_idx].name
|
|
}
|
|
|
|
// Find clip by name
|
|
function find_clip(instance, name) {
|
|
if (!instance.animations) return -1
|
|
for (var i = 0; i < instance.animations.length; i++) {
|
|
if (instance.animations[i].name == name) return i
|
|
}
|
|
return -1
|
|
}
|
|
|
|
// Play a clip
|
|
function play(instance, clip_idx, loop_val) {
|
|
instance.clip_index = clip_idx
|
|
instance.loop = loop_val != null ? loop_val : true
|
|
instance.playing = true
|
|
instance.time = 0
|
|
}
|
|
|
|
// Stop playback
|
|
function stop(instance) {
|
|
instance.playing = false
|
|
}
|
|
|
|
// Set time
|
|
function set_time(instance, t) {
|
|
instance.time = t
|
|
}
|
|
|
|
// Set speed
|
|
function set_speed(instance, s) {
|
|
instance.speed = s
|
|
}
|
|
|
|
// Update animation time
|
|
function update(instance, dt) {
|
|
if (!instance.playing) return
|
|
|
|
instance.time += dt * instance.speed
|
|
|
|
var duration = clip_duration(instance, instance.clip_index)
|
|
if (duration > 0 && instance.time > duration) {
|
|
if (instance.loop) {
|
|
instance.time = instance.time % duration
|
|
} else {
|
|
instance.time = duration
|
|
instance.playing = false
|
|
}
|
|
}
|
|
}
|
|
|
|
// Apply animation to model nodes at current time
|
|
// This samples all channels and updates node TRS values
|
|
function apply(instance) {
|
|
if (!instance.animations || instance.clip_index >= instance.animations.length) return
|
|
|
|
var anim = instance.animations[instance.clip_index]
|
|
var model = instance.model
|
|
var t = instance.time
|
|
|
|
for (var ci = 0; ci < anim.channels.length; ci++) {
|
|
var chan = anim.channels[ci]
|
|
var node_idx = chan.node
|
|
if (node_idx == null || node_idx >= model.nodes.length) continue
|
|
|
|
var node = model.nodes[node_idx]
|
|
|
|
if (chan.path == "translation") {
|
|
var v = model_c.sample_vec3(chan.times, chan.values, chan.count, t, chan.interpolation)
|
|
node.x = v[0]
|
|
node.y = v[1]
|
|
node.z = v[2]
|
|
node.has_local_mat = false
|
|
node.dirty_local = true
|
|
node.dirty_world = true
|
|
} else if (chan.path == "rotation") {
|
|
var q = model_c.sample_quat(chan.times, chan.values, chan.count, t, chan.interpolation)
|
|
node.qx = q[0]
|
|
node.qy = q[1]
|
|
node.qz = q[2]
|
|
node.qw = q[3]
|
|
node.has_local_mat = false
|
|
node.dirty_local = true
|
|
node.dirty_world = true
|
|
} else if (chan.path == "scale") {
|
|
var s = model_c.sample_vec3(chan.times, chan.values, chan.count, t, chan.interpolation)
|
|
node.sx = s[0]
|
|
node.sy = s[1]
|
|
node.sz = s[2]
|
|
node.has_local_mat = false
|
|
node.dirty_local = true
|
|
node.dirty_world = true
|
|
}
|
|
}
|
|
|
|
// Mark all children dirty (propagate down hierarchy)
|
|
for (var ni = 0; ni < model.nodes.length; ni++) {
|
|
var n = model.nodes[ni]
|
|
if (n.dirty_world) {
|
|
_mark_children_dirty(n)
|
|
}
|
|
}
|
|
}
|
|
|
|
function _mark_children_dirty(node) {
|
|
for (var i = 0; i < node.children.length; i++) {
|
|
var child = node.children[i]
|
|
child.dirty_world = true
|
|
_mark_children_dirty(child)
|
|
}
|
|
}
|
|
|
|
return {
|
|
prepare_animations: prepare_animations,
|
|
create_instance: create_instance,
|
|
clip_count: clip_count,
|
|
clip_duration: clip_duration,
|
|
clip_name: clip_name,
|
|
find_clip: find_clip,
|
|
play: play,
|
|
stop: stop,
|
|
set_time: set_time,
|
|
set_speed: set_speed,
|
|
update: update,
|
|
apply: apply
|
|
}
|