diff options
author | metamuffin <metamuffin@disroot.org> | 2025-02-19 22:21:44 +0100 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2025-02-19 22:21:44 +0100 |
commit | ee198b516bad5db9312dde3749bf864d6c7079b1 (patch) | |
tree | e1fb544e5c78a3cd7c6fff58642522be5ccef7e0 | |
parent | 6edc755bc040bc67ad3cd88fa694e9d29daf4a3b (diff) | |
download | weareserver-ee198b516bad5db9312dde3749bf864d6c7079b1.tar weareserver-ee198b516bad5db9312dde3749bf864d6c7079b1.tar.bz2 weareserver-ee198b516bad5db9312dde3749bf864d6c7079b1.tar.zst |
animations
-rw-r--r-- | doc/resources.md | 30 | ||||
-rw-r--r-- | shared/src/resources.rs | 6 | ||||
-rw-r--r-- | world/src/main.rs | 149 | ||||
-rw-r--r-- | world/src/mesh.rs | 19 | ||||
-rw-r--r-- | world/src/physics.rs | 13 |
5 files changed, 141 insertions, 76 deletions
diff --git a/doc/resources.md b/doc/resources.md index a7683e3..761a6fa 100644 --- a/doc/resources.md +++ b/doc/resources.md @@ -180,29 +180,25 @@ inherited. Attribute values are zipped similar to vertex attributes. | Key | Value Type | | | ---------- | ------------------ | --------- | +| `name` | `String` | | | `channel` | `AnimationChannel` | Multi key | | `duration` | `f32` | | ## AnimationChannel -| Key | Value Type | | -| --------------------- | ------------- | - | -| `t_mesh_translation` | `u32` | | -| `t_mesh_rotation` | `u32` | | -| `t_mesh_scale` | `u32` | | -| `t_mesh_morph_weight` | `u32`, `u32` | | -| `t_joint_translation` | `u32`, `u32` | | -| `t_joint_rotation` | `u32`, `u32` | | -| `t_joint_scale` | `u32`, `u32` | | -| `t_light_translation` | `u32` | | -| `time` | `Res<[f32]>` | | -| `value` | `Res<[Vec3]>` | | +| Key | Value Type | Indexes | `value` components | +| --------------------- | ------------ | ----------- | ------------------ | +| `t_mesh_translation` | `u32` | mesh | 3 | +| `t_mesh_rotation` | `u32` | mesh | 4 | +| `t_mesh_scale` | `u32` | mesh | 3 | +| `t_mesh_morph_weight` | `u32`, `u32` | mesh, morph | 1 | +| `t_joint_translation` | `u32`, `u32` | arma, joint | 3 | +| `t_joint_rotation` | `u32`, `u32` | arma, joint | 4 | +| `t_joint_scale` | `u32`, `u32` | arma, joint | 3 | +| `t_light_translation` | `u32` | mesh | 3 | +| `time` | `Res<[f32]>` | | | +| `value` | `Res<[f32]>` | | | -- **Joint targets**: Index into armature array of the prefab and index into - joint array of that armature. -- **Mesh targets**: Index into mesh array of the prefab. -- **Mesh morph targets**: Index into mesh array of the prefab and index into - morph targets array of that mesh. - **Time & Values**: Time and value arrays contain corresponding elements. Every point is a keyframe and lineraly interpolated by default. diff --git a/shared/src/resources.rs b/shared/src/resources.rs index 5e2e268..bab9a43 100644 --- a/shared/src/resources.rs +++ b/shared/src/resources.rs @@ -92,7 +92,9 @@ resource_dicts!( } pub struct AnimationPart { - channel[multi]: Resource<AnimationChannel>, + name: String, + channel[multi]: AnimationChannel, + duration: f32, } pub struct AnimationChannel { @@ -105,7 +107,7 @@ resource_dicts!( t_joint_scale: (u32, u32), t_light_translation: u32, time: Resource<Vec<f32>>, - value: Resource<Vec<Vec3A>>, + value: Resource<Vec<f32>>, } pub struct AvatarInfoPart { diff --git a/world/src/main.rs b/world/src/main.rs index 1931fdb..53489f3 100644 --- a/world/src/main.rs +++ b/world/src/main.rs @@ -22,7 +22,13 @@ pub mod vrm; use anyhow::{Context, Result, anyhow}; use clap::Parser; -use gltf::{Gltf, Node, image::Source, import_buffers, scene::Transform}; +use gltf::{ + Gltf, Node, + animation::{Property, util::ReadOutputs}, + image::Source, + import_buffers, + scene::Transform, +}; use humansize::BINARY; use image::{ImageReader, codecs::webp::WebPEncoder}; use log::{debug, info}; @@ -49,7 +55,10 @@ use weareshared::{ Affine3A, Vec3A, helper::ReadWrite, packets::{Data, Object, Packet, Resource}, - resources::{ArmaturePart, EnvironmentPart, Image, LightPart, ParticlesPart, Prefab}, + resources::{ + AnimationChannel, AnimationPart, ArmaturePart, EnvironmentPart, Image, LightPart, + ParticlesPart, Prefab, + }, store::ResourceStore, vec3a, }; @@ -223,10 +232,12 @@ fn main() -> Result<()> { .collect::<Vec<_>>() }; - let mut prefab = nodes + let prefab = Mutex::new(Prefab::default()); + let node_to_meshes = Mutex::new(BTreeMap::<usize, Vec<usize>>::new()); + + nodes .par_iter() .map(|(trans, node)| { - let mut prefab = Prefab::default(); // if node.name().unwrap_or_default() == "particles" { // eprintln!("{:?}", node.transform()); // eprintln!("{:?}", node.extensions()); @@ -241,20 +252,30 @@ fn main() -> Result<()> { if !name.ends_with("-collider") { if let Some(mesh) = node.mesh() { - import_mesh( + let meshes = import_mesh( mesh, *trans, &buffers, &store, path_base, node, - &mut prefab, &args, &texture_cache, &skin_index_to_arm_index, &vrm, &head_bones, )?; + for (node, trans, mesh) in meshes { + let mut k = prefab.lock().unwrap(); + let i = k.mesh.len(); + k.mesh.push((trans, mesh)); + node_to_meshes + .lock() + .unwrap() + .entry(node) + .or_default() + .push(i) + } } } if extras.get("particles") == Some(&Value::Bool(true)) { @@ -274,17 +295,21 @@ fn main() -> Result<()> { serde_json::from_value(extras).context("particles attributes")?; info!("adding particles part"); - prefab.particles.push(( - transform_to_affine(node.transform()), - store.set(&ParticlesPart { - sprite: None, - density: attr.density, - lifetime: attr.lifetime, - lifetime_spread: attr.lifetime_spread, - velocity: attr.velocity, - velocity_spread: attr.velocity_spread, - })?, - )); + + let part = store.set(&ParticlesPart { + sprite: None, + density: attr.density, + lifetime: attr.lifetime, + lifetime_spread: attr.lifetime_spread, + velocity: attr.velocity, + velocity_spread: attr.velocity_spread, + })?; + + prefab + .lock() + .unwrap() + .particles + .push((transform_to_affine(node.transform()), part)); } if let Some(light) = node.light() { @@ -299,33 +324,81 @@ fn main() -> Result<()> { debug!("emission is {e}"); } let (position, _, _) = node.transform().decomposed(); - prefab.light.push(( - Vec3A::from_array(position), - store.set(&LightPart { - emission, - name, - radius: None, - })?, - )); + let part = store.set(&LightPart { + emission, + name, + radius: None, + })?; + prefab + .lock() + .unwrap() + .light + .push((Vec3A::from_array(position), part)); } - import_physics(&gltf, *trans, node, &mut prefab, &store, &buffers)?; - Ok::<_, anyhow::Error>(prefab) + import_physics(&gltf, *trans, node, &store, &buffers)?; + + Ok::<_, anyhow::Error>(()) }) .reduce( - || Ok(Prefab::default()), - |a, b| { - let mut a = a?; - let b = b?; - a.collision.extend(b.collision); - a.mesh.extend(b.mesh); - a.light.extend(b.light); - a.particles.extend(b.particles); - a.environment = a.environment.or(b.environment); - a.name = a.name.or(b.name); - Ok(a) + || Ok(()), + |a, b| match (a, b) { + (Ok(()), Ok(())) => Ok(()), + (Ok(()), a) => a, + (a, _) => a, }, )?; + let mut prefab = prefab.into_inner().unwrap(); + let node_to_meshes = node_to_meshes.into_inner().unwrap(); + + for a in gltf.animations() { + 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<f32> = reader.read_inputs().unwrap().collect::<Vec<f32>>(); + let outputs: Vec<f32> = 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)?; + let meshes = node_to_meshes + .get(&node) + .ok_or(anyhow!("animation ref to meshless 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 => todo!(), + } + ch.time = Some(time.clone()); + ch.value = Some(value.clone()); + debug!( + "animation channel {:?} of mesh {m} with {} times and {} component values", + c.target().property(), + inputs.len(), + outputs.len() + ); + channels.push(ch); + } + } + info!("adding animation with {} channels", channels.len()); + prefab.animations.push(store.set(&AnimationPart { + name: a.name().map(|n| n.to_string()), + channel: channels, + duration: Some(max_time), + })?); + } + prefab.armature = if armature.parent.as_ref().is_some_and(|a| !a.is_empty()) { vec![store.set(&armature)?] } else { diff --git a/world/src/mesh.rs b/world/src/mesh.rs index c4a6372..ffc0f2f 100644 --- a/world/src/mesh.rs +++ b/world/src/mesh.rs @@ -20,10 +20,8 @@ use gltf::{Mesh, Node, buffer::Data}; use log::{debug, info, warn}; use std::{collections::BTreeMap, path::Path}; use weareshared::{ - Affine3A, Vec3A, - resources::{MeshPart, Prefab}, - store::ResourceStore, - vec2, vec3a, vec4, + Affine3A, Vec3A, packets::Resource, resources::MeshPart, store::ResourceStore, vec2, vec3a, + vec4, }; pub fn import_mesh( @@ -33,13 +31,13 @@ pub fn import_mesh( store: &ResourceStore, path_base: &Path, node: &Node, - prefab: &mut Prefab, args: &Args, texture_cache: &TextureCache, joint_index_map: &BTreeMap<(usize, u16), u32>, vrm: &VrmInfo, head_bones: &[u32], -) -> Result<()> { +) -> Result<Vec<(usize, Affine3A, Resource<MeshPart>)>> { + let mut meshes = Vec::new(); for p in mesh.primitives() { let name = mesh.name().or(node.name()).map(|e| e.to_owned()); if let Some(name) = &name { @@ -375,7 +373,7 @@ pub fn import_mesh( let armature = node.skin().map(|_| 0); - let mesh = store.set(&MeshPart { + let mesh = MeshPart { name, index, armature, @@ -417,11 +415,10 @@ pub fn import_mesh( va_emission: None, va_metallic: None, va_roughness: None, - })?; - - prefab.mesh.push((trans, mesh)) + }; + meshes.push((node.index(), trans, store.set(&mesh)?)) } - Ok(()) + Ok(meshes) } fn bool_to_opt(b: bool, log: &str) -> Option<()> { diff --git a/world/src/physics.rs b/world/src/physics.rs index 23b3303..202c4e0 100644 --- a/world/src/physics.rs +++ b/world/src/physics.rs @@ -18,20 +18,17 @@ use anyhow::{Result, anyhow}; use gltf::{Gltf, Node, buffer::Data, json::Value}; use log::{debug, info}; use weareshared::{ - Affine3A, - resources::{CollisionPart, Prefab}, - store::ResourceStore, - vec3a, + Affine3A, packets::Resource, resources::CollisionPart, store::ResourceStore, vec3a, }; pub fn import_physics( gltf: &Gltf, trans: Affine3A, node: &Node, - prefab: &mut Prefab, store: &ResourceStore, buffers: &[Data], -) -> Result<()> { +) -> Result<Vec<(Affine3A, Resource<CollisionPart>)>> { + let mut collision = Vec::new(); if let Some(physics) = node .extensions() .and_then(|e| e.get("KHR_physics_rigid_bodies")) @@ -80,11 +77,11 @@ pub fn import_physics( } info!("added collision {:?}", node.name().unwrap_or_default()); - prefab.collision.push((trans, store.set(&collpart)?)); + collision.push((trans, store.set(&collpart)?)); } } } } } - Ok(()) + Ok(collision) } |