diff --git a/lib/rbx_loader/src/union.rs b/lib/rbx_loader/src/union.rs index a68bcc1..30afbbf 100644 --- a/lib/rbx_loader/src/union.rs +++ b/lib/rbx_loader/src/union.rs @@ -1,6 +1,5 @@ -use std::collections::HashMap; - -use strafesnet_common::model::{self, ColorId, IndexedVertex, NormalId, PolygonGroup, PolygonGroupId, PolygonList, PositionId, RenderConfigId, TextureCoordinateId, VertexId}; +use rbx_mesh::mesh_data::NormalId2 as MeshDataNormalId2; +use strafesnet_common::model::{self,IndexedVertex,PolygonGroup,PolygonGroupId,PolygonList,RenderConfigId}; use strafesnet_common::integer::vec3; #[allow(dead_code)] @@ -19,6 +18,40 @@ impl std::fmt::Display for Error{ } } +// wacky state machine to make sure all vertices in a face agree upon what NormalId to use. +// Roblox duplicates this information per vertex when it should only exist per-face. +enum MeshDataNormalStatus{ + Agree(MeshDataNormalId2), + Conflicting, +} +struct MeshDataNormalChecker{ + status:Option<MeshDataNormalStatus>, +} +impl MeshDataNormalChecker{ + fn new()->Self{ + Self{status:None} + } + fn check(&mut self,normal:MeshDataNormalId2){ + self.status=match self.status.take(){ + None=>Some(MeshDataNormalStatus::Agree(normal)), + Some(MeshDataNormalStatus::Agree(old_normal))=>{ + if old_normal==normal{ + Some(MeshDataNormalStatus::Agree(old_normal)) + }else{ + Some(MeshDataNormalStatus::Conflicting) + } + }, + Some(MeshDataNormalStatus::Conflicting)=>Some(MeshDataNormalStatus::Conflicting), + }; + } + fn into_agreed_normal(self)->Option<MeshDataNormalId2>{ + self.status.and_then(|status|match status{ + MeshDataNormalStatus::Agree(normal)=>Some(normal), + MeshDataNormalStatus::Conflicting=>None, + }) + } +} + impl std::error::Error for Error{} pub fn convert(roblox_physics_data:&[u8],roblox_mesh_data:&[u8])->Result<model::Mesh,Error>{ match (roblox_physics_data,roblox_mesh_data){ @@ -52,10 +85,48 @@ pub fn convert(roblox_physics_data:&[u8],roblox_mesh_data:&[u8])->Result<model:: rbx_mesh::physics_data::PhysicsData::CSGPHS(rbx_mesh::physics_data::CSGPHS::PhysicsInfoMesh(pim)) =>vec![pim.mesh], }; + + // build graphics and physics meshes let mut mb=strafesnet_common::model::MeshBuilder::new(); + // graphics + const NORMAL_FACES:usize=6; + let mut polygon_groups_normal_id=vec![Vec::new();NORMAL_FACES]; + for [vertex_id0,vertex_id1,vertex_id2] in graphics_mesh.faces{ + let face=[ + graphics_mesh.vertices.get(vertex_id0.0 as usize).ok_or(Error::MissingVertexId(vertex_id0.0))?, + graphics_mesh.vertices.get(vertex_id1.0 as usize).ok_or(Error::MissingVertexId(vertex_id1.0))?, + graphics_mesh.vertices.get(vertex_id2.0 as usize).ok_or(Error::MissingVertexId(vertex_id2.0))?, + ]; + let mut normal_agreement_checker=MeshDataNormalChecker::new(); + let face=face.into_iter().map(|vertex|{ + normal_agreement_checker.check(vertex.normal_id); + let pos=mb.acquire_pos_id(vec3::try_from_f32_array(vertex.pos)?); + let normal=mb.acquire_normal_id(vec3::try_from_f32_array(vertex.norm)?); + let tex=mb.acquire_tex_id(glam::Vec2::from_array(vertex.tex)); + let color=mb.acquire_color_id(glam::Vec4::from_array(vertex.color.map(|f|f as f32/255.0f32))); + Ok(mb.acquire_vertex_id(IndexedVertex{pos,tex,normal,color})) + }).collect::<Result<Vec<_>,_>>().map_err(Error::Planar64Vec3)?; + if let Some(normal_id)=normal_agreement_checker.into_agreed_normal(){ + polygon_groups_normal_id[normal_id as usize-1].push(face); + }else{ + panic!("Empty face!"); + } + } + let graphics_groups=(0..polygon_groups_normal_id.len()).map(|polygon_group_id|{ + model::IndexedGraphicsGroup{ + render:RenderConfigId::new(0), + groups:vec![PolygonGroupId::new(polygon_group_id as u32)] + } + }).collect(); + + //physics let color=mb.acquire_color_id(glam::Vec4::ONE); let tex=mb.acquire_tex_id(glam::Vec2::ZERO); - let polygon_groups:Vec<PolygonGroup>=physics_convex_meshes.into_iter().map(|mesh|{ + let polygon_groups:Vec<PolygonGroup>=polygon_groups_normal_id.into_iter().map(|faces| + // graphics polygon groups (to be rendered) + Ok(PolygonGroup::PolygonList(PolygonList::new(faces))) + ).chain(physics_convex_meshes.into_iter().map(|mesh|{ + // physics polygon groups (to do physics) Ok(PolygonGroup::PolygonList(PolygonList::new(mesh.faces.into_iter().map(|[vertex_id0,vertex_id1,vertex_id2]|{ let face=[ mesh.vertices.get(vertex_id0.0 as usize).ok_or(Error::MissingVertexId(vertex_id0.0))?, @@ -70,12 +141,8 @@ pub fn convert(roblox_physics_data:&[u8],roblox_mesh_data:&[u8])->Result<model:: Ok(mb.acquire_vertex_id(IndexedVertex{pos,tex,normal,color})) }).collect() }).collect::<Result<_,_>>()?))) - }).collect::<Result<_,_>>()?; - let graphics_groups=vec![model::IndexedGraphicsGroup{ - render:RenderConfigId::new(0), - groups:(0..polygon_groups.len()).map(|id|PolygonGroupId::new(id as u32)).collect() - }]; - let physics_groups=(0..polygon_groups.len()).map(|id|model::IndexedPhysicsGroup{ + })).collect::<Result<_,_>>()?; + let physics_groups=(NORMAL_FACES..polygon_groups.len()).map(|id|model::IndexedPhysicsGroup{ groups:vec![PolygonGroupId::new(id as u32)] }).collect(); Ok(mb.build(