summaryrefslogtreecommitdiff
path: root/world/src
diff options
context:
space:
mode:
Diffstat (limited to 'world/src')
-rw-r--r--world/src/animation.rs133
-rw-r--r--world/src/main.rs304
-rw-r--r--world/src/mesh.rs431
-rw-r--r--world/src/physics.rs89
-rw-r--r--world/src/prefab.rs426
-rw-r--r--world/src/vrm.rs135
6 files changed, 0 insertions, 1518 deletions
diff --git a/world/src/animation.rs b/world/src/animation.rs
deleted file mode 100644
index ca049e5..0000000
--- a/world/src/animation.rs
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- 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 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<usize, Affine3A>,
- 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> =
- 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| q * rot)
- .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(),
- _ => continue,
- }
- } 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),
- })
-}
diff --git a/world/src/main.rs b/world/src/main.rs
deleted file mode 100644
index 554a2bd..0000000
--- a/world/src/main.rs
+++ /dev/null
@@ -1,304 +0,0 @@
-/*
- 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/>.
-*/
-#![feature(iter_array_chunks)]
-#![allow(clippy::too_many_arguments, clippy::type_complexity)]
-pub mod animation;
-pub mod mesh;
-pub mod physics;
-pub mod prefab;
-pub mod vrm;
-
-use anyhow::{Result, bail};
-use clap::Parser;
-use gltf::{image::Source, scene::Transform};
-use humansize::BINARY;
-use image::{ImageReader, codecs::webp::WebPEncoder};
-use log::{debug, info};
-use prefab::import_prefab;
-use rand::random;
-use std::{
- borrow::Cow,
- collections::HashMap,
- fs::File,
- io::{BufWriter, Cursor, Read, Write},
- marker::PhantomData,
- net::{SocketAddr, TcpStream},
- path::{Path, PathBuf},
- sync::{Arc, Mutex},
- thread::{self, sleep},
- time::Duration,
-};
-use weareshared::{
- Affine3A, Vec3A,
- helper::ReadWrite,
- packets::{Data, Object, Packet, Resource},
- resources::{Image, RespackEntry},
- respack::save_respack,
- store::ResourceStore,
- vec3a,
-};
-
-#[derive(Parser)]
-pub struct Args {
- #[arg(short, long)]
- address: Option<SocketAddr>,
-
- /// Output converted prefab as resource package
- #[arg(short = 'o', long)]
- pack: Option<PathBuf>,
-
- /// Path(s) to a glTF file, binary or json format
- scene: Vec<PathBuf>,
- /// Send all resources to the server then quit
- #[arg(short, long)]
- push: bool,
-
- /// Remove all other object from the world
- #[arg(short, long)]
- clear: bool,
- /// Add the object to the world
- #[arg(short, long)]
- add: bool,
- /// Transcode all textures to WebP
- #[arg(short, long)]
- webp: bool,
- /// Add skybox
- #[arg(long)]
- skybox: Option<PathBuf>,
- /// Override prefab name
- #[arg(short, long)]
- name: Option<String>,
- #[arg(short, long)]
- scale: Option<f32>,
- #[arg(short, long)]
- dry_run: bool,
- #[arg(short, long)]
- line_up: bool,
-
- #[arg(long)]
- use_cache: bool,
-
- #[arg(short = 'S', long)]
- with_default_sun: bool,
-
- #[arg(long)]
- animation: Option<PathBuf>,
- #[arg(long)]
- animation_bone_map: Option<PathBuf>,
- #[arg(long)]
- animation_rotation_y: Option<f32>,
- #[arg(long)]
- animation_scale: Option<f32>,
- #[arg(long)]
- animation_apply_ibm: bool,
-
- /// Spins the object
- #[arg(long)]
- debug_spin: bool,
- /// Adds a light
- #[arg(long)]
- debug_light: bool,
- #[arg(long)]
- no_particles: bool,
- #[arg(long)]
- no_animations: bool,
-}
-
-fn main() -> Result<()> {
- env_logger::init_from_env("LOG");
- let args = Args::parse();
-
- let store = if args.use_cache && !args.pack.is_some() {
- ResourceStore::new_env()?
- } else {
- ResourceStore::new_memory()
- };
-
- let mut prefabs = Vec::new();
- let texture_cache = Arc::new(Mutex::new(HashMap::new()));
-
- for scenepath in &args.scene {
- prefabs.push(import_prefab(&store, &texture_cache, scenepath, &args)?);
- }
-
- let mut size = 0;
- store.iter(|_k, len| size += len).unwrap();
- info!(
- "prefab has network size of {}",
- humansize::format_size(size, BINARY)
- );
-
- if args.dry_run {
- return Ok(());
- }
- if let Some(outpath) = args.pack {
- let entry = store.set(&RespackEntry { name: None })?;
- let mut resources = Vec::new();
- store.iter(|r, _| resources.push(r))?;
- save_respack(
- BufWriter::new(File::create(outpath)?),
- &store,
- &resources,
- Some(entry),
- )?;
- } else if let Some(address) = args.address {
- let mut sock = TcpStream::connect(address)?;
- Packet::Connect(random()).write(&mut sock)?;
- for p in &prefabs {
- Packet::AnnouncePrefab(p.clone()).write(&mut sock)?;
- }
- sock.flush()?;
-
- let mut obs = Vec::new();
- if args.add {
- for (i, p) in prefabs.iter().enumerate() {
- let ob = Object::new();
- info!("adding object {ob}");
- Packet::Add(ob, p.clone()).write(&mut sock)?;
- if args.line_up {
- Packet::Position(ob, vec3a(i as f32 * 1.2, 0., i as f32 * 0.3), Vec3A::ZERO)
- .write(&mut sock)?;
- }
- obs.push(ob);
- }
- sock.flush()?;
- }
-
- if args.debug_spin {
- let ob = obs[0];
- let mut sock2 = sock.try_clone().unwrap();
- thread::spawn(move || {
- let mut x = 0.;
- loop {
- Packet::Position(ob, Vec3A::ZERO, vec3a(0., x * 0.1, 0.))
- .write(&mut sock2)
- .unwrap();
- sock2.flush().unwrap();
- x += 0.1;
- sleep(Duration::from_millis(50));
- }
- });
- }
- if args.push {
- if args.use_cache {
- return Ok(());
- }
- store.iter(|k, _| {
- Packet::RespondResource(k, Data(store.get_raw(k).unwrap().unwrap()))
- .write(&mut sock)
- .unwrap();
- })?;
- sock.flush()?;
- } else {
- loop {
- let packet = Packet::read(&mut sock)?;
- match packet {
- Packet::RequestResource(hash) => {
- if let Some(d) = store.get_raw(hash)? {
- Packet::RespondResource(hash, Data(d)).write(&mut sock)?;
- sock.flush()?;
- }
- }
- Packet::Add(ob_a, _) => {
- if args.clear && !obs.contains(&ob_a) {
- info!("removing object {ob_a}");
- Packet::Remove(ob_a).write(&mut sock)?;
- sock.flush()?;
- }
- }
- _ => (),
- }
- }
- }
- } else {
- bail!("no output option specified. either provide an address to a server or use --pack")
- }
- Ok(())
-}
-
-pub type TextureCache = Arc<Mutex<HashMap<String, Resource<Image<'static>>>>>;
-fn load_texture(
- name: &str,
- store: &ResourceStore,
- path: &Path,
- buffers: &[gltf::buffer::Data],
- source: &Source,
- webp: bool,
- texture_cache: &TextureCache,
-) -> Result<Resource<Image<'static>>> {
- let (mut image, uri) = match source {
- gltf::image::Source::View { view, mime_type } => {
- debug!("{name} texture is embedded and of type {mime_type:?}");
- let buf =
- &buffers[view.buffer().index()].0[view.offset()..view.offset() + view.length()];
- (Image(Cow::Borrowed(buf)), None)
- }
- gltf::image::Source::Uri {
- uri,
- mime_type: Some(mime_type),
- } => {
- debug!("{name} texture is {uri:?} and of type {mime_type:?}");
- if let Some(res) = texture_cache.lock().unwrap().get(*uri) {
- return Ok(res.to_owned());
- }
- let path = path.join(uri);
- let mut buf = Vec::new();
- File::open(path)?.read_to_end(&mut buf)?;
- (Image(buf.into()), Some(uri.to_string()))
- }
- gltf::image::Source::Uri {
- uri,
- mime_type: None,
- } => {
- debug!("{name} texture is {uri:?} and has no type");
- if let Some(res) = texture_cache.lock().unwrap().get(*uri) {
- return Ok(res.to_owned());
- }
- let path = path.join(uri);
- let mut buf = Vec::new();
- File::open(path)?.read_to_end(&mut buf)?;
- (Image(buf.into()), Some(uri.to_string()))
- }
- };
-
- if webp {
- let mut image_out = Vec::new();
-
- let len = image.0.len();
- ImageReader::new(Cursor::new(image.0))
- .with_guessed_format()?
- .decode()?
- .write_with_encoder(WebPEncoder::new_lossless(&mut image_out))?;
- debug!("webp encode: {len} -> {}", image_out.len());
- image = Image(Cow::Owned(image_out));
- }
- let res = Resource(store.set(&image)?.0, PhantomData);
- if let Some(uri) = uri {
- texture_cache.lock().unwrap().insert(uri, res.clone());
- }
- Ok(res)
-}
-
-pub fn transform_to_affine(trans: Transform) -> Affine3A {
- let mat = trans.matrix();
- Affine3A::from_cols_array_2d(&[
- [mat[0][0], mat[0][1], mat[0][2]],
- [mat[1][0], mat[1][1], mat[1][2]],
- [mat[2][0], mat[2][1], mat[2][2]],
- [mat[3][0], mat[3][1], mat[3][2]],
- ])
-}
diff --git a/world/src/mesh.rs b/world/src/mesh.rs
deleted file mode 100644
index ffc0f2f..0000000
--- a/world/src/mesh.rs
+++ /dev/null
@@ -1,431 +0,0 @@
-/*
- 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 crate::{Args, TextureCache, load_texture, vrm::VrmInfo};
-use anyhow::Result;
-use gltf::{Mesh, Node, buffer::Data};
-use log::{debug, info, warn};
-use std::{collections::BTreeMap, path::Path};
-use weareshared::{
- Affine3A, Vec3A, packets::Resource, resources::MeshPart, store::ResourceStore, vec2, vec3a,
- vec4,
-};
-
-pub fn import_mesh(
- mesh: Mesh,
- trans: Affine3A,
- buffers: &[Data],
- store: &ResourceStore,
- path_base: &Path,
- node: &Node,
- args: &Args,
- texture_cache: &TextureCache,
- joint_index_map: &BTreeMap<(usize, u16), u32>,
- vrm: &VrmInfo,
- head_bones: &[u32],
-) -> 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 {
- info!("adding mesh {name:?}");
- } else {
- info!("adding mesh");
- }
- let reader = p.reader(|buf| Some(&buffers[buf.index()]));
-
- let mut num_vertex = 0;
- let va_position = reader
- .read_positions()
- .map(|iter| {
- let a = iter.map(|[x, y, z]| vec3a(x, y, z)).collect::<Vec<_>>();
- debug!("{} vertex positions", a.len());
- num_vertex = a.len();
- store.set(&a)
- })
- .transpose()?;
-
- let va_normal = reader
- .read_normals()
- .map(|iter| {
- let a = iter.map(|[x, y, z]| vec3a(x, y, z)).collect::<Vec<_>>();
- debug!("{} vertex normals", a.len());
- store.set(&a)
- })
- .transpose()?;
-
- let va_tangent = reader
- .read_tangents()
- .map(|iter| {
- // TODO dont ignore handedness
- let a = iter
- .map(|[x, y, z, h]| vec4(x, y, z, h))
- .collect::<Vec<_>>();
- debug!("{} vertex tangents", a.len());
- store.set(&a)
- })
- .transpose()?;
-
- let mut many_head_bones = false;
- let va_joint_index = reader
- .read_joints(0)
- .map(|iter| {
- let si = node.skin().unwrap().index();
- let a = iter
- .into_u16()
- .map(|x| x.map(|x| joint_index_map[&(si, x)]))
- .collect::<Vec<_>>();
-
- let head_bone_count = a
- .iter()
- .flatten()
- .filter(|b| head_bones.contains(*b))
- .count();
- many_head_bones |= head_bone_count > a.len() / 2;
- debug!("{} vertex joint indecies", a.len());
- if many_head_bones {
- debug!("many joints are head bones");
- }
- if a.len() != num_vertex {
- warn!("joint index count does not vertex count")
- }
- store.set(&a)
- })
- .transpose()?;
-
- let va_joint_weight = reader
- .read_weights(0)
- .map(|iter| {
- let a = iter.into_f32().collect::<Vec<_>>();
- debug!("{} vertex joint weights", a.len());
- if a.len() != num_vertex {
- warn!("joint weight count does not vertex count")
- }
- store.set(&a)
- })
- .transpose()?;
-
- let va_texcoord = reader
- .read_tex_coords(0)
- .map(|iter| {
- let a = iter.into_f32().map(|[x, y]| vec2(x, y)).collect::<Vec<_>>();
- debug!("{} vertex texture coordinates", a.len());
- store.set(&a)
- })
- .transpose()?;
-
- let va_albedo = reader
- .read_colors(0)
- .map(|iter| {
- let a = iter
- .into_rgb_f32()
- .map(|[x, y, z]| vec3a(x, y, z))
- .collect::<Vec<_>>();
- debug!("{} vertex colors", a.len());
- store.set(&a)
- })
- .transpose()?;
-
- let va_alpha = reader
- .read_colors(0)
- .map(|iter| {
- let mut color_a = vec![];
- for p in iter.into_rgba_f32() {
- color_a.push(p[3]);
- }
- let o = if color_a.iter().any(|x| *x != 1.) {
- debug!("{} vertex transmissions", color_a.len());
- Some(store.set(&color_a)?)
- } else {
- debug!("vertex transmission pruned");
- None
- };
- Ok::<_, anyhow::Error>(o)
- })
- .transpose()?
- .flatten();
-
- let index = reader
- .read_indices()
- .unwrap()
- .into_u32()
- .array_chunks::<3>()
- .collect::<Vec<_>>();
-
- debug!("{} indecies", index.len() * 3);
- let index = Some(store.set(&index)?);
-
- let mut tex_albedo = None;
- let mut tex_alpha = None;
- if let Some(tex) = p.material().pbr_metallic_roughness().base_color_texture() {
- let r = load_texture(
- "albedo",
- store,
- path_base,
- buffers,
- &tex.texture().source().source(),
- args.webp,
- texture_cache,
- )?;
- tex_albedo = Some(r.clone());
- tex_alpha = Some(r.clone());
- }
- let mut tex_normal = None;
- if let Some(tex) = p.material().normal_texture() {
- tex_normal = Some(load_texture(
- "normal",
- store,
- path_base,
- buffers,
- &tex.texture().source().source(),
- args.webp,
- texture_cache,
- )?);
- }
- let mut tex_emission = None;
- if let Some(tex) = p.material().emissive_texture() {
- tex_emission = Some(load_texture(
- "emission",
- store,
- path_base,
- buffers,
- &tex.texture().source().source(),
- args.webp,
- texture_cache,
- )?);
- }
- let mut tex_transmission = None;
- if let Some(tex) = p
- .material()
- .transmission()
- .and_then(|t| t.transmission_texture())
- {
- tex_transmission = Some(load_texture(
- "transmission",
- store,
- path_base,
- buffers,
- &tex.texture().source().source(),
- args.webp,
- texture_cache,
- )?);
- }
- let mut tex_thickness = None;
- if let Some(tex) = p.material().volume().and_then(|t| t.thickness_texture()) {
- tex_thickness = Some(load_texture(
- "thickness",
- store,
- path_base,
- buffers,
- &tex.texture().source().source(),
- args.webp,
- texture_cache,
- )?);
- }
- let mut tex_occlusion = None;
- if let Some(tex) = p.material().occlusion_texture() {
- tex_occlusion = Some(load_texture(
- "occlusion",
- store,
- path_base,
- buffers,
- &tex.texture().source().source(),
- args.webp,
- texture_cache,
- )?);
- }
- let mut tex_roughness = None;
- let mut tex_metallic = None;
- if let Some(tex) = p
- .material()
- .pbr_metallic_roughness()
- .metallic_roughness_texture()
- {
- let r = load_texture(
- "metallic+roughness",
- store,
- path_base,
- buffers,
- &tex.texture().source().source(),
- args.webp,
- texture_cache,
- )?;
- tex_roughness = Some(r.clone());
- tex_metallic = Some(r.clone());
- }
-
- let g_metallic = Some(p.material().pbr_metallic_roughness().metallic_factor());
- let g_roughness = Some(p.material().pbr_metallic_roughness().roughness_factor());
-
- let base_color = p.material().pbr_metallic_roughness().base_color_factor();
-
- let g_albedo = if base_color[0] != 1. || base_color[1] != 1. || base_color[2] != 1. {
- debug!(
- "albedo is r={},g={},b={}",
- base_color[0], base_color[1], base_color[2]
- );
- Some(Vec3A::new(base_color[0], base_color[1], base_color[2]))
- } else {
- debug!("albedo pruned");
- None
- };
- let g_alpha = if base_color[3] != 1. {
- debug!("alpha is {}", base_color[3]);
- Some(base_color[3])
- } else {
- debug!("alpha pruned");
- None
- };
-
- let emission = p.material().emissive_factor();
- let g_emission = if emission[0] != 0. || emission[1] != 0. || emission[2] != 0. {
- debug!(
- "emission is r={},g={},b={}",
- base_color[0], base_color[1], base_color[2]
- );
- Some(Vec3A::new(emission[0], emission[1], emission[2]))
- } else {
- debug!("emission pruned");
- None
- };
-
- let transmission = p
- .material()
- .transmission()
- .map(|t| t.transmission_factor())
- .unwrap_or(0.);
-
- let g_transmission = if transmission != 0. {
- debug!("transmission is {transmission}");
- Some(transmission)
- } else {
- debug!("transmission pruned");
- None
- };
-
- let g_dispersion = p
- .material()
- .extension_value("KHR_materials_dispersion")
- .and_then(|e| e.get("dispersion"))
- .and_then(|e| e.as_f64())
- .map(|e| e as f32);
- if let Some(d) = g_dispersion {
- debug!("dispersion is {d}");
- }
-
- // if node.name() == Some("fog") {
- // eprintln!("{:#?}", p.material().volume().is_some());
- // eprintln!("{:#?}", p.material().ior());
- // eprintln!("{:#?}", p.material().transmission().is_some());
- // }
-
- let g_attenuation = p.material().volume().map(|v| {
- let ref_dist = v.attenuation_distance();
- let att = Vec3A::from_array(v.attenuation_color().map(
- // manually derived from attenuation coefficient formula. i hope this is correct.
- |factor| -(factor.powf(1. / ref_dist)).ln(),
- ));
- debug!("attenuation is {att}");
- att
- });
- let g_refractive_index = p.material().ior();
- if let Some(i) = g_refractive_index {
- debug!("refractive index is {i}");
- }
- let g_thickness = p.material().volume().map(|v| v.thickness_factor());
-
- let g_unlit = bool_to_opt(
- p.material()
- .extension_value("KHR_materials_unlit")
- .is_some(),
- "unlit",
- );
-
- let g_double_sided = bool_to_opt(p.material().double_sided(), "double sided");
-
- let hint_volume = bool_to_opt(
- g_attenuation.is_some_and(|a| a.length() > 0.01),
- "volume hint",
- );
-
- let hint_hide_first_person = bool_to_opt(
- many_head_bones | vrm.hide_first_person.contains(&node.index()),
- "hide first person hint",
- );
-
- let hint_mirror = bool_to_opt(
- node.name().unwrap_or_default().ends_with("-mirror"),
- "mirror hint",
- );
-
- let armature = node.skin().map(|_| 0);
-
- let mesh = MeshPart {
- name,
- index,
- armature,
- g_albedo,
- g_alpha,
- g_metallic,
- g_roughness,
- g_emission,
- g_transmission,
- g_attenuation,
- g_thickness,
- g_refractive_index,
- g_dispersion,
- g_unlit,
- g_double_sided,
- va_position,
- va_normal,
- va_tangent,
- va_texcoord,
- va_albedo,
- va_alpha,
- va_joint_index,
- va_joint_weight,
- tex_albedo,
- tex_normal,
- tex_roughness,
- tex_metallic,
- tex_alpha,
- tex_emission,
- tex_transmission,
- tex_thickness,
- tex_occlusion,
- hint_hide_first_person,
- hint_mirror,
- hint_volume,
- // not supported by gltf
- hint_static: None, // TODO Set when instancing
- va_transmission: None,
- va_emission: None,
- va_metallic: None,
- va_roughness: None,
- };
- meshes.push((node.index(), trans, store.set(&mesh)?))
- }
- Ok(meshes)
-}
-
-fn bool_to_opt(b: bool, log: &str) -> Option<()> {
- if b {
- debug!("{log}");
- Some(())
- } else {
- None
- }
-}
diff --git a/world/src/physics.rs b/world/src/physics.rs
deleted file mode 100644
index 3beb49b..0000000
--- a/world/src/physics.rs
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- 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, anyhow};
-use gltf::{Gltf, Node, buffer::Data, json::Value};
-use log::{debug, info};
-use weareshared::{
- Affine3A, packets::Resource, resources::CollisionPart, store::ResourceStore, vec3a,
-};
-
-pub fn import_physics(
- gltf: &Gltf,
- trans: Affine3A,
- node: &Node,
- store: &ResourceStore,
- buffers: &[Data],
-) -> 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"))
- {
- debug!("--- COLLISION ---");
- if let Some(collider) = physics.get("collider") {
- if let Some(geometry) = collider.get("geometry") {
- if let Some(&Value::Bool(chull)) = geometry.get("convexHull") {
- let node = geometry
- .get("node")
- .and_then(|n| n.as_u64())
- .ok_or(anyhow!("coll geom node missing"))?;
- let node = gltf
- .nodes()
- .nth(node as usize)
- .ok_or(anyhow!("coll geom node reference invalid"))?;
- let mesh = node.mesh().ok_or(anyhow!("coll geom node has no mesh"))?;
- for p in mesh.primitives() {
- let reader = p.reader(|buf| Some(&buffers[buf.index()]));
-
- let index = reader
- .read_indices()
- .ok_or(anyhow!("coll geom no index buffer"))?
- .into_u32()
- .array_chunks::<3>()
- .collect::<Vec<_>>();
- let position = reader
- .read_positions()
- .ok_or(anyhow!("coll geom no positions"))?
- .map(|[x, y, z]| vec3a(x, y, z))
- .collect::<Vec<_>>();
-
- let mut collpart = CollisionPart {
- name: node.name().map(|s| s.to_string()),
- ..Default::default()
- };
-
- if chull {
- debug!("convex hull has {} positions", position.len());
- collpart.sh_convex_hull = Some(store.set(&position)?);
- } else {
- debug!(
- "mesh has {} indecies and {} positions",
- index.len(),
- position.len()
- );
- collpart.sh_mesh = Some((store.set(&index)?, store.set(&position)?));
- }
-
- info!("added collision {:?}", node.name().unwrap_or_default());
- collision.push((trans, store.set(&collpart)?));
- }
- }
- }
- }
- }
- Ok(collision)
-}
diff --git a/world/src/prefab.rs b/world/src/prefab.rs
deleted file mode 100644
index bf45f01..0000000
--- a/world/src/prefab.rs
+++ /dev/null
@@ -1,426 +0,0 @@
-use crate::{
- Args, TextureCache, animation::import_animation, mesh::import_mesh, physics::import_physics,
- transform_to_affine, vrm::extract_vrm_data,
-};
-use anyhow::{Context, Result, anyhow};
-use glam::{Affine3A, Vec3, Vec3A, vec3a};
-use gltf::{Gltf, Node, import_buffers, scene::Transform};
-use log::{debug, info};
-use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
-use serde::Deserialize;
-use serde_json::Value;
-use std::{
- borrow::Cow,
- collections::BTreeMap,
- fs::{File, read_to_string},
- io::Read,
- path::Path,
- sync::Mutex,
-};
-use weareshared::{
- packets::Resource,
- resources::{
- ArmaturePart, AvatarInfoPart, EnvironmentPart, Image, LightPart, ParticlesPart, Prefab,
- },
- store::ResourceStore,
-};
-
-pub fn import_prefab(
- store: &ResourceStore,
- texture_cache: &TextureCache,
- scenepath: &Path,
- args: &Args,
-) -> Result<Resource<Prefab>> {
- let path_base = scenepath.parent().unwrap();
- let mut gltf =
- Gltf::from_reader_without_validation(File::open(scenepath)?).context("gltf parsing")?;
- let blob = gltf.blob.take();
- let buffers = import_buffers(&gltf, Some(path_base), blob).context("importing buffers")?;
-
- let root = gltf.default_scene().ok_or(anyhow!("no default scene"))?;
-
- // gltf.as_json().asset.copyright
- // eprintln!("{:?}", gltf.extensions_used());
- // eprintln!("{:?}", gltf.extensions());
- // eprintln!("{:?}", root.extensions());
- // eprintln!("{:?}", root.extras());
-
- let mut nodes = Vec::new();
- fn traverse<'a>(out: &mut Vec<(Affine3A, Node<'a>)>, node: Node<'a>, trans: Affine3A) {
- let trans = trans * transform_to_affine(node.transform());
- for c in node.children() {
- traverse(out, c, trans);
- }
- out.push((trans, node));
- }
-
- let mut root_affine = Affine3A::IDENTITY;
- root_affine.matrix3 *= args.scale.unwrap_or(1.);
- root_affine.translation *= args.scale.unwrap_or(1.);
-
- for node in root.nodes() {
- traverse(&mut nodes, node, root_affine);
- }
-
- let vrm = extract_vrm_data(&gltf)?;
-
- 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();
- let mut transform = Vec::new();
- let mut inverse_bind_transform = Vec::new();
-
- for skin in gltf.skins() {
- let mut inverse_bind_mat = skin
- .reader(|buf| Some(&buffers[buf.index()]))
- .read_inverse_bind_matrices();
- for (j_ind, j) in skin.joints().enumerate() {
- let ibm = inverse_bind_mat.as_mut().map(|x| x.next().unwrap());
- let a_ind = match joint_index_to_arm_index.get(&j.index()) {
- Some(i) => *i,
- None => {
- let a_ind = name.len();
- // name.push(j.name().unwrap_or("").to_string());
- name.push(format!("bone{}", a_ind));
- transform.push(transform_to_affine(j.transform()));
- parent_pre_map.push(
- gltf.nodes()
- .find(|n| n.children().any(|c| c.index() == j.index()))
- .map(|n| n.index()),
- );
- inverse_bind_transform.push(
- ibm.map(|a| transform_to_affine(Transform::Matrix { matrix: a }))
- .unwrap_or(Affine3A::IDENTITY),
- );
-
- joint_index_to_arm_index.insert(j.index(), a_ind);
- a_ind
- }
- };
- skin_index_to_arm_index.insert((skin.index(), j_ind as u16), a_ind as u32);
- }
- }
-
- let parent = parent_pre_map
- .into_iter()
- .enumerate()
- .map(|(i, p)| {
- p.and_then(|i| joint_index_to_arm_index.get(&i).copied())
- .unwrap_or(i) as u16
- })
- .collect::<Vec<_>>();
-
- for (node, bname) in &vrm.bone_node_names {
- let ind = joint_index_to_arm_index[node];
- name[ind] = bname.to_owned();
- }
-
- joint_name_to_arm_index = name
- .iter()
- .cloned()
- .enumerate()
- .map(|(a, b)| (b, a))
- .collect();
-
- ArmaturePart {
- name: Some(name),
- parent: Some(parent),
- transform: Some(transform),
- inverse_bind_transform: Some(inverse_bind_transform),
- }
- };
-
- let head_bones = {
- let pa = armature.parent.clone().unwrap_or_default();
- let na = armature.name.clone().unwrap_or_default();
- (0..pa.len())
- .filter(|&(mut i)| {
- let mut f = false;
- while pa[i] as usize != i {
- f |= na[i] == "head";
- i = pa[i] as usize;
- }
- f
- })
- .map(|e| e as u32)
- .collect::<Vec<_>>()
- };
-
- let prefab = Mutex::new(Prefab::default());
- let node_to_meshes = Mutex::new(BTreeMap::<usize, Vec<usize>>::new());
-
- nodes
- .par_iter()
- .map(|(trans, node)| {
- // if node.name().unwrap_or_default() == "particles" {
- // eprintln!("{:?}", node.transform());
- // eprintln!("{:?}", node.extensions());
- // eprintln!("{:?}", node.extras());
- // }
- let name = node.name().unwrap_or_default();
- let extras: Value = node
- .extras()
- .to_owned()
- .map(|v| serde_json::from_str(v.get()).unwrap())
- .unwrap_or(Value::Object(Default::default()));
-
- if !name.ends_with("-collider") {
- if let Some(mesh) = node.mesh() {
- let meshes = import_mesh(
- mesh,
- *trans,
- &buffers,
- &store,
- path_base,
- node,
- &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)) && !args.no_particles {
- #[derive(Deserialize)]
- struct ParticlesAttr {
- density: Option<f32>,
- lifetime: Option<f32>,
- lifetime_spread: Option<f32>,
- velocity: Option<Vec3A>,
- velocity_spread: Option<Vec3A>,
- }
- // let sprite = extras
- // .get("sprite")
- // .ok_or(anyhow!("particle volume is missing sprite"))?;
-
- let attr: ParticlesAttr =
- serde_json::from_value(extras).context("particles attributes")?;
-
- info!("adding particles part");
-
- 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() {
- let name = node.name().map(|e| e.to_owned());
- if let Some(name) = &name {
- info!("adding light {name:?}");
- } else {
- info!("adding light");
- }
- let emission = Some(Vec3A::from_array(light.color()) * light.intensity());
- if let Some(e) = emission {
- debug!("emission is {e}");
- }
- let (position, _, _) = node.transform().decomposed();
- let part = store.set(&LightPart {
- emission,
- name,
- radius: None,
- })?;
- prefab
- .lock()
- .unwrap()
- .light
- .push((Vec3A::from_array(position), part));
- }
- {
- let collider = import_physics(&gltf, *trans, node, &store, &buffers)?;
- prefab.lock().unwrap().collision.extend(collider);
- }
-
- Ok::<_, anyhow::Error>(())
- })
- .reduce(
- || 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();
-
- if let Some(apath) = args.animation.clone() {
- 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")?;
-
- 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() && !l.starts_with(";") {
- let (a, b) = l.split_once("=").unwrap();
- map.insert(a.to_string(), b.to_string());
- }
- }
- Some(map)
- } else {
- None
- };
-
- let mut anim_joint_index_to_arm_index = BTreeMap::new();
- let mut joint_index_to_ibm = 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) {
- debug!(
- "mapping {name:?} (node={}) -> {vrm_name:?} (bone={})",
- anim_node_index, scene_arm_index
- );
- anim_joint_index_to_arm_index.insert(anim_node_index, *scene_arm_index);
- }
- }
- }
- if args.animation_apply_ibm {
- for s in gltf.skins() {
- let reader = s.reader(|buf| Some(&buffers[buf.index()]));
- if let Some(ibms) = reader.read_inverse_bind_matrices() {
- for (jn, ibm) in s.joints().zip(ibms) {
- joint_index_to_ibm.insert(
- jn.index(),
- transform_to_affine(Transform::Matrix { matrix: ibm }),
- );
- }
- }
- }
- debug!("{} joint IBMs found", joint_index_to_ibm.len());
- }
- let transform = args
- .animation_rotation_y
- .map(Affine3A::from_rotation_y)
- .unwrap_or_default()
- * args
- .animation_scale
- .map(Vec3::splat)
- .map(Affine3A::from_scale)
- .unwrap_or_default();
- for a in gltf.animations() {
- prefab.animation.push(import_animation(
- a,
- &store,
- transform,
- &joint_index_to_ibm,
- &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,
- Affine3A::IDENTITY,
- &BTreeMap::new(),
- &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()
- {
- info!("avatar info enabled");
- prefab.avatar_info = Some(
- store.set(&AvatarInfoPart {
- armature: Some(0), // TODO
- camera_mount: vrm
- .camera_mount
- .map(|e| joint_index_to_arm_index[&e] as u32),
- camera_mount_offset: vrm.camera_mount_offset,
- ..Default::default()
- })?,
- );
- }
-
- prefab.armature = if armature.parent.as_ref().is_some_and(|a| !a.is_empty()) {
- vec![store.set(&armature)?]
- } else {
- vec![]
- };
-
- let skybox = if let Some(skybox) = &args.skybox {
- let mut buf = Vec::new();
- File::open(skybox)?.read_to_end(&mut buf)?;
- Some(store.set(&Image(Cow::Owned(buf)))?)
- } else {
- None
- };
- let sun = if args.with_default_sun {
- Some((
- vec3a(1., -5., 1.).normalize(),
- vec3a(1., 1., 1.).normalize() * 100_000.,
- ))
- } else {
- None
- };
- prefab.environment = if skybox.is_some() || sun.is_some() {
- Some(store.set(&EnvironmentPart { skybox, sun })?)
- } else {
- None
- };
-
- prefab.name = args.name.clone().or(gltf
- .default_scene()
- .and_then(|n| n.name())
- .map(|n| n.to_owned()));
-
- if args.debug_light {
- prefab.light.push((
- vec3a(5., 5., 5.),
- store.set(&LightPart {
- name: Some("debug light".to_owned()),
- emission: Some(vec3a(10., 5., 15.)),
- radius: Some(0.3),
- })?,
- ));
- }
-
- Ok(store.set(&prefab)?)
-}
diff --git a/world/src/vrm.rs b/world/src/vrm.rs
deleted file mode 100644
index f014e66..0000000
--- a/world/src/vrm.rs
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- 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::Gltf;
-use serde::Deserialize;
-use std::collections::{BTreeMap, BTreeSet};
-use weareshared::Vec3A;
-
-#[derive(Debug, Default)]
-pub struct VrmInfo {
- pub bone_node_names: Vec<(usize, String)>,
- pub hide_first_person: BTreeSet<usize>,
- pub camera_mount: Option<usize>,
- pub camera_mount_offset: Option<Vec3A>,
-}
-
-pub fn extract_vrm_data(gltf: &Gltf) -> Result<VrmInfo> {
- let mut o = VrmInfo::default();
- let mut fp = None;
- if let Some(vrm) = gltf.extension_value("VRM") {
- // serde_json::to_writer(std::fs::File::create("/tmp/vrm").unwrap(), vrm).unwrap();
- let vrm: Vrm = serde_json::from_value(vrm.clone())?;
- for bone in vrm.humanoid.human_bones {
- o.bone_node_names.push((bone.node, bone.bone))
- }
- fp = vrm.first_person;
- }
- if let Some(vrm) = gltf.extension_value("VRMC_vrm") {
- // serde_json::to_writer(std::fs::File::create("/tmp/vrmc").unwrap(), vrm).unwrap();
- let vrm: Vrmc = serde_json::from_value(vrm.clone())?;
- for (name, bone) in vrm.humanoid.human_bones {
- o.bone_node_names.push((bone.node, name))
- }
- fp = vrm.first_person;
- }
- if let Some(fp) = fp {
- o.camera_mount = fp.first_person_bone;
- o.camera_mount_offset = fp.first_person_bone_offset.map(convert_vrm_vec);
- for ann in fp.mesh_annotations {
- if let FirstPersonFlag::ThirdPersonOnly = ann.first_person_flag {
- o.hide_first_person.insert(ann.node);
- }
- }
- }
- Ok(o)
-}
-
-#[derive(Debug, Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct Vrm {
- humanoid: VrmHumanoid,
- first_person: Option<VrmcFirstPerson>,
-}
-
-#[derive(Debug, Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct VrmHumanoid {
- human_bones: Vec<VrmHumanBone>,
-}
-
-#[derive(Debug, Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct VrmHumanBone {
- bone: String,
- node: usize,
-}
-
-#[derive(Debug, Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct Vrmc {
- humanoid: VrmcHumanoid,
- first_person: Option<VrmcFirstPerson>,
-}
-
-#[derive(Debug, Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct VrmcFirstPerson {
- first_person_bone: Option<usize>,
- first_person_bone_offset: Option<VrmVec3>,
- mesh_annotations: Vec<VrmcFirstPersonMeshAnnotation>,
-}
-
-#[derive(Debug, Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct VrmcFirstPersonMeshAnnotation {
- node: usize,
- #[serde(alias = "type")] // spec says something but their sample uses "type" instead
- first_person_flag: FirstPersonFlag,
-}
-
-#[derive(Debug, Deserialize, Default)]
-#[serde(rename_all = "camelCase")]
-enum FirstPersonFlag {
- ThirdPersonOnly,
- FirstPersonOnly,
- Both,
- #[default]
- Auto,
-}
-
-#[derive(Debug, Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct VrmcHumanoid {
- human_bones: BTreeMap<String, VrmcHumanBone>,
-}
-
-#[derive(Debug, Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct VrmcHumanBone {
- node: usize,
-}
-
-#[derive(Debug, Deserialize)]
-struct VrmVec3 {
- x: f32,
- y: f32,
- z: f32,
-}
-fn convert_vrm_vec(VrmVec3 { x, y, z }: VrmVec3) -> Vec3A {
- Vec3A::new(x, y, z)
-}