From c48beced0571935aa3a8691f43094b36eedcdd33 Mon Sep 17 00:00:00 2001 From: Quaternions <krakow20@gmail.com> Date: Fri, 31 Jan 2025 09:12:18 -0800 Subject: [PATCH] plumb decal into union convert --- lib/rbx_loader/src/loader.rs | 12 ++- lib/rbx_loader/src/rbx.rs | 145 +++++++++++++++++++++++------------ lib/rbx_loader/src/union.rs | 31 +++++++- 3 files changed, 133 insertions(+), 55 deletions(-) diff --git a/lib/rbx_loader/src/loader.rs b/lib/rbx_loader/src/loader.rs index bef9d8d..9cfa8ab 100644 --- a/lib/rbx_loader/src/loader.rs +++ b/lib/rbx_loader/src/loader.rs @@ -4,6 +4,7 @@ use strafesnet_common::model::Mesh; use strafesnet_deferred_loader::{loader::Loader,texture::Texture}; use crate::data::RobloxMeshBytes; +use crate::rbx::RobloxFaceTextureDescription; fn read_entire_file(path:impl AsRef<std::path::Path>)->Result<Vec<u8>,std::io::Error>{ let mut file=std::fs::File::open(path)?; @@ -103,6 +104,7 @@ pub enum MeshType<'a>{ mesh_data:&'a [u8], physics_data:&'a [u8], size_float_bits:[u32;3], + part_texture_description:[Option<RobloxFaceTextureDescription>;6], }, } #[derive(Hash,Eq,PartialEq)] @@ -122,12 +124,14 @@ impl MeshIndex<'_>{ mesh_data:&'a [u8], physics_data:&'a [u8], size:&rbx_dom_weak::types::Vector3, + part_texture_description:crate::rbx::RobloxPartDescription, )->MeshIndex<'a>{ MeshIndex{ mesh_type:MeshType::Union{ mesh_data, physics_data, - size_float_bits:[size.x.to_bits(),size.y.to_bits(),size.z.to_bits()] + size_float_bits:[size.x.to_bits(),size.y.to_bits(),size.z.to_bits()], + part_texture_description, }, content, } @@ -152,7 +156,7 @@ impl<'a> Loader for MeshLoader<'a>{ let data=read_entire_file(file_name)?; crate::mesh::convert(RobloxMeshBytes::new(data))? }, - MeshType::Union{mut physics_data,mut mesh_data,size_float_bits}=>{ + MeshType::Union{mut physics_data,mut mesh_data,size_float_bits,part_texture_description}=>{ // decode asset let size=glam::Vec3::from_array(size_float_bits.map(f32::from_bits)); if !index.content.is_empty()&&(physics_data.is_empty()||mesh_data.is_empty()){ @@ -176,9 +180,9 @@ impl<'a> Loader for MeshLoader<'a>{ mesh_data=data.as_ref(); } } - crate::union::convert(physics_data,mesh_data,size)? + crate::union::convert(physics_data,mesh_data,size,part_texture_description)? }else{ - crate::union::convert(physics_data,mesh_data,size)? + crate::union::convert(physics_data,mesh_data,size,part_texture_description)? } }, }; diff --git a/lib/rbx_loader/src/rbx.rs b/lib/rbx_loader/src/rbx.rs index c54d95a..4d517b3 100644 --- a/lib/rbx_loader/src/rbx.rs +++ b/lib/rbx_loader/src/rbx.rs @@ -346,58 +346,103 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:mode } } -#[derive(Clone,Copy,PartialEq)] -struct RobloxTextureTransform{ - offset_u:f32, - offset_v:f32, - scale_u:f32, - scale_v:f32, +#[derive(Clone,Copy)] +pub struct RobloxTextureTransform{ + offset_studs_u:f32, + offset_studs_v:f32, + studs_per_tile_u:f32, + studs_per_tile_v:f32, + size_u:f32, + size_v:f32, } -impl std::cmp::Eq for RobloxTextureTransform{}//???? -impl std::default::Default for RobloxTextureTransform{ - fn default()->Self{ - Self{offset_u:0.0,offset_v:0.0,scale_u:1.0,scale_v:1.0} - } +#[derive(Clone,Copy,Hash,Eq,PartialEq)] +pub struct RobloxTextureTransformBits{ + offset_studs_u:u32, + offset_studs_v:u32, + studs_per_tile_u:u32, + studs_per_tile_v:u32, + size_u:u32, + size_v:u32, } -impl std::hash::Hash for RobloxTextureTransform{ - fn hash<H:std::hash::Hasher>(&self,state:&mut H) { - self.offset_u.to_ne_bytes().hash(state); - self.offset_v.to_ne_bytes().hash(state); - self.scale_u.to_ne_bytes().hash(state); - self.scale_v.to_ne_bytes().hash(state); +impl RobloxTextureTransform{ + fn identity()->Self{ + Self{ + offset_studs_u:0.0, + offset_studs_v:0.0, + studs_per_tile_u:1.0, + studs_per_tile_v:1.0, + size_u:1.0, + size_v:1.0, + } + } + pub fn to_bits(self)->RobloxTextureTransformBits{ + RobloxTextureTransformBits{ + offset_studs_u:self.offset_studs_u.to_bits(), + offset_studs_v:self.offset_studs_v.to_bits(), + studs_per_tile_u:self.studs_per_tile_u.to_bits(), + studs_per_tile_v:self.studs_per_tile_v.to_bits(), + size_u:self.size_u.to_bits(), + size_v:self.size_v.to_bits(), + } + } + pub fn affine(&self)->glam::Affine2{ + glam::Affine2::from_translation( + glam::vec2(self.offset_studs_u/self.studs_per_tile_u,self.offset_studs_v/self.studs_per_tile_v) + ) + *glam::Affine2::from_scale( + glam::vec2(self.size_u/self.studs_per_tile_u,self.size_v/self.studs_per_tile_v) + ) + } + pub fn set_size(&mut self,size_u:f32,size_v:f32){ + self.size_u=size_u; + self.size_v=size_v; } } -#[derive(Clone,PartialEq)] -struct RobloxFaceTextureDescription{ - render:RenderConfigId, - color:glam::Vec4, - transform:RobloxTextureTransform, +impl core::hash::Hash for RobloxTextureTransform{ + fn hash<H:core::hash::Hasher>(&self,state:&mut H){ + self.to_bits().hash(state); + } } -impl std::cmp::Eq for RobloxFaceTextureDescription{}//???? -impl std::hash::Hash for RobloxFaceTextureDescription{ - fn hash<H:std::hash::Hasher>(&self,state:&mut H){ - self.render.hash(state); - self.transform.hash(state); - for &el in self.color.as_ref().iter(){ - el.to_ne_bytes().hash(state); - } - } +#[derive(Clone,Copy,Hash,Eq,PartialEq)] +pub struct RobloxFaceTextureDescriptionBits{ + render:RenderConfigId, + color:[u32;4], + transform:RobloxTextureTransformBits, +} +#[derive(Clone,Copy)] +pub struct RobloxFaceTextureDescription{ + pub render:RenderConfigId, + pub color:glam::Vec4, + pub transform:RobloxTextureTransform, +} +impl core::cmp::PartialEq for RobloxFaceTextureDescription{ + fn eq(&self,other:&Self)->bool{ + self.to_bits().eq(&other.to_bits()) + } +} +impl core::cmp::Eq for RobloxFaceTextureDescription{} +impl core::hash::Hash for RobloxFaceTextureDescription{ + fn hash<H:core::hash::Hasher>(&self,state:&mut H){ + self.to_bits().hash(state); + } } impl RobloxFaceTextureDescription{ - fn to_face_description(&self)->primitives::FaceDescription{ + pub fn to_bits(self)->RobloxFaceTextureDescriptionBits{ + RobloxFaceTextureDescriptionBits{ + render:self.render, + color:self.color.to_array().map(f32::to_bits), + transform:self.transform.to_bits(), + } + } + pub fn to_face_description(&self)->primitives::FaceDescription{ primitives::FaceDescription{ render:self.render, - transform:glam::Affine2::from_translation( - glam::vec2(self.transform.offset_u,self.transform.offset_v) - ) - *glam::Affine2::from_scale( - glam::vec2(self.transform.scale_u,self.transform.scale_v) - ), + transform:self.transform.affine(), color:self.color, } } } -type RobloxPartDescription=[Option<RobloxFaceTextureDescription>;6]; +pub type RobloxPartDescription=[Option<RobloxFaceTextureDescription>;6]; type RobloxWedgeDescription=[Option<RobloxFaceTextureDescription>;5]; type RobloxCornerWedgeDescription=[Option<RobloxFaceTextureDescription>;5]; #[derive(Clone,Eq,Hash,PartialEq)] @@ -438,10 +483,10 @@ fn get_texture_description<'a>( let (roblox_texture_color,roblox_texture_transform)=if decal.class=="Texture"{ //generate tranform if let ( - Some(rbx_dom_weak::types::Variant::Float32(ox)), - Some(rbx_dom_weak::types::Variant::Float32(oy)), - Some(rbx_dom_weak::types::Variant::Float32(sx)), - Some(rbx_dom_weak::types::Variant::Float32(sy)), + Some(&rbx_dom_weak::types::Variant::Float32(offset_studs_u)), + Some(&rbx_dom_weak::types::Variant::Float32(offset_studs_v)), + Some(&rbx_dom_weak::types::Variant::Float32(studs_per_tile_u)), + Some(&rbx_dom_weak::types::Variant::Float32(studs_per_tile_v)), ) = ( decal.properties.get("OffsetStudsU"), decal.properties.get("OffsetStudsV"), @@ -461,15 +506,19 @@ fn get_texture_description<'a>( ( glam::vec4(decal_color3.r,decal_color3.g,decal_color3.b,1.0-*decal_transparency), RobloxTextureTransform{ - offset_u:*ox/(*sx),offset_v:*oy/(*sy), - scale_u:size_u/(*sx),scale_v:size_v/(*sy), + offset_studs_u, + offset_studs_v, + studs_per_tile_u, + studs_per_tile_v, + size_u, + size_v, } ) }else{ - (glam::Vec4::ONE,RobloxTextureTransform::default()) + (glam::Vec4::ONE,RobloxTextureTransform::identity()) } }else{ - (glam::Vec4::ONE,RobloxTextureTransform::default()) + (glam::Vec4::ONE,RobloxTextureTransform::identity()) }; part_texture_description[normal_id as usize]=Some(RobloxFaceTextureDescription{ render:render_id, @@ -729,7 +778,7 @@ pub fn convert<'a>( physics_data=data.as_ref(); } let part_texture_description=get_texture_description(&mut temp_objects,render_config_deferred_loader,dom,object,size); - let mesh_index=MeshIndex::union(content,mesh_data,physics_data,size); + let mesh_index=MeshIndex::union(content,mesh_data,physics_data,size,part_texture_description.clone()); let mesh_id=mesh_deferred_loader.acquire_mesh_id(mesh_index); (MeshAvailability::DeferredUnion(part_texture_description),mesh_id) }, diff --git a/lib/rbx_loader/src/union.rs b/lib/rbx_loader/src/union.rs index 331806e..5e7cf46 100644 --- a/lib/rbx_loader/src/union.rs +++ b/lib/rbx_loader/src/union.rs @@ -52,7 +52,12 @@ impl MeshDataNormalChecker{ } impl std::error::Error for Error{} -pub fn convert(roblox_physics_data:&[u8],roblox_mesh_data:&[u8],size:glam::Vec3)->Result<model::Mesh,Error>{ +pub fn convert( + roblox_physics_data:&[u8], + roblox_mesh_data:&[u8], + size:glam::Vec3, + part_texture_description:crate::rbx::RobloxPartDescription, +)->Result<model::Mesh,Error>{ const NORMAL_FACES:usize=6; let mut polygon_groups_normal_id=vec![Vec::new();NORMAL_FACES]; @@ -60,6 +65,12 @@ pub fn convert(roblox_physics_data:&[u8],roblox_mesh_data:&[u8],size:glam::Vec3) let mut mb=strafesnet_common::model::MeshBuilder::new(); // graphics let graphics_groups=if !roblox_mesh_data.is_empty(){ + // create per-face texture coordinate affine transforms + let cube_face_description=part_texture_description.map(|opt|opt.map(|mut t|{ + t.transform.set_size(1.0,1.0); + t.to_face_description() + })); + let mesh_data=rbx_mesh::read_mesh_data_versioned( std::io::Cursor::new(roblox_mesh_data) ).map_err(Error::RobloxMeshData)?; @@ -79,8 +90,22 @@ pub fn convert(roblox_physics_data:&[u8],roblox_mesh_data:&[u8],size:glam::Vec3) 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))); + let tex_coord=glam::Vec2::from_array(vertex.tex); + let maybe_face_description=&cube_face_description[vertex.normal_id as usize-1]; + let (tex,color)=match maybe_face_description{ + Some(face_description)=>{ + // transform texture coordinates and set decal color + let tex=mb.acquire_tex_id(face_description.transform.transform_point2(tex_coord)); + let color=mb.acquire_color_id(face_description.color); + (tex,color) + }, + None=>{ + // texture coordinates don't matter and pass through mesh vertex color + let tex=mb.acquire_tex_id(tex_coord); + let color=mb.acquire_color_id(glam::Vec4::from_array(vertex.color.map(|f|f as f32/255.0f32))); + (tex,color) + }, + }; 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(){