/* 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 . */ 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)>> { 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::>(); 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::>(); 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::>(); 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::>(); 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::>(); 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::>(); 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::>(); 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::>(); 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 } }