forked from StrafesNET/strafe-project
191 lines
7.9 KiB
Rust
191 lines
7.9 KiB
Rust
use crate::rbx::RobloxPartDescription;
|
|
use crate::primitives::{CUBE_DEFAULT_VERTICES,CUBE_DEFAULT_POLYS};
|
|
|
|
use rbx_mesh::mesh_data::{VertexId as MeshDataVertexId,NormalId2 as MeshDataNormalId2};
|
|
use rbx_mesh::physics_data::VertexId as PhysicsDataVertexId;
|
|
use strafesnet_common::model::{self,IndexedVertex,PolygonGroup,PolygonGroupId,PolygonList,RenderConfigId};
|
|
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),
|
|
RobloxMeshData(rbx_mesh::mesh_data::Error),
|
|
}
|
|
impl std::fmt::Display for Error{
|
|
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
|
write!(f,"{self:?}")
|
|
}
|
|
}
|
|
|
|
// 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],
|
|
size:glam::Vec3,
|
|
RobloxPartDescription(part_texture_description):RobloxPartDescription,
|
|
)->Result<model::Mesh,Error>{
|
|
const NORMAL_FACES:usize=6;
|
|
let mut polygon_groups_normal_id:[_;NORMAL_FACES]=[vec![],vec![],vec![],vec![],vec![],vec![]];
|
|
|
|
// build graphics and physics meshes
|
|
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)?;
|
|
let graphics_mesh=match mesh_data{
|
|
rbx_mesh::mesh_data::MeshData::CSGK(_)=>return Err(Error::Block),
|
|
rbx_mesh::mesh_data::MeshData::CSGMDL(rbx_mesh::mesh_data::CSGMDL::CSGMDL2(mesh_data2))=>mesh_data2.mesh,
|
|
rbx_mesh::mesh_data::MeshData::CSGMDL(rbx_mesh::mesh_data::CSGMDL::CSGMDL4(mesh_data4))=>mesh_data4.mesh,
|
|
};
|
|
for [MeshDataVertexId(vertex_id0),MeshDataVertexId(vertex_id1),MeshDataVertexId(vertex_id2)] in graphics_mesh.faces{
|
|
let face=[
|
|
graphics_mesh.vertices.get(vertex_id0 as usize).ok_or(Error::MissingVertexId(vertex_id0))?,
|
|
graphics_mesh.vertices.get(vertex_id1 as usize).ok_or(Error::MissingVertexId(vertex_id1))?,
|
|
graphics_mesh.vertices.get(vertex_id2 as usize).ok_or(Error::MissingVertexId(vertex_id2))?,
|
|
];
|
|
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_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(){
|
|
polygon_groups_normal_id[normal_id as usize-1].push(face);
|
|
}else{
|
|
panic!("Empty face!");
|
|
}
|
|
}
|
|
(0..NORMAL_FACES).map(|polygon_group_id|{
|
|
model::IndexedGraphicsGroup{
|
|
render:cube_face_description[polygon_group_id].as_ref().map_or(RenderConfigId::new(0),|face_description|face_description.render),
|
|
groups:vec![PolygonGroupId::new(polygon_group_id as u32)]
|
|
}
|
|
}).collect()
|
|
}else{
|
|
Vec::new()
|
|
};
|
|
|
|
//physics
|
|
let polygon_groups_normal_it=polygon_groups_normal_id.into_iter().map(|faces|
|
|
// graphics polygon groups (to be rendered)
|
|
Ok(PolygonGroup::PolygonList(PolygonList::new(faces)))
|
|
);
|
|
let polygon_groups:Vec<PolygonGroup>=if !roblox_physics_data.is_empty(){
|
|
let physics_data=rbx_mesh::read_physics_data_versioned(
|
|
std::io::Cursor::new(roblox_physics_data)
|
|
).map_err(Error::RobloxPhysicsData)?;
|
|
let physics_convex_meshes=match physics_data{
|
|
rbx_mesh::physics_data::PhysicsData::CSGK(_)
|
|
// have not seen this format in practice
|
|
|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::Meshes(meshes))
|
|
=>meshes.meshes,
|
|
rbx_mesh::physics_data::PhysicsData::CSGPHS(rbx_mesh::physics_data::CSGPHS::PhysicsInfoMesh(pim))
|
|
=>vec![pim.mesh],
|
|
};
|
|
let physics_convex_meshes_it=physics_convex_meshes.into_iter().map(|mesh|{
|
|
// this can be factored out of the loop but I am lazy
|
|
let color=mb.acquire_color_id(glam::Vec4::ONE);
|
|
let tex=mb.acquire_tex_id(glam::Vec2::ZERO);
|
|
// physics polygon groups (to do physics)
|
|
Ok(PolygonGroup::PolygonList(PolygonList::new(mesh.faces.into_iter().map(|[PhysicsDataVertexId(vertex_id0),PhysicsDataVertexId(vertex_id1),PhysicsDataVertexId(vertex_id2)]|{
|
|
let face=[
|
|
mesh.vertices.get(vertex_id0 as usize).ok_or(Error::MissingVertexId(vertex_id0))?,
|
|
mesh.vertices.get(vertex_id1 as usize).ok_or(Error::MissingVertexId(vertex_id1))?,
|
|
mesh.vertices.get(vertex_id2 as usize).ok_or(Error::MissingVertexId(vertex_id2))?,
|
|
].map(|v|glam::Vec3::from_slice(v)/size);
|
|
let vertex_norm=(face[1]-face[0])
|
|
.cross(face[2]-face[0]);
|
|
let normal=mb.acquire_normal_id(vec3::try_from_f32_array(vertex_norm.to_array()).map_err(Error::Planar64Vec3)?);
|
|
face.into_iter().map(|vertex_pos|{
|
|
let pos=mb.acquire_pos_id(vec3::try_from_f32_array(vertex_pos.to_array()).map_err(Error::Planar64Vec3)?);
|
|
Ok(mb.acquire_vertex_id(IndexedVertex{pos,tex,normal,color}))
|
|
}).collect()
|
|
}).collect::<Result<_,_>>()?)))
|
|
});
|
|
polygon_groups_normal_it.chain(physics_convex_meshes_it).collect::<Result<_,_>>()?
|
|
}else{
|
|
// generate a unit cube as default physics
|
|
let pos_list=CUBE_DEFAULT_VERTICES.map(|pos|mb.acquire_pos_id(pos>>1));
|
|
let tex=mb.acquire_tex_id(glam::Vec2::ZERO);
|
|
let normal=mb.acquire_normal_id(vec3::ZERO);
|
|
let color=mb.acquire_color_id(glam::Vec4::ONE);
|
|
let polygon_group=PolygonGroup::PolygonList(PolygonList::new(CUBE_DEFAULT_POLYS.map(|poly|poly.map(|[pos_id,_]|
|
|
mb.acquire_vertex_id(IndexedVertex{pos:pos_list[pos_id as usize],tex,normal,color})
|
|
).to_vec()).to_vec()));
|
|
polygon_groups_normal_it.chain([Ok(polygon_group)]).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(
|
|
polygon_groups,
|
|
graphics_groups,
|
|
physics_groups,
|
|
))
|
|
}
|