summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--world/src/animation.rs102
-rw-r--r--world/src/main.rs144
2 files changed, 174 insertions, 72 deletions
diff --git a/world/src/animation.rs b/world/src/animation.rs
new file mode 100644
index 0000000..00c716f
--- /dev/null
+++ b/world/src/animation.rs
@@ -0,0 +1,102 @@
+/*
+ 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 <https://www.gnu.org/licenses/>.
+*/
+use anyhow::Result;
+use gltf::{
+ Animation,
+ animation::{Property, util::ReadOutputs},
+ buffer::Data,
+};
+use log::{debug, info};
+use std::collections::BTreeMap;
+use weareshared::{
+ packets::Resource,
+ resources::{AnimationChannel, AnimationPart},
+ store::ResourceStore,
+};
+
+pub fn import_animation<'a>(
+ a: Animation<'a>,
+ store: &ResourceStore,
+ joint_index_to_arm_index: &BTreeMap<usize, usize>,
+ node_to_meshes: &BTreeMap<usize, Vec<usize>>,
+ buffers: &[Data],
+) -> Result<Resource<AnimationPart>> {
+ 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)?;
+
+ 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),
+ })
+}
diff --git a/world/src/main.rs b/world/src/main.rs
index 16ed941..cfe2361 100644
--- a/world/src/main.rs
+++ b/world/src/main.rs
@@ -16,22 +16,18 @@
*/
#![feature(iter_array_chunks)]
#![allow(clippy::too_many_arguments, clippy::type_complexity)]
+pub mod animation;
pub mod mesh;
pub mod physics;
pub mod vrm;
+use animation::import_animation;
use anyhow::{Context, Result, anyhow};
use clap::Parser;
-use gltf::{
- Gltf, Node,
- animation::{Property, util::ReadOutputs},
- image::Source,
- import_buffers,
- scene::Transform,
-};
+use gltf::{Gltf, Node, image::Source, import_buffers, scene::Transform};
use humansize::BINARY;
use image::{ImageReader, codecs::webp::WebPEncoder};
-use log::{debug, info};
+use log::{debug, info, warn};
use mesh::import_mesh;
use physics::import_physics;
use rand::random;
@@ -41,7 +37,7 @@ use serde_json::Value;
use std::{
borrow::Cow,
collections::{BTreeMap, HashMap},
- fs::File,
+ fs::{File, read_to_string},
io::{Cursor, Read, Write},
marker::PhantomData,
net::{SocketAddr, TcpStream},
@@ -56,8 +52,7 @@ use weareshared::{
helper::ReadWrite,
packets::{Data, Object, Packet, Resource},
resources::{
- AnimationChannel, AnimationPart, ArmaturePart, AvatarInfoPart, EnvironmentPart, Image,
- LightPart, ParticlesPart, Prefab,
+ ArmaturePart, AvatarInfoPart, EnvironmentPart, Image, LightPart, ParticlesPart, Prefab,
},
store::ResourceStore,
vec3a,
@@ -100,6 +95,11 @@ pub struct Args {
#[arg(short = 'S', long)]
with_default_sun: bool,
+ #[arg(long)]
+ animation: Option<PathBuf>,
+ #[arg(long)]
+ animation_bone_map: Option<PathBuf>,
+
/// Spins the object
#[arg(long)]
debug_spin: bool,
@@ -117,7 +117,7 @@ pub struct Args {
fn main() -> Result<()> {
env_logger::init_from_env("LOG");
- let args = Args::parse();
+ let mut args = Args::parse();
let store = if args.use_cache {
ResourceStore::new_env()?
@@ -166,6 +166,7 @@ fn main() -> Result<()> {
let mut skin_index_to_arm_index = BTreeMap::new();
let mut joint_index_to_arm_index = BTreeMap::new();
+ let joint_name_to_arm_index: BTreeMap<String, usize>;
let armature = {
let mut name = Vec::new();
let mut parent_pre_map = Vec::new();
@@ -227,6 +228,13 @@ fn main() -> Result<()> {
}
}
+ joint_name_to_arm_index = name
+ .iter()
+ .cloned()
+ .enumerate()
+ .map(|(a, b)| (b, a))
+ .collect();
+
ArmaturePart {
name: Some(name),
parent: Some(parent),
@@ -373,75 +381,67 @@ fn main() -> Result<()> {
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)?;
+ if let Some(apath) = args.animation.take() {
+ let path_base = apath.parent().unwrap();
+ let mut gltf = Gltf::from_reader_without_validation(File::open(&apath)?)
+ .context("gltf parsing")?;
+ let blob = gltf.blob.take();
+ let buffers =
+ import_buffers(&gltf, Some(path_base), blob).context("importing buffers")?;
- 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,
+ let anim_name_map = if let Some(ref map_path) = args.animation_bone_map {
+ let mut map = BTreeMap::new();
+ for l in read_to_string(map_path)?.lines() {
+ if !l.trim().is_empty() {
+ let (a, b) = l.split_once("=").unwrap();
+ map.insert(a.to_string(), b.to_string());
}
- 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);
+ Some(map)
+ } else {
+ None
+ };
+
+ let mut anim_joint_index_to_arm_index = BTreeMap::new();
+ for n in gltf.nodes() {
+ if let Some(name) = n.name() {
+ let Some(vrm_name) = (if let Some(map) = &anim_name_map {
+ map.get(name).cloned()
+ } else {
+ Some(name.to_string())
+ }) else {
+ continue;
+ };
+ let anim_node_index = n.index();
+ if let Some(scene_arm_index) = joint_name_to_arm_index.get(&vrm_name) {
+ anim_joint_index_to_arm_index.insert(anim_node_index, *scene_arm_index);
}
}
}
- if !args.no_animations {
- info!("adding animation with {} channels", channels.len());
- prefab.animation.push(store.set(&AnimationPart {
- name: a.name().map(|n| n.to_string()),
- channel: channels,
- duration: Some(max_time),
- })?);
+
+ for a in gltf.animations() {
+ prefab.animation.push(import_animation(
+ a,
+ &store,
+ &anim_joint_index_to_arm_index,
+ &BTreeMap::new(),
+ &buffers,
+ )?);
}
}
+ for a in gltf.animations() {
+ if !args.no_animations {
+ prefab.animation.push(import_animation(
+ a,
+ &store,
+ &joint_index_to_arm_index,
+ &node_to_meshes,
+ &buffers,
+ )?);
+ };
+ }
+
if vrm.camera_mount.is_some()
|| vrm.camera_mount_offset.is_some()
|| !vrm.bone_node_names.is_empty()