summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2025-02-19 22:21:44 +0100
committermetamuffin <metamuffin@disroot.org>2025-02-19 22:21:44 +0100
commitee198b516bad5db9312dde3749bf864d6c7079b1 (patch)
treee1fb544e5c78a3cd7c6fff58642522be5ccef7e0
parent6edc755bc040bc67ad3cd88fa694e9d29daf4a3b (diff)
downloadweareserver-ee198b516bad5db9312dde3749bf864d6c7079b1.tar
weareserver-ee198b516bad5db9312dde3749bf864d6c7079b1.tar.bz2
weareserver-ee198b516bad5db9312dde3749bf864d6c7079b1.tar.zst
animations
-rw-r--r--doc/resources.md30
-rw-r--r--shared/src/resources.rs6
-rw-r--r--world/src/main.rs149
-rw-r--r--world/src/mesh.rs19
-rw-r--r--world/src/physics.rs13
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)
}