/*
wearechat - generic multiplayer game with voip
Copyright (C) 2025 metamuffin
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, version 3 of the License only.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
use anyhow::Result;
use glam::{Quat, vec3a};
use gltf::{
Animation,
animation::{Property, util::ReadOutputs},
buffer::Data,
};
use log::{debug, info};
use std::collections::BTreeMap;
use weareshared::{
Affine3A,
packets::Resource,
resources::{AnimationChannel, AnimationPart},
store::ResourceStore,
};
pub fn import_animation(
a: Animation<'_>,
store: &ResourceStore,
transform: Affine3A,
joint_index_to_ibm: &BTreeMap,
joint_index_to_arm_index: &BTreeMap,
node_to_meshes: &BTreeMap>,
buffers: &[Data],
) -> Result> {
let mut max_time = 0f32;
let mut channels = Vec::new();
for c in a.channels() {
let node = c.target().node().index();
let reader = c.reader(|i| Some(&buffers[i.index()].0));
let inputs: Vec = reader.read_inputs().unwrap().collect::>();
let outputs: Vec =
if joint_index_to_ibm.contains_key(&node) || transform != Affine3A::IDENTITY {
let t = transform
* joint_index_to_ibm
.get(&node)
.copied()
.unwrap_or_default()
.inverse();
let (_, rot, _) = t.to_scale_rotation_translation();
match reader.read_outputs().unwrap() {
ReadOutputs::Translations(iter) => iter
.flat_map(|[x, y, z]| (t.matrix3 * vec3a(x, y, z)).to_array())
.collect(),
ReadOutputs::Rotations(iter) => iter
.into_f32()
.map(Quat::from_array)
.map(|q| rot.mul_quat(q))
.flat_map(|q| q.to_array())
.collect(),
ReadOutputs::Scales(iter) => iter
.flat_map(|[x, y, z]| (t.matrix3 * vec3a(x, y, z)).to_array())
.collect(),
ReadOutputs::MorphTargetWeights(iter) => iter.into_f32().collect(),
}
} else {
match reader.read_outputs().unwrap() {
ReadOutputs::Translations(iter) => iter.flatten().collect(),
ReadOutputs::Rotations(iter) => iter.into_f32().flatten().collect(),
ReadOutputs::Scales(iter) => iter.flatten().collect(),
ReadOutputs::MorphTargetWeights(iter) => iter.into_f32().collect(),
}
};
for x in &inputs {
max_time = max_time.max(*x)
}
let time = store.set(&inputs)?;
let value = store.set(&outputs)?;
if let Some(&m) = joint_index_to_arm_index.get(&node) {
let a = 0; // TODO
let mut ch = AnimationChannel::default();
match c.target().property() {
Property::Translation => ch.t_joint_translation = Some((a, m as u32)),
Property::Rotation => ch.t_joint_rotation = Some((a, m as u32)),
Property::Scale => ch.t_joint_scale = Some((a, m as u32)),
Property::MorphTargetWeights => continue,
}
ch.time = Some(time.clone());
ch.value = Some(value.clone());
debug!(
"animation channel {:?} of joint {m} of armature {a} with {} time and {} component values",
c.target().property(),
inputs.len(),
outputs.len()
);
channels.push(ch);
}
if let Some(meshes) = node_to_meshes.get(&node) {
for &m in meshes {
let mut ch = AnimationChannel::default();
match c.target().property() {
Property::Translation => ch.t_mesh_translation = Some(m as u32),
Property::Rotation => ch.t_mesh_rotation = Some(m as u32),
Property::Scale => ch.t_mesh_scale = Some(m as u32),
Property::MorphTargetWeights => continue,
}
ch.time = Some(time.clone());
ch.value = Some(value.clone());
debug!(
"animation channel {:?} of mesh {m} with {} time and {} component values",
c.target().property(),
inputs.len(),
outputs.len()
);
channels.push(ch);
}
}
}
info!("adding animation with {} channels", channels.len());
store.set(&AnimationPart {
name: a.name().map(|n| n.to_string()),
channel: channels,
duration: Some(max_time),
})
}