diff --git a/lib/rbx_loader/src/lib.rs b/lib/rbx_loader/src/lib.rs index bb73961..bc5f777 100644 --- a/lib/rbx_loader/src/lib.rs +++ b/lib/rbx_loader/src/lib.rs @@ -3,6 +3,7 @@ use rbx_dom_weak::WeakDom; mod rbx; mod mesh; +mod union; mod primitives; pub mod data{ diff --git a/lib/rbx_loader/src/mesh.rs b/lib/rbx_loader/src/mesh.rs index 4b18f56..2b3bb60 100644 --- a/lib/rbx_loader/src/mesh.rs +++ b/lib/rbx_loader/src/mesh.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; -use rbx_mesh::mesh::{Vertex2, Vertex2Truncated}; -use strafesnet_common::{integer::vec3,model::{self, ColorId, IndexedVertex, NormalId, PolygonGroup, PolygonList, PositionId, TextureCoordinateId, VertexId}}; +use rbx_mesh::mesh::{Vertex2,Vertex2Truncated}; +use strafesnet_common::{integer::vec3,model::{self,ColorId,IndexedVertex,NormalId,PolygonGroup,PolygonList,PositionId,RenderConfigId,TextureCoordinateId,VertexId}}; #[allow(dead_code)] #[derive(Debug)] @@ -205,7 +205,13 @@ pub fn convert(roblox_mesh_bytes:crate::data::RobloxMeshBytes)->Result<model::Me unique_vertices, polygon_groups, //these should probably be moved to the model... - graphics_groups:Vec::new(), + //but what if models want to use the same texture + graphics_groups:vec![model::IndexedGraphicsGroup{ + render:RenderConfigId::new(0), + //the lowest lod is highest quality + groups:vec![model::PolygonGroupId::new(0)] + }], + //disable physics physics_groups:Vec::new(), }) } diff --git a/lib/rbx_loader/src/rbx.rs b/lib/rbx_loader/src/rbx.rs index 643cf3d..6afcdbf 100644 --- a/lib/rbx_loader/src/rbx.rs +++ b/lib/rbx_loader/src/rbx.rs @@ -406,6 +406,7 @@ enum RobloxBasePartDescription{ enum Shape{ Primitive(primitives::Primitives), MeshPart, + PhysicsData, } enum MeshAvailability{ Immediate, @@ -446,6 +447,7 @@ where let mut primitive_models_deferred_attributes=Vec::new(); let mut primitive_meshes=Vec::new(); let mut mesh_id_from_description=HashMap::new(); + let mut mesh_id_from_physics_data=HashMap::<&[u8],_>::new(); //just going to leave it like this for now instead of reworking the data structures for this whole thing let textureless_render_group=acquire_render_config_id(None); @@ -471,7 +473,7 @@ where object.properties.get("CanCollide"), ) { - let model_transform=planar64_affine3_from_roblox(cf,size); + let mut model_transform=planar64_affine3_from_roblox(cf,size); if model_transform.matrix3.det().is_zero(){ let mut parent_ref=object.parent(); @@ -503,6 +505,7 @@ where "WedgePart"=>Shape::Primitive(primitives::Primitives::Wedge), "CornerWedgePart"=>Shape::Primitive(primitives::Primitives::CornerWedge), "MeshPart"=>Shape::MeshPart, + "UnionOperation"=>Shape::PhysicsData, _=>{ println!("Unsupported BasePart ClassName={}; defaulting to cube",object.class); Shape::Primitive(primitives::Primitives::Cube) @@ -697,6 +700,41 @@ where }else{ panic!("Mesh has no Mesh or Texture"); }, + Shape::PhysicsData=>{ + //The union mesh is sized already + model_transform=planar64_affine3_from_roblox(cf,&rbx_dom_weak::types::Vector3{x:2.0,y:2.0,z:2.0}); + if let Some(rbx_dom_weak::types::Variant::BinaryString(data))=object.properties.get("PhysicsData"){ + let physics_data=data.as_ref(); + let mesh_id=if let Some(&mesh_id)=mesh_id_from_physics_data.get(physics_data){ + mesh_id + }else{ + match crate::union::convert(physics_data){ + Ok(mesh)=>{ + let mesh_id=model::MeshId::new(primitive_meshes.len() as u32); + primitive_meshes.push(mesh); + mesh_id_from_physics_data.insert(physics_data,mesh_id); + mesh_id + }, + Err(e)=>{ + model_transform=planar64_affine3_from_roblox(cf,size); + if !matches!(e,crate::union::Error::Block){ + println!("Union mesh decode error {e:?}"); + } + *mesh_id_from_description.entry(RobloxBasePartDescription::Part(RobloxPartDescription::default())) + .or_insert_with(||{ + let mesh_id=model::MeshId::new(primitive_meshes.len() as u32); + let mesh=primitives::unit_cube(textureless_render_group); + primitive_meshes.push(mesh); + mesh_id + }) + }, + } + }; + (MeshAvailability::Immediate,mesh_id) + }else{ + panic!("Mesh has no Mesh or Texture"); + } + }, }; let model_deferred_attributes=ModelDeferredAttributes{ mesh:mesh_id, @@ -776,12 +814,10 @@ impl PartialMap1{ .entry(render).or_insert_with(||{ let mesh_id=model::MeshId::new(self.primitive_meshes.len() as u32); let mut mesh_clone=mesh_with_aabb.mesh.clone(); - //add a render group lool - mesh_clone.graphics_groups.push(model::IndexedGraphicsGroup{ - render, - //the lowest lod is highest quality - groups:vec![model::PolygonGroupId::new(0)] - }); + //set the render group lool + if let Some(graphics_group)=mesh_clone.graphics_groups.first_mut(){ + graphics_group.render=render; + } self.primitive_meshes.push(mesh_clone); mesh_id }), diff --git a/lib/rbx_loader/src/union.rs b/lib/rbx_loader/src/union.rs new file mode 100644 index 0000000..b4b8131 --- /dev/null +++ b/lib/rbx_loader/src/union.rs @@ -0,0 +1,126 @@ +use std::collections::HashMap; + +use strafesnet_common::model::{self, ColorId, IndexedVertex, NormalId, PolygonGroup, PolygonGroupId, PolygonList, PositionId, RenderConfigId, TextureCoordinateId, VertexId}; +use strafesnet_common::integer::vec3; + +#[allow(dead_code)] +#[derive(Debug)] +pub enum Error{ + Block, + MissingVertexId(u32), + Planar64Vec3(strafesnet_common::integer::Planar64TryFromFloatError), + RobloxPhysicsData(rbx_mesh::physics_data::Error), +} +impl std::fmt::Display for Error{ + fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ + write!(f,"{self:?}") + } +} + +impl std::error::Error for Error{} +pub fn convert(roblox_physics_data:&[u8])->Result<model::Mesh,Error>{ + if let b""=roblox_physics_data{ + return Err(Error::Block); + } + let mut cursor=std::io::Cursor::new(roblox_physics_data); + let physics_data:rbx_mesh::physics_data::PhysicsData=rbx_mesh::read_physics_data(&mut cursor).map_err(Error::RobloxPhysicsData)?; + assert_eq!(cursor.position(),cursor.into_inner().len() as u64); + let meshes=match physics_data{ + rbx_mesh::physics_data::PhysicsData::CSGK(_) + |rbx_mesh::physics_data::PhysicsData::CSGPHS(rbx_mesh::physics_data::CSGPHS::Block) + =>return Err(Error::Block), + rbx_mesh::physics_data::PhysicsData::CSGPHS(rbx_mesh::physics_data::CSGPHS::Meshes3(meshes)) + |rbx_mesh::physics_data::PhysicsData::CSGPHS(rbx_mesh::physics_data::CSGPHS::Meshes5(meshes)) + =>meshes.meshes, + rbx_mesh::physics_data::PhysicsData::CSGPHS(rbx_mesh::physics_data::CSGPHS::PhysicsInfoMesh(pim)) + =>vec![pim.mesh], + }; + let mut unique_pos=Vec::new(); + let mut pos_id_from=HashMap::new(); + let mut unique_tex=Vec::new(); + let mut tex_id_from=HashMap::new(); + let mut unique_normal=Vec::new(); + let mut normal_id_from=HashMap::new(); + let mut unique_color=Vec::new(); + let mut color_id_from=HashMap::new(); + let mut unique_vertices=Vec::new(); + let mut vertex_id_from=HashMap::new(); + let mut acquire_pos_id=|pos|{ + let p=vec3::try_from_f32_array(pos).map_err(Error::Planar64Vec3)?; + Ok(PositionId::new(*pos_id_from.entry(p).or_insert_with(||{ + let pos_id=unique_pos.len(); + unique_pos.push(p); + pos_id + }) as u32)) + }; + let mut acquire_tex_id=|tex|{ + let h=bytemuck::cast::<[f32;2],[u32;2]>(tex); + TextureCoordinateId::new(*tex_id_from.entry(h).or_insert_with(||{ + let tex_id=unique_tex.len(); + unique_tex.push(glam::Vec2::from_array(tex)); + tex_id + }) as u32) + }; + let mut acquire_normal_id=|normal|{ + let n=vec3::try_from_f32_array(normal).map_err(Error::Planar64Vec3)?; + Ok(NormalId::new(*normal_id_from.entry(n).or_insert_with(||{ + let normal_id=unique_normal.len(); + unique_normal.push(n); + normal_id + }) as u32)) + }; + let mut acquire_color_id=|color|{ + let h=bytemuck::cast::<[f32;4],[u32;4]>(color); + ColorId::new(*color_id_from.entry(h).or_insert_with(||{ + let color_id=unique_color.len(); + unique_color.push(glam::Vec4::from_array(color)); + color_id + }) as u32) + }; + let mut acquire_vertex_id=|vertex:IndexedVertex|{ + VertexId::new(*vertex_id_from.entry(vertex.clone()).or_insert_with(||{ + let vertex_id=unique_vertices.len(); + unique_vertices.push(vertex); + vertex_id + }) as u32) + }; + let color=acquire_color_id([1.0f32;4]); + let tex=acquire_tex_id([0.0f32;2]); + let polygon_groups:Vec<PolygonGroup>=meshes.into_iter().map(|mesh|{ + Ok(PolygonGroup::PolygonList(PolygonList::new(mesh.faces.into_iter().map(|[vertex_id0,vertex_id1,vertex_id2]|{ + let v0=mesh.vertices.get(vertex_id0.0 as usize).ok_or(Error::MissingVertexId(vertex_id0.0))?; + let v1=mesh.vertices.get(vertex_id1.0 as usize).ok_or(Error::MissingVertexId(vertex_id1.0))?; + let v2=mesh.vertices.get(vertex_id2.0 as usize).ok_or(Error::MissingVertexId(vertex_id2.0))?; + let vertex_norm=(glam::Vec3::from_slice(v1)-glam::Vec3::from_slice(v0)) + .cross(glam::Vec3::from_slice(v2)-glam::Vec3::from_slice(v0)).to_array(); + let mut ingest_vertex_id=|&vertex_pos:&[f32;3]|Ok(acquire_vertex_id(IndexedVertex{ + pos:acquire_pos_id(vertex_pos)?, + tex, + normal:acquire_normal_id(vertex_norm)?, + color, + })); + Ok(vec![ + ingest_vertex_id(v0)?, + ingest_vertex_id(v1)?, + ingest_vertex_id(v2)?, + ]) + }).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{ + groups:vec![PolygonGroupId::new(id as u32)] + }).collect(); + Ok(model::Mesh{ + unique_pos, + unique_normal, + unique_tex, + unique_color, + unique_vertices, + polygon_groups, + graphics_groups, + physics_groups, + }) +}