diff options
Diffstat (limited to 'import/src/mesh.rs')
-rw-r--r-- | import/src/mesh.rs | 431 |
1 files changed, 431 insertions, 0 deletions
diff --git a/import/src/mesh.rs b/import/src/mesh.rs new file mode 100644 index 0000000..ffc0f2f --- /dev/null +++ b/import/src/mesh.rs @@ -0,0 +1,431 @@ +/* + 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 + } +} |