Compare commits

...

21 Commits

Author SHA1 Message Date
4765a14641 lowest lod number is highest quality 2024-03-13 11:45:01 -07:00
0c5f47d1af calculate mesh size and plumb it through to compose with the transform 2024-03-13 11:44:45 -07:00
f4f9bb47a7 update common 2024-03-13 11:43:51 -07:00
7e0d402012 scale vertex color properly 2024-03-13 10:48:00 -07:00
28a47e2daf arrays end at #-1 2024-03-13 10:42:21 -07:00
5faac6b2b3 completely ignore meshes that fail to load 2024-03-12 22:35:47 -07:00
e4cd556f94 update deps 2024-03-12 22:28:18 -07:00
02c678359a oopsie 2024-03-12 22:27:52 -07:00
fe1313c70f holy smokes 2024-03-12 21:51:28 -07:00
1d20fb2c23 make ? visible (not offscreen LOL) 2024-03-12 21:18:38 -07:00
117546deeb complete mesh converter 2024-03-12 21:11:04 -07:00
c1062449b2 surely it needs to be a mutable reference to avoid copying the function 2024-03-12 20:40:25 -07:00
d50c15c187 implement Error trait 2024-03-12 20:40:02 -07:00
1b196dc831 hash by rbx_mesh VertexId2 2024-03-12 20:39:50 -07:00
9b131ee857 some uhh mesh stuff 2024-03-12 20:31:36 -07:00
3784f5aacf on a better track 2024-03-12 20:31:30 -07:00
b14d5a7e56 not quite right 2024-03-12 18:25:18 -07:00
f6d5972743 getting there 2024-03-12 18:08:44 -07:00
7dfb421016 insanity 2024-03-12 17:12:32 -07:00
118ce9b239 wip 2024-03-12 16:15:34 -07:00
a103a3c034 add rbx_mesh dep 2024-03-12 16:15:34 -07:00
5 changed files with 527 additions and 63 deletions

82
Cargo.lock generated
View File

@ -11,6 +11,12 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "array-init"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc"
[[package]] [[package]]
name = "arrayref" name = "arrayref"
version = "0.3.7" version = "0.3.7"
@ -35,6 +41,30 @@ version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]]
name = "binrw"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "173901312e9850391d4d7c1318c4e099fdc037d61870fca427429830efdb4e5f"
dependencies = [
"array-init",
"binrw_derive",
"bytemuck",
]
[[package]]
name = "binrw_derive"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb515fdd6f8d3a357c8e19b8ec59ef53880807864329b1cb1cba5c53bf76557e"
dependencies = [
"either",
"owo-colors",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.3.2" version = "1.3.2"
@ -60,6 +90,12 @@ dependencies = [
"constant_time_eq", "constant_time_eq",
] ]
[[package]]
name = "bytemuck"
version = "1.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f"
[[package]] [[package]]
name = "byteorder" name = "byteorder"
version = "1.5.0" version = "1.5.0"
@ -84,6 +120,12 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2"
[[package]]
name = "either"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.12" version = "0.2.12"
@ -108,7 +150,7 @@ source = "git+https://git.itzana.me/Quaternions/id?rev=1f710976cc786c8853dab73d6
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.52",
] ]
[[package]] [[package]]
@ -131,7 +173,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"regex", "regex",
"syn", "syn 2.0.52",
] ]
[[package]] [[package]]
@ -193,6 +235,12 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "owo-colors"
version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
[[package]] [[package]]
name = "paste" name = "paste"
version = "1.0.14" version = "1.0.14"
@ -230,7 +278,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd" checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd"
dependencies = [ dependencies = [
"quote", "quote",
"syn", "syn 2.0.52",
] ]
[[package]] [[package]]
@ -295,6 +343,15 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "rbx_mesh"
version = "0.1.0"
source = "git+https://github.com/krakow10/rbx_mesh?rev=0e1c66d9acc7e1f2c187dd0899a9434d93ae0453#0e1c66d9acc7e1f2c187dd0899a9434d93ae0453"
dependencies = [
"binrw",
"lazy-regex",
]
[[package]] [[package]]
name = "rbx_reflection" name = "rbx_reflection"
version = "4.5.0" version = "4.5.0"
@ -411,13 +468,13 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.52",
] ]
[[package]] [[package]]
name = "strafesnet_common" name = "strafesnet_common"
version = "0.1.0" version = "0.1.0"
source = "git+https://git.itzana.me/StrafesNET/common?rev=ef922135e6d19f296d8e4d90df0d0d71fa3412e2#ef922135e6d19f296d8e4d90df0d0d71fa3412e2" source = "git+https://git.itzana.me/StrafesNET/common?rev=a9f3e61f2bb1074025b6cb466a5e3f2abc988c34#a9f3e61f2bb1074025b6cb466a5e3f2abc988c34"
dependencies = [ dependencies = [
"bitflags 2.4.2", "bitflags 2.4.2",
"glam", "glam",
@ -428,15 +485,28 @@ dependencies = [
name = "strafesnet_rbx_loader" name = "strafesnet_rbx_loader"
version = "0.2.0" version = "0.2.0"
dependencies = [ dependencies = [
"bytemuck",
"glam", "glam",
"lazy-regex", "lazy-regex",
"rbx_binary", "rbx_binary",
"rbx_dom_weak", "rbx_dom_weak",
"rbx_mesh",
"rbx_reflection_database", "rbx_reflection_database",
"rbx_xml", "rbx_xml",
"strafesnet_common", "strafesnet_common",
] ]
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.52" version = "2.0.52"
@ -465,7 +535,7 @@ checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.52",
] ]
[[package]] [[package]]

View File

@ -12,4 +12,6 @@ rbx_binary = { git = "https://github.com/krakow10/rbx-dom", rev = "841643178680c
rbx_dom_weak = { git = "https://github.com/krakow10/rbx-dom", rev = "841643178680c9f3143422778f9c52f949b222b9" } rbx_dom_weak = { git = "https://github.com/krakow10/rbx-dom", rev = "841643178680c9f3143422778f9c52f949b222b9" }
rbx_reflection_database = { git = "https://github.com/krakow10/rbx-dom", rev = "841643178680c9f3143422778f9c52f949b222b9" } rbx_reflection_database = { git = "https://github.com/krakow10/rbx-dom", rev = "841643178680c9f3143422778f9c52f949b222b9" }
rbx_xml = { git = "https://github.com/krakow10/rbx-dom", rev = "841643178680c9f3143422778f9c52f949b222b9" } rbx_xml = { git = "https://github.com/krakow10/rbx-dom", rev = "841643178680c9f3143422778f9c52f949b222b9" }
strafesnet_common = { git = "https://git.itzana.me/StrafesNET/common", rev = "ef922135e6d19f296d8e4d90df0d0d71fa3412e2" } rbx_mesh = { git = "https://github.com/krakow10/rbx_mesh", rev = "0e1c66d9acc7e1f2c187dd0899a9434d93ae0453" }
strafesnet_common = { git = "https://git.itzana.me/StrafesNET/common", rev = "a9f3e61f2bb1074025b6cb466a5e3f2abc988c34" }
bytemuck = "1.14.3"

View File

@ -1,8 +1,21 @@
use std::io::Read; use std::io::Read;
mod rbx; mod rbx;
mod mesh;
mod primitives; mod primitives;
pub mod data{
pub struct RobloxMeshBytes(Vec<u8>);
impl RobloxMeshBytes{
pub fn new(bytes:Vec<u8>)->Self{
Self(bytes)
}
pub(crate) fn cursor(self)->std::io::Cursor<Vec<u8>>{
std::io::Cursor::new(self.0)
}
}
}
pub struct Dom(rbx_dom_weak::WeakDom); pub struct Dom(rbx_dom_weak::WeakDom);
#[derive(Debug)] #[derive(Debug)]
@ -31,6 +44,14 @@ pub fn read<R:Read>(input:R)->Result<Dom,ReadError>{
//ConvertError //ConvertError
pub fn convert<F:FnMut(Option<&str>)->strafesnet_common::model::RenderConfigId>(dom:&Dom,acquire_render_config_id:F)->rbx::PartialMap1{ pub fn convert<AcquireRenderConfigId,AcquireMeshId>(
rbx::convert(&dom.0,acquire_render_config_id) dom:&Dom,
acquire_render_config_id:AcquireRenderConfigId,
acquire_mesh_id:AcquireMeshId
)->rbx::PartialMap1
where
AcquireRenderConfigId:FnMut(Option<&str>)->strafesnet_common::model::RenderConfigId,
AcquireMeshId:FnMut(&str)->strafesnet_common::model::MeshId,
{
rbx::convert(&dom.0,acquire_render_config_id,acquire_mesh_id)
} }

210
src/mesh.rs Normal file
View File

@ -0,0 +1,210 @@
use std::collections::HashMap;
use rbx_mesh::mesh::{Vertex2, Vertex2Truncated};
use strafesnet_common::{integer::Planar64Vec3,model::{self, ColorId, IndexedVertex, NormalId, PolygonGroup, PolygonList, PositionId, TextureCoordinateId, VertexId}};
#[derive(Debug)]
pub enum Error{
Planar64Vec3(strafesnet_common::integer::Planar64TryFromFloatError),
RbxMesh(rbx_mesh::mesh::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{}
fn ingest_vertices2<
AcquirePosId,
AcquireTexId,
AcquireNormalId,
AcquireColorId,
AcquireVertexId,
>(
vertices:Vec<Vertex2>,
acquire_pos_id:&mut AcquirePosId,
acquire_tex_id:&mut AcquireTexId,
acquire_normal_id:&mut AcquireNormalId,
acquire_color_id:&mut AcquireColorId,
acquire_vertex_id:&mut AcquireVertexId,
)->Result<HashMap<rbx_mesh::mesh::VertexId2,VertexId>,Error>
where
AcquirePosId:FnMut([f32;3])->Result<PositionId,Error>,
AcquireTexId:FnMut([f32;2])->TextureCoordinateId,
AcquireNormalId:FnMut([f32;3])->Result<NormalId,Error>,
AcquireColorId:FnMut([f32;4])->ColorId,
AcquireVertexId:FnMut(IndexedVertex)->VertexId,
{
//this monster is collecting a map of old_vertices_index -> unique_vertices_index
//while also doing the inserting unique entries into lists simultaneously
Ok(vertices.into_iter().enumerate().map(|(vertex_id,vertex)|Ok((
rbx_mesh::mesh::VertexId2(vertex_id as u32),
acquire_vertex_id(IndexedVertex{
pos:acquire_pos_id(vertex.pos)?,
tex:acquire_tex_id(vertex.tex),
normal:acquire_normal_id(vertex.norm)?,
color:acquire_color_id(vertex.color.map(|f|f as f32/255.0f32))
}),
))).collect::<Result<_,_>>()?)
}
fn ingest_vertices_truncated2<
AcquirePosId,
AcquireTexId,
AcquireNormalId,
AcquireVertexId,
>(
vertices:Vec<Vertex2Truncated>,
acquire_pos_id:&mut AcquirePosId,
acquire_tex_id:&mut AcquireTexId,
acquire_normal_id:&mut AcquireNormalId,
static_color_id:ColorId,//pick one color and fill everything with it
acquire_vertex_id:&mut AcquireVertexId,
)->Result<HashMap<rbx_mesh::mesh::VertexId2,VertexId>,Error>
where
AcquirePosId:FnMut([f32;3])->Result<PositionId,Error>,
AcquireTexId:FnMut([f32;2])->TextureCoordinateId,
AcquireNormalId:FnMut([f32;3])->Result<NormalId,Error>,
AcquireVertexId:FnMut(IndexedVertex)->VertexId,
{
//this monster is collecting a map of old_vertices_index -> unique_vertices_index
//while also doing the inserting unique entries into lists simultaneously
Ok(vertices.into_iter().enumerate().map(|(vertex_id,vertex)|Ok((
rbx_mesh::mesh::VertexId2(vertex_id as u32),
acquire_vertex_id(IndexedVertex{
pos:acquire_pos_id(vertex.pos)?,
tex:acquire_tex_id(vertex.tex),
normal:acquire_normal_id(vertex.norm)?,
color:static_color_id
}),
))).collect::<Result<_,_>>()?)
}
fn ingest_faces2_lods3(
polygon_groups:&mut Vec<PolygonGroup>,
vertex_id_map:&HashMap<rbx_mesh::mesh::VertexId2,VertexId>,
faces:&Vec<rbx_mesh::mesh::Face2>,
lods:&Vec<rbx_mesh::mesh::Lod3>
){
//faces have to be split into polygon groups based on lod
polygon_groups.extend(lods.windows(2).map(|lod_pair|
PolygonGroup::PolygonList(PolygonList::new(faces[lod_pair[0].0 as usize..lod_pair[1].0 as usize].iter().map(|face|
vec![vertex_id_map[&face.0],vertex_id_map[&face.1],vertex_id_map[&face.2]]
).collect()))
))
}
pub fn convert(roblox_mesh_bytes:crate::data::RobloxMeshBytes)->Result<model::Mesh,Error>{
//generate that mesh boi
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 polygon_groups=Vec::new();
let mut acquire_pos_id=|pos|{
let p=Planar64Vec3::try_from(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=Planar64Vec3::try_from(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)
};
match rbx_mesh::read_versioned(roblox_mesh_bytes.cursor()).map_err(Error::RbxMesh)?{
rbx_mesh::mesh::VersionedMesh::Version1(mesh)=>{
let color_id=acquire_color_id([1.0f32;4]);
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(mesh.vertices.chunks_exact(3).map(|trip|{
let mut ingest_vertex1=|vertex:&rbx_mesh::mesh::Vertex1|Ok(acquire_vertex_id(IndexedVertex{
pos:acquire_pos_id(vertex.pos)?,
tex:acquire_tex_id([vertex.tex[0],vertex.tex[1]]),
normal:acquire_normal_id(vertex.norm)?,
color:color_id,
}));
Ok(vec![ingest_vertex1(&trip[0])?,ingest_vertex1(&trip[1])?,ingest_vertex1(&trip[2])?])
}).collect::<Result<_,_>>()?)));
},
rbx_mesh::mesh::VersionedMesh::Version2(mesh)=>{
let vertex_id_map=match mesh.header.sizeof_vertex{
rbx_mesh::mesh::SizeOfVertex2::Truncated=>{
//pick white and make all the vertices white
let color_id=acquire_color_id([1.0f32;4]);
ingest_vertices_truncated2(mesh.vertices_truncated,&mut acquire_pos_id,&mut acquire_tex_id,&mut acquire_normal_id,color_id,&mut acquire_vertex_id)
},
rbx_mesh::mesh::SizeOfVertex2::Full=>ingest_vertices2(mesh.vertices,&mut acquire_pos_id,&mut acquire_tex_id,&mut acquire_normal_id,&mut acquire_color_id,&mut acquire_vertex_id),
}?;
//one big happy group for all the faces
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(mesh.faces.into_iter().map(|face|
vec![vertex_id_map[&face.0],vertex_id_map[&face.1],vertex_id_map[&face.2]]
).collect())));
},
rbx_mesh::mesh::VersionedMesh::Version3(mesh)=>{
let vertex_id_map=match mesh.header.sizeof_vertex{
rbx_mesh::mesh::SizeOfVertex2::Truncated=>{
let color_id=acquire_color_id([1.0f32;4]);
ingest_vertices_truncated2(mesh.vertices_truncated,&mut acquire_pos_id,&mut acquire_tex_id,&mut acquire_normal_id,color_id,&mut acquire_vertex_id)
},
rbx_mesh::mesh::SizeOfVertex2::Full=>ingest_vertices2(mesh.vertices,&mut acquire_pos_id,&mut acquire_tex_id,&mut acquire_normal_id,&mut acquire_color_id,&mut acquire_vertex_id),
}?;
ingest_faces2_lods3(&mut polygon_groups,&vertex_id_map,&mesh.faces,&mesh.lods);
},
rbx_mesh::mesh::VersionedMesh::Version4(mesh)=>{
let vertex_id_map=ingest_vertices2(
mesh.vertices,&mut acquire_pos_id,&mut acquire_tex_id,&mut acquire_normal_id,&mut acquire_color_id,&mut acquire_vertex_id
)?;
ingest_faces2_lods3(&mut polygon_groups,&vertex_id_map,&mesh.faces,&mesh.lods);
},
rbx_mesh::mesh::VersionedMesh::Version5(mesh)=>{
let vertex_id_map=ingest_vertices2(
mesh.vertices,&mut acquire_pos_id,&mut acquire_tex_id,&mut acquire_normal_id,&mut acquire_color_id,&mut acquire_vertex_id
)?;
ingest_faces2_lods3(&mut polygon_groups,&vertex_id_map,&mesh.faces,&mesh.lods);
},
}
Ok(model::Mesh{
unique_pos,
unique_normal,
unique_tex,
unique_color,
unique_vertices,
polygon_groups,
//these should probably be moved to the model...
graphics_groups:Vec::new(),
physics_groups:Vec::new(),
})
}

View File

@ -134,8 +134,7 @@ impl ModesBuilder{
self.stage_updates.push((mode_id,stage_id,stage_update)); self.stage_updates.push((mode_id,stage_id,stage_update));
} }
} }
fn get_attributes(object:&rbx_dom_weak::Instance,can_collide:bool,velocity:Planar64Vec3,model_id:model::ModelId,modes_builder:&mut ModesBuilder,wormhole_in_model_to_id:&mut HashMap<model::ModelId,u32>,wormhole_id_to_out_model:&mut HashMap<u32,model::ModelId>)->attr::CollisionAttributes{ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:model::ModelId,modes_builder:&mut ModesBuilder,wormhole_in_model_to_id:&mut HashMap<model::ModelId,u32>,wormhole_id_to_out_model:&mut HashMap<u32,model::ModelId>)->attr::CollisionAttributes{
let name=object.name.as_str();
let mut general=attr::GeneralAttributes::default(); let mut general=attr::GeneralAttributes::default();
let mut intersecting=attr::IntersectingAttributes::default(); let mut intersecting=attr::IntersectingAttributes::default();
let mut contacting=attr::ContactingAttributes::default(); let mut contacting=attr::ContactingAttributes::default();
@ -404,24 +403,49 @@ enum RobloxBasePartDescription{
Wedge(RobloxWedgeDescription), Wedge(RobloxWedgeDescription),
CornerWedge(RobloxCornerWedgeDescription), CornerWedge(RobloxCornerWedgeDescription),
} }
enum Shape{
Primitive(primitives::Primitives),
MeshPart,
}
enum MeshAvailability{
Immediate,
Deferred(RenderConfigId),
}
struct DeferredModelDeferredAttributes{
render:RenderConfigId,
model:ModelDeferredAttributes,
}
struct ModelDeferredAttributes{
mesh:model::MeshId,
deferred_attributes:GetAttributesArgs,
color:model::Color4,//transparency is in here
transform:Planar64Affine3,
}
struct ModelOwnedAttributes{ struct ModelOwnedAttributes{
mesh:model::MeshId, mesh:model::MeshId,
attributes:attr::CollisionAttributes, attributes:attr::CollisionAttributes,
color:model::Color4,//transparency is in here color:model::Color4,//transparency is in here
transform:Planar64Affine3, transform:Planar64Affine3,
} }
pub fn convert<F:FnMut(Option<&str>)->model::RenderConfigId>(dom:&rbx_dom_weak::WeakDom,mut acquire_render_config_id:F)->PartialMap1{ struct GetAttributesArgs{
let mut modes_builder=ModesBuilder::default(); name:Box<str>,
can_collide:bool,
velocity:Planar64Vec3,
}
pub fn convert<AcquireRenderConfigId,AcquireMeshId>(
dom:&rbx_dom_weak::WeakDom,
mut acquire_render_config_id:AcquireRenderConfigId,
mut acquire_mesh_id:AcquireMeshId,
)->PartialMap1
where
AcquireRenderConfigId:FnMut(Option<&str>)->model::RenderConfigId,
AcquireMeshId:FnMut(&str)->model::MeshId,
{
let mut models1=Vec::new(); let mut deferred_models_deferred_attributes=Vec::new();
let mut meshes=Vec::new(); let mut primitive_models_deferred_attributes=Vec::new();
let mut indexed_model_id_from_description=HashMap::new(); let mut primitive_meshes=Vec::new();
let mut mesh_id_from_description=HashMap::new();
let mut unique_attributes=Vec::new();
let mut attributes_id_from_attributes=HashMap::new();
let mut wormhole_in_model_to_id=HashMap::new();
let mut wormhole_id_to_out_model=HashMap::new();
//just going to leave it like this for now instead of reworking the data structures for this whole thing //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); let textureless_render_group=acquire_render_config_id(None);
@ -462,31 +486,35 @@ pub fn convert<F:FnMut(Option<&str>)->model::RenderConfigId>(dom:&rbx_dom_weak::
} }
//at this point a new model is going to be generated for sure. //at this point a new model is going to be generated for sure.
let model_id=model::ModelId::new(models1.len() as u32); let model_id=model::ModelId::new(primitive_models_deferred_attributes.len() as u32);
//TODO: also detect "CylinderMesh" etc here //TODO: also detect "CylinderMesh" etc here
let shape=match object.class.as_str(){ let shape=match object.class.as_str(){
"Part"=>if let Some(rbx_dom_weak::types::Variant::Enum(shape))=object.properties.get("Shape"){ "Part"=>if let Some(rbx_dom_weak::types::Variant::Enum(shape))=object.properties.get("Shape"){
match shape.to_u32(){ Shape::Primitive(match shape.to_u32(){
0=>primitives::Primitives::Sphere, 0=>primitives::Primitives::Sphere,
1=>primitives::Primitives::Cube, 1=>primitives::Primitives::Cube,
2=>primitives::Primitives::Cylinder, 2=>primitives::Primitives::Cylinder,
3=>primitives::Primitives::Wedge, 3=>primitives::Primitives::Wedge,
4=>primitives::Primitives::CornerWedge, 4=>primitives::Primitives::CornerWedge,
other=>panic!("Funky roblox PartType={};",other), other=>panic!("Funky roblox PartType={};",other),
} })
}else{ }else{
panic!("Part has no Shape!"); panic!("Part has no Shape!");
}, },
"TrussPart"=>primitives::Primitives::Cube, "TrussPart"=>Shape::Primitive(primitives::Primitives::Cube),
"WedgePart"=>primitives::Primitives::Wedge, "WedgePart"=>Shape::Primitive(primitives::Primitives::Wedge),
"CornerWedgePart"=>primitives::Primitives::CornerWedge, "CornerWedgePart"=>Shape::Primitive(primitives::Primitives::CornerWedge),
"MeshPart"=>Shape::MeshPart,
_=>{ _=>{
println!("Unsupported BasePart ClassName={}; defaulting to cube",object.class); println!("Unsupported BasePart ClassName={}; defaulting to cube",object.class);
primitives::Primitives::Cube Shape::Primitive(primitives::Primitives::Cube)
} }
}; };
let (availability,mesh_id)=match shape{
Shape::Primitive(primitive_shape)=>{
//TODO: TAB TAB
//use the biggest one and cut it down later... //use the biggest one and cut it down later...
let mut part_texture_description:RobloxPartDescription=[None,None,None,None,None,None]; let mut part_texture_description:RobloxPartDescription=[None,None,None,None,None,None];
temp_objects.clear(); temp_objects.clear();
@ -549,7 +577,7 @@ pub fn convert<F:FnMut(Option<&str>)->model::RenderConfigId>(dom:&rbx_dom_weak::
transform:roblox_texture_transform, transform:roblox_texture_transform,
}); });
}else{ }else{
println!("NormalId={} unsupported for shape={:?}",normal_id,shape); println!("NormalId={} unsupported for shape={:?}",normal_id,primitive_shape);
} }
} }
} }
@ -563,7 +591,7 @@ pub fn convert<F:FnMut(Option<&str>)->model::RenderConfigId>(dom:&rbx_dom_weak::
f4,//Cube::Bottom f4,//Cube::Bottom
f5,//Cube::Front f5,//Cube::Front
]=part_texture_description; ]=part_texture_description;
let basepart_texture_description=match shape{ let basepart_description=match primitive_shape{
primitives::Primitives::Sphere=>RobloxBasePartDescription::Sphere([f0,f1,f2,f3,f4,f5]), primitives::Primitives::Sphere=>RobloxBasePartDescription::Sphere([f0,f1,f2,f3,f4,f5]),
primitives::Primitives::Cube=>RobloxBasePartDescription::Part([f0,f1,f2,f3,f4,f5]), primitives::Primitives::Cube=>RobloxBasePartDescription::Part([f0,f1,f2,f3,f4,f5]),
primitives::Primitives::Cylinder=>RobloxBasePartDescription::Cylinder([f0,f1,f2,f3,f4,f5]), primitives::Primitives::Cylinder=>RobloxBasePartDescription::Cylinder([f0,f1,f2,f3,f4,f5]),
@ -585,13 +613,13 @@ pub fn convert<F:FnMut(Option<&str>)->model::RenderConfigId>(dom:&rbx_dom_weak::
]), ]),
}; };
//make new model if unit cube has not been created before //make new model if unit cube has not been created before
let indexed_model_id=if let Some(&indexed_model_id)=indexed_model_id_from_description.get(&basepart_texture_description){ let mesh_id=if let Some(&mesh_id)=mesh_id_from_description.get(&basepart_description){
//push to existing texture model //push to existing texture model
indexed_model_id mesh_id
}else{ }else{
let indexed_model_id=model::MeshId::new(meshes.len() as u32); let mesh_id=model::MeshId::new(primitive_meshes.len() as u32);
indexed_model_id_from_description.insert(basepart_texture_description.clone(),indexed_model_id);//borrow checker going crazy mesh_id_from_description.insert(basepart_description.clone(),mesh_id);//borrow checker going crazy
meshes.push(match basepart_texture_description{ let mesh=match basepart_description{
RobloxBasePartDescription::Sphere(part_texture_description) RobloxBasePartDescription::Sphere(part_texture_description)
|RobloxBasePartDescription::Cylinder(part_texture_description) |RobloxBasePartDescription::Cylinder(part_texture_description)
|RobloxBasePartDescription::Part(part_texture_description)=>{ |RobloxBasePartDescription::Part(part_texture_description)=>{
@ -652,34 +680,166 @@ pub fn convert<F:FnMut(Option<&str>)->model::RenderConfigId>(dom:&rbx_dom_weak::
} }
primitives::generate_partial_unit_cornerwedge(cornerwedge_face_description) primitives::generate_partial_unit_cornerwedge(cornerwedge_face_description)
}, },
}); };
indexed_model_id primitive_meshes.push(mesh);
mesh_id
}; };
let attributes=get_attributes( (MeshAvailability::Immediate,mesh_id)
&object, },
*can_collide, Shape::MeshPart=>if let (
Planar64Vec3::try_from([velocity.x,velocity.y,velocity.z]).unwrap(), Some(rbx_dom_weak::types::Variant::Content(mesh_asset_id)),
Some(rbx_dom_weak::types::Variant::Content(texture_asset_id)),
)=(
object.properties.get("MeshId"),
object.properties.get("TextureID"),
){
(
MeshAvailability::Deferred(acquire_render_config_id(Some(texture_asset_id.as_ref()))),
acquire_mesh_id(mesh_asset_id.as_ref()),
)
}else{
panic!("Mesh has no Mesh or Texture");
},
};
let model_deferred_attributes=ModelDeferredAttributes{
mesh:mesh_id,
transform:model_transform,
color:glam::vec4(color3.r as f32/255f32, color3.g as f32/255f32, color3.b as f32/255f32, 1.0-*transparency),
deferred_attributes:GetAttributesArgs{
name:object.name.as_str().into(),
can_collide:*can_collide,
velocity:Planar64Vec3::try_from([velocity.x,velocity.y,velocity.z]).unwrap(),
},
};
match availability{
MeshAvailability::Immediate=>primitive_models_deferred_attributes.push(model_deferred_attributes),
MeshAvailability::Deferred(render)=>deferred_models_deferred_attributes.push(DeferredModelDeferredAttributes{
render,
model:model_deferred_attributes
}),
}
}
}
}
PartialMap1{
primitive_meshes,
primitive_models_deferred_attributes,
deferred_models_deferred_attributes,
}
}
struct MeshWithAabb{
mesh:model::Mesh,
aabb:strafesnet_common::aabb::Aabb,
}
pub struct PartialMap1{
primitive_meshes:Vec<model::Mesh>,
primitive_models_deferred_attributes:Vec<ModelDeferredAttributes>,
deferred_models_deferred_attributes:Vec<DeferredModelDeferredAttributes>,
}
impl PartialMap1{
pub fn add_meshpart_meshes_and_calculate_attributes(
mut self,
meshpart_meshes:impl IntoIterator<Item=(model::MeshId,crate::data::RobloxMeshBytes)>,
)->PartialMap2{
//calculate attributes
let mut modes_builder=ModesBuilder::default();
let mut unique_attributes=Vec::new();
let mut attributes_id_from_attributes=HashMap::new();
let mut wormhole_in_model_to_id=HashMap::new();
let mut wormhole_id_to_out_model=HashMap::new();
//decode roblox meshes
//generate mesh_id_map based on meshes that failed to load
let loaded_meshes:HashMap<model::MeshId,MeshWithAabb>=
meshpart_meshes.into_iter().flat_map(|(old_mesh_id,roblox_mesh_bytes)|
match crate::mesh::convert(roblox_mesh_bytes){
Ok(mesh)=>{
let mut aabb=strafesnet_common::aabb::Aabb::default();
for &pos in &mesh.unique_pos{
aabb.grow(pos);
}
Some((old_mesh_id,MeshWithAabb{
mesh,
aabb,
}))
},
Err(e)=>{
println!("Error converting mesh: {e:?}");
None
},
}
).collect();
let mut mesh_id_from_render_config_id=HashMap::new();
//ignore meshes that fail to load completely for now
let mut acquire_mesh_id_from_render_config_id=|old_mesh_id,render|{
loaded_meshes.get(&old_mesh_id).map(|mesh_with_aabb|(
*mesh_id_from_render_config_id.entry(old_mesh_id).or_insert_with(||HashMap::new())
.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)]
});
self.primitive_meshes.push(mesh_clone);
mesh_id
}),
&mesh_with_aabb.aabb,
))
};
//now that the meshes are loaded, these models can be generated
let models_owned_attributes:Vec<ModelOwnedAttributes>=
self.deferred_models_deferred_attributes.into_iter().flat_map(|deferred_model_deferred_attributes|{
//meshes need to be cloned from loaded_meshes with a new id when they are used with a new render_id
//insert into primitive_meshes
let (mesh,aabb)=acquire_mesh_id_from_render_config_id(
deferred_model_deferred_attributes.model.mesh,
deferred_model_deferred_attributes.render
)?;
let size=aabb.size();
Some(ModelDeferredAttributes{
mesh,
deferred_attributes:deferred_model_deferred_attributes.model.deferred_attributes,
color:deferred_model_deferred_attributes.model.color,
transform:Planar64Affine3::new(
Planar64Mat3::from_cols(
deferred_model_deferred_attributes.model.transform.matrix3.x_axis*2/size.x(),
deferred_model_deferred_attributes.model.transform.matrix3.y_axis*2/size.y(),
deferred_model_deferred_attributes.model.transform.matrix3.z_axis*2/size.z()
),
deferred_model_deferred_attributes.model.transform.translation
),
})
}).chain(self.primitive_models_deferred_attributes.into_iter())
.enumerate().map(|(model_id,model_deferred_attributes)|{
let model_id=model::ModelId::new(model_id as u32);
ModelOwnedAttributes{
mesh:model_deferred_attributes.mesh,
attributes:get_attributes(
&model_deferred_attributes.deferred_attributes.name,
model_deferred_attributes.deferred_attributes.can_collide,
model_deferred_attributes.deferred_attributes.velocity,
model_id, model_id,
&mut modes_builder, &mut modes_builder,
&mut wormhole_in_model_to_id, &mut wormhole_in_model_to_id,
&mut wormhole_id_to_out_model, &mut wormhole_id_to_out_model,
); ),
models1.push(ModelOwnedAttributes{ color:model_deferred_attributes.color,
mesh:indexed_model_id, transform:model_deferred_attributes.transform,
transform:model_transform,
color:glam::vec4(color3.r as f32/255f32, color3.g as f32/255f32, color3.b as f32/255f32, 1.0-*transparency),
attributes,
});
} }
} }).collect();
} let models=models_owned_attributes.into_iter().enumerate().map(|(model_id,mut model_owned_attributes)|{
let models=models1.into_iter().enumerate().map(|(model_id,mut model1)|{ //TODO: TAB
let model_id=model::ModelId::new(model_id as u32); let model_id=model::ModelId::new(model_id as u32);
//update attributes with wormhole id //update attributes with wormhole id
//TODO: errors/prints //TODO: errors/prints
if let Some(wormhole_id)=wormhole_in_model_to_id.get(&model_id){ if let Some(wormhole_id)=wormhole_in_model_to_id.get(&model_id){
if let Some(&wormhole_out_model_id)=wormhole_id_to_out_model.get(wormhole_id){ if let Some(&wormhole_out_model_id)=wormhole_id_to_out_model.get(wormhole_id){
match &mut model1.attributes{ match &mut model_owned_attributes.attributes{
attr::CollisionAttributes::Contact{contacting:_,general} attr::CollisionAttributes::Contact{contacting:_,general}
|attr::CollisionAttributes::Intersect{intersecting:_,general} |attr::CollisionAttributes::Intersect{intersecting:_,general}
=>general.wormhole=Some(attr::Wormhole{destination_model:wormhole_out_model_id}), =>general.wormhole=Some(attr::Wormhole{destination_model:wormhole_out_model_id}),
@ -689,36 +849,37 @@ pub fn convert<F:FnMut(Option<&str>)->model::RenderConfigId>(dom:&rbx_dom_weak::
} }
//index the attributes //index the attributes
let attributes_id=if let Some(&attributes_id)=attributes_id_from_attributes.get(&model1.attributes){ let attributes_id=if let Some(&attributes_id)=attributes_id_from_attributes.get(&model_owned_attributes.attributes){
attributes_id attributes_id
}else{ }else{
let attributes_id=attr::CollisionAttributesId::new(unique_attributes.len() as u32); let attributes_id=attr::CollisionAttributesId::new(unique_attributes.len() as u32);
attributes_id_from_attributes.insert(model1.attributes.clone(),attributes_id); attributes_id_from_attributes.insert(model_owned_attributes.attributes.clone(),attributes_id);
unique_attributes.push(model1.attributes); unique_attributes.push(model_owned_attributes.attributes);
attributes_id attributes_id
}; };
model::Model{ model::Model{
mesh:model1.mesh, mesh:model_owned_attributes.mesh,
transform:model1.transform, transform:model_owned_attributes.transform,
color:model1.color, color:model_owned_attributes.color,
attributes:attributes_id, attributes:attributes_id,
} }
}).collect(); }).collect();
PartialMap1{ PartialMap2{
meshes, meshes:self.primitive_meshes,
models, models,
modes:modes_builder.build(), modes:modes_builder.build(),
attributes:unique_attributes, attributes:unique_attributes,
} }
}
} }
pub struct PartialMap1{ pub struct PartialMap2{
meshes:Vec<model::Mesh>, meshes:Vec<model::Mesh>,
models:Vec<model::Model>, models:Vec<model::Model>,
modes:gameplay_modes::Modes, modes:gameplay_modes::Modes,
attributes:Vec<strafesnet_common::gameplay_attributes::CollisionAttributes>, attributes:Vec<strafesnet_common::gameplay_attributes::CollisionAttributes>,
} }
impl PartialMap1{ impl PartialMap2{
pub fn add_render_configs_and_textures( pub fn add_render_configs_and_textures(
self, self,
render_configs:impl IntoIterator<Item=(model::RenderConfigId,model::RenderConfig)>, render_configs:impl IntoIterator<Item=(model::RenderConfigId,model::RenderConfig)>,