diff options
-rw-r--r-- | doc/resources.md | 85 | ||||
-rw-r--r-- | shared/src/resources.rs | 3 | ||||
-rw-r--r-- | world/src/main.rs | 21 | ||||
-rw-r--r-- | world/src/mesh.rs | 10 | ||||
-rw-r--r-- | world/src/vrm.rs | 107 |
5 files changed, 160 insertions, 66 deletions
diff --git a/doc/resources.md b/doc/resources.md index 344101c..7ef22be 100644 --- a/doc/resources.md +++ b/doc/resources.md @@ -40,46 +40,47 @@ array. ombinations of g_\*, va_\* and tex_\* are multiplied except normal which is added. Defaults should be the identity for that operation, so default is 1 / white except normals are zero. -| Key | Value Type | | -| -------------------- | ------------------- | ------------------ | -| `name` | `String` | | -| `index` | `Res<[u32; 3]>` | | -| `armature` | `u32` | | -| `g_metallic` | `Float` | | -| `g_roughness` | `Float` | | -| `g_albedo` | `Vec3` | | -| `g_alpha` | `Float` | | -| `g_transmission` | `Float` | | -| `g_emission` | `Vec3` | | -| `g_refractive_index` | `Float` | | -| `g_attenuation` | `Vec3` | | -| `g_dispersion` | `Float` | | -| `g_thickness` | `Float` | | -| `g_unlit` | | | -| `g_double_sided` | | | -| `va_position` | `Res<[Vec3]>` | | -| `va_normal` | `Res<[Vec3]>` | | -| `va_tangent` | `Res<[Vec4]>` | Tangent+Handedness | -| `va_texcoord` | `Res<[Vec2]>` | | -| `va_roughness` | `Res<[Float]>` | | -| `va_metallic` | `Res<[Float]>` | | -| `va_albedo` | `Res<[Vec3]>` | | -| `va_alpha` | `Res<[Float]>` | | -| `va_transmission` | `Res<[Float]>` | | -| `va_emission` | `Res<[Vec3]>` | | -| `va_joint_weight` | `Res<[[Float; 4]]>` | | -| `va_joint_index` | `Res<[[u32; 4]]>` | | -| `tex_normal` | `Res<Texture>` | Use color channels | -| `tex_roughness` | `Res<Texture>` | Use green channel | -| `tex_metallic` | `Res<Texture>` | Use blue channel | -| `tex_albedo` | `Res<Texture>` | Use color channels | -| `tex_alpha` | `Res<Texture>` | Use alpha channel | -| `tex_transmission` | `Res<Texture>` | Use red channel | -| `tex_emission` | `Res<Texture>` | Use color channels | -| `tex_thickness` | `Res<Texture>` | Use green channel | -| `tex_occlusion` | `Res<Texture>` | Use red channel | -| `hint_mirror` | | | -| `hint_static` | | | +| Key | Value Type | | +| ------------------------ | ------------------- | ------------------ | +| `name` | `String` | | +| `index` | `Res<[u32; 3]>` | | +| `armature` | `u32` | | +| `g_metallic` | `Float` | | +| `g_roughness` | `Float` | | +| `g_albedo` | `Vec3` | | +| `g_alpha` | `Float` | | +| `g_transmission` | `Float` | | +| `g_emission` | `Vec3` | | +| `g_refractive_index` | `Float` | | +| `g_attenuation` | `Vec3` | | +| `g_dispersion` | `Float` | | +| `g_thickness` | `Float` | | +| `g_unlit` | | | +| `g_double_sided` | | | +| `va_position` | `Res<[Vec3]>` | | +| `va_normal` | `Res<[Vec3]>` | | +| `va_tangent` | `Res<[Vec4]>` | Tangent+Handedness | +| `va_texcoord` | `Res<[Vec2]>` | | +| `va_roughness` | `Res<[Float]>` | | +| `va_metallic` | `Res<[Float]>` | | +| `va_albedo` | `Res<[Vec3]>` | | +| `va_alpha` | `Res<[Float]>` | | +| `va_transmission` | `Res<[Float]>` | | +| `va_emission` | `Res<[Vec3]>` | | +| `va_joint_weight` | `Res<[[Float; 4]]>` | | +| `va_joint_index` | `Res<[[u32; 4]]>` | | +| `tex_normal` | `Res<Texture>` | Use color channels | +| `tex_roughness` | `Res<Texture>` | Use green channel | +| `tex_metallic` | `Res<Texture>` | Use blue channel | +| `tex_albedo` | `Res<Texture>` | Use color channels | +| `tex_alpha` | `Res<Texture>` | Use alpha channel | +| `tex_transmission` | `Res<Texture>` | Use red channel | +| `tex_emission` | `Res<Texture>` | Use color channels | +| `tex_thickness` | `Res<Texture>` | Use green channel | +| `tex_occlusion` | `Res<Texture>` | Use red channel | +| `hint_mirror` | | | +| `hint_hide_first_person` | | | +| `hint_static` | | | - **Attenuation**: Attenuation coefficient for each color channel due to scattering within the material volume expressed as e-folding distance (m^-1). @@ -100,6 +101,10 @@ white except normals are zero. the mirrors perspective. It can be assumed that the mesh is on a single plane. - **Static Hint**: Object will not move often. This allows implementations to choose instanced rending when MeshPart are added more than once. +- **Hide First-Person Hint**: Object should not be rendered if the prefab is the + own avatar and first person view is used. This is set for the head and hair + that should not be visible to yourself. See `thirdPersonOnly` variant of + [VRMC_vrm] `firstPerson.meshAnnotations.firstPersonFlag`. ## ArmaturePart diff --git a/shared/src/resources.rs b/shared/src/resources.rs index 4cfc6fa..66ecf7a 100644 --- a/shared/src/resources.rs +++ b/shared/src/resources.rs @@ -87,6 +87,7 @@ pub struct MeshPart { pub tex_occlusion: Option<Resource<Image<'static>>>, pub hint_mirror: Option<()>, pub hint_static: Option<()>, + pub hint_hide_first_person: Option<()>, } #[derive(Debug, Default, Clone)] @@ -303,6 +304,7 @@ impl ReadWrite for MeshPart { write_kv_opt(w, b"tex_occlusion", &self.tex_occlusion)?; write_kv_opt(w, b"hint_mirror", &self.hint_mirror)?; write_kv_opt(w, b"hint_static", &self.hint_static)?; + write_kv_opt(w, b"hint_hide_first_person", &self.hint_hide_first_person)?; Ok(()) } fn read(r: &mut dyn Read) -> Result<Self> { @@ -341,6 +343,7 @@ impl ReadWrite for MeshPart { b"tex_occlusion" => Ok(s.tex_occlusion = Some(read_slice(v)?)), b"hint_mirror" => Ok(s.hint_mirror = Some(read_slice(v)?)), b"hint_static" => Ok(s.hint_static = Some(read_slice(v)?)), + b"hint_hide_first_person" => Ok(s.hint_hide_first_person = Some(read_slice(v)?)), x => Ok(warn!( "unknown mesh part key: {:?}", String::from_utf8_lossy(x) diff --git a/world/src/main.rs b/world/src/main.rs index 055be47..e3c365a 100644 --- a/world/src/main.rs +++ b/world/src/main.rs @@ -42,7 +42,7 @@ use std::{ thread::{self, sleep}, time::Duration, }; -use vrm::{Vrm, Vrmc}; +use vrm::extract_vrm_data; use weareshared::{ Affine3A, Vec3A, helper::ReadWrite, @@ -137,6 +137,8 @@ fn main() -> Result<()> { traverse(&mut nodes, node, root_affine); } + let vrm = extract_vrm_data(&gltf)?; + let mut skin_index_to_arm_index = BTreeMap::new(); let armature = { let mut joint_index_to_arm_index = BTreeMap::new(); @@ -176,19 +178,9 @@ fn main() -> Result<()> { }) .collect::<Vec<_>>(); - if let Some(vrm) = gltf.extension_value("VRM") { - let vrm: Vrm = serde_json::from_value(vrm.clone())?; - for bone in vrm.humanoid.human_bones { - let ind = joint_index_to_arm_index[&bone.node]; - name[ind] = bone.bone; - } - } - if let Some(vrm) = gltf.extension_value("VRMC_vrm") { - let vrm: Vrmc = serde_json::from_value(vrm.clone())?; - for (bname, bone) in vrm.humanoid.human_bones { - let ind = joint_index_to_arm_index[&bone.node]; - name[ind] = bname; - } + for (node, bname) in &vrm.bone_node_names { + let ind = joint_index_to_arm_index[node]; + name[ind] = bname.to_owned(); } for (i, (name, tr)) in name.iter().zip(transform.iter()).enumerate() { @@ -226,6 +218,7 @@ fn main() -> Result<()> { &args, &texture_cache, &skin_index_to_arm_index, + &vrm, )?; } } diff --git a/world/src/mesh.rs b/world/src/mesh.rs index 6fff9c4..ebecfa5 100644 --- a/world/src/mesh.rs +++ b/world/src/mesh.rs @@ -14,7 +14,7 @@ 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}; +use crate::{Args, TextureCache, load_texture, vrm::VrmInfo}; use anyhow::Result; use gltf::{Mesh, Node, buffer::Data}; use log::{debug, info, warn}; @@ -37,6 +37,7 @@ pub fn import_mesh( args: &Args, texture_cache: &TextureCache, joint_index_map: &BTreeMap<(usize, u16), u32>, + vrm: &VrmInfo, ) -> Result<()> { for p in mesh.primitives() { let name = mesh.name().or(node.name()).map(|e| e.to_owned()); @@ -348,6 +349,12 @@ pub fn import_mesh( None }; + let hint_hide_first_person = if vrm.hide_first_person.contains(&node.index()) { + Some(()) + } else { + None + }; + let armature = node.skin().map(|_| 0); let mesh = store.set(&MeshPart { @@ -383,6 +390,7 @@ pub fn import_mesh( tex_transmission, tex_thickness, tex_occlusion, + hint_hide_first_person, // not supported by gltf hint_mirror: None, // TODO hint_static: None, // TODO Set when instancing diff --git a/world/src/vrm.rs b/world/src/vrm.rs index a9ea6f4..1b89642 100644 --- a/world/src/vrm.rs +++ b/world/src/vrm.rs @@ -1,6 +1,65 @@ -use std::collections::BTreeMap; +/* + 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}, + fs::File, +}; + +pub struct VrmInfo { + pub bone_node_names: Vec<(usize, String)>, + pub hide_first_person: BTreeSet<usize>, +} + +pub fn extract_vrm_data(gltf: &Gltf) -> Result<VrmInfo> { + let mut bone_node_names = Vec::new(); + let mut hide_first_person = BTreeSet::new(); + if let Some(vrm) = gltf.extension_value("VRM") { + serde_json::to_writer(File::create("/tmp/vrm").unwrap(), vrm).unwrap(); + let vrm: Vrm = serde_json::from_value(vrm.clone())?; + for bone in vrm.humanoid.human_bones { + bone_node_names.push((bone.node, bone.bone)) + } + } + if let Some(vrm) = gltf.extension_value("VRMC_vrm") { + serde_json::to_writer(File::create("/tmp/vrmc").unwrap(), vrm).unwrap(); + let vrm: Vrmc = serde_json::from_value(vrm.clone())?; + for (name, bone) in vrm.humanoid.human_bones { + bone_node_names.push((bone.node, name)) + } + if let Some(fp) = vrm.first_person { + for ann in fp.mesh_annotations { + match ann.first_person_flag { + FirstPersonFlag::ThirdPersonOnly => { + hide_first_person.insert(ann.node); + } + _ => (), + } + } + } + } + eprintln!("hide {hide_first_person:?}"); + Ok(VrmInfo { + bone_node_names, + hide_first_person, + }) +} #[derive(Debug, Deserialize)] pub struct Vrm { @@ -10,29 +69,55 @@ pub struct Vrm { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct VrmHumanoid { - pub human_bones: Vec<VrmHumanBone>, + 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")] -pub struct VrmHumanBone { - pub bone: String, - pub node: usize, +struct VrmcFirstPerson { + mesh_annotations: Vec<VrmcFirstPersonMeshAnnotation>, } #[derive(Debug, Deserialize)] -pub struct Vrmc { - pub humanoid: VrmcHumanoid, +#[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")] -pub struct VrmcHumanoid { - pub human_bones: BTreeMap<String, VrmcHumanBone>, +struct VrmcHumanoid { + human_bones: BTreeMap<String, VrmcHumanBone>, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct VrmcHumanBone { - pub node: usize, +struct VrmcHumanBone { + node: usize, } |