| Crates.io | rbx_mesh |
| lib.rs | rbx_mesh |
| version | 0.5.0 |
| created_at | 2024-03-13 20:48:21.811326+00 |
| updated_at | 2025-07-24 02:21:38.725754+00 |
| description | Rust parser for Roblox mesh files. |
| homepage | |
| repository | https://github.com/krakow10/rbx_mesh |
| max_upload_size | |
| id | 1172363 |
| size | 56,265 |
rbx_mesh is a collection of deserializers for the different mesh versions in Roblox, and a function to detect the version and invoke the correct deserializer. The meshes are decoded into a structure that reflects the on-disk format of the particular mesh version, rather than being translated into a catch-all structure.
Print the mesh vertices for any mesh version / vertex size
use rbx_mesh::{read_versioned,mesh::{Mesh,SizeOfVertex2}};
let file=std::fs::read("torso.mesh")?;
let versioned_mesh=read_versioned(std::io::Cursor::new(file))?;
match versioned_mesh{
Mesh::V1(mesh)=>println!("{:?}",mesh.vertices),
Mesh::V2(mesh)=>{
match mesh.header.sizeof_vertex{
SizeOfVertex2::Full=>println!("{:?}",mesh.vertices),
SizeOfVertex2::Truncated=>println!("{:?}",mesh.vertices_truncated),
}
},
Mesh::V3(mesh)=>{
match mesh.header.sizeof_vertex{
SizeOfVertex2::Full=>println!("{:?}",mesh.vertices),
SizeOfVertex2::Truncated=>println!("{:?}",mesh.vertices_truncated),
}
},
Mesh::V4(mesh)=>println!("{:?}",mesh.vertices),
Mesh::V5(mesh)=>println!("{:?}",mesh.vertices),
}
// PART 1: MeshData
use rbx_mesh::read_mesh_data_versioned;
use rbx_mesh::mesh_data::{MeshData,CSGMDL};
// this data is extracted from the "MeshData" property of UnionOperation
// the data is not usually contained in the roblox file itself
// but is sourced from the associated "AssetId" of the UnionOperation
let mesh_file=std::fs::read("4500696697_4.meshdata")?;
let mesh_data=read_mesh_data_versioned(std::io::Cursor::new(mesh_file))?;
// print mesh vertices
match mesh_data{
MeshData::CSGK(_)=>(),
MeshData::CSGMDL(CSGMDL::V2(mesh_data2))=>println!("{:?}",mesh_data2.mesh.vertices),
MeshData::CSGMDL(CSGMDL::V4(mesh_data4))=>println!("{:?}",mesh_data4.mesh.vertices),
MeshData::CSGMDL(CSGMDL::V5(mesh_data5))=>{
// CSGMDL::V5
let vertices:Vec<_>=mesh_data5
.faces
.indices
.chunks_exact(3)
.map(|face_vertex_indices|{
// construct face triangle from indices
[
mesh_data5.positions[face_vertex_indices[0] as usize],
mesh_data5.positions[face_vertex_indices[1] as usize],
mesh_data5.positions[face_vertex_indices[2] as usize],
]
})
.collect();
println!("{:?}",vertices);
},
}
// PART 2: PhysicsData
use rbx_mesh::read_physics_data_versioned;
use rbx_mesh::physics_data::{PhysicsData,CSGPHS};
// this data is extracted from the "PhysicsData" property of UnionOperation
let phys_file=std::fs::read("CSGPHS_3.data")?;
let physics_data=read_physics_data_versioned(std::io::Cursor::new(phys_file))?;
match physics_data{
// the most common format (99% of the 100000 unions in my testing)
PhysicsData::CSGPHS(CSGPHS::V3(meshes)),
|PhysicsData::CSGPHS(CSGPHS::V5(meshes))=>println!("CSGPHS V3 or V5"),
// new mesh format (2025)
PhysicsData::CSGPHS(CSGPHS::V7(meshes))=>println!("CSGPHS V7"),
// Only one occurence in my data set.
// Who writes a uuid as ascii hex in a binary format!?
PhysicsData::CSGK(csgk)=>println!("CSGK"),
// These formats have zero occurences in my dataset
// But they are documented at
// https://devforum.roblox.com/t/some-info-on-sharedstrings-for-custom-collision-data-meshparts-unions-etc/294588
PhysicsData::CSGPHS(CSGPHS::Block)=>println!("CSGPHS Block"),
PhysicsData::CSGPHS(CSGPHS::V6(csgphs))=>println!("CSGPHS V6"),
}