Compare commits

...

21 Commits

Author SHA1 Message Date
05e609521a implement texture_loader 2024-02-13 02:09:35 -08:00
b5ab8f377d update common 2024-02-13 02:09:20 -08:00
5c9a79390a todo 2024-02-12 22:07:44 -08:00
5baff7a6e7 fix wormholes 2024-02-12 22:07:44 -08:00
c3fef4dc35 update common 2024-02-12 22:07:44 -08:00
ea48c40938 fix touching spawns 2024-02-12 21:37:54 -08:00
66269f326d fix stage_elements 2024-02-12 21:33:55 -08:00
77fb784c7e fix attributes & modes 2024-02-12 20:43:09 -08:00
cb80773db4 todo 2024-02-12 20:42:54 -08:00
da9fa7c51c sneak into stack 2024-02-12 18:54:54 -08:00
7a6b494358 update common 2024-02-09 00:18:09 -08:00
864f9dc551 rbx_loader::read 2024-02-08 20:58:00 -08:00
5fc2dda3a1 update common 2024-02-08 20:32:51 -08:00
777ee3439c update common 2024-02-07 22:42:07 -08:00
69f08061ed update common 2024-02-06 22:54:13 -08:00
1ad6104c14 collapse Models struct into Map 2024-02-04 22:42:07 -08:00
20cc3ef96f make it work 2024-02-04 20:57:25 -08:00
ffb20c6dc9 update common 2024-02-04 20:57:01 -08:00
451808f9af primitives wip 2024-02-01 22:17:44 -08:00
2cc7d76a48 wip 2024-02-01 20:03:22 -08:00
56859d4e0c wip make it work 2024-02-01 04:05:30 -08:00
5 changed files with 520 additions and 269 deletions

13
Cargo.lock generated
View File

@ -98,6 +98,16 @@ version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3" checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3"
[[package]]
name = "id"
version = "0.1.0"
source = "git+https://git.itzana.me/Quaternions/id?rev=1f710976cc786c8853dab73d6e1cee53158deeb0#1f710976cc786c8853dab73d6e1cee53158deeb0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "lazy-regex" name = "lazy-regex"
version = "3.1.0" version = "3.1.0"
@ -410,9 +420,10 @@ dependencies = [
[[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=5ee826d9487b5e2bea4b3cf99a68ce9a95d72f72#5ee826d9487b5e2bea4b3cf99a68ce9a95d72f72" source = "git+https://git.itzana.me/StrafesNET/common?rev=fccb13bc6080ea6db94ef9175affd4aef1d9249d#fccb13bc6080ea6db94ef9175affd4aef1d9249d"
dependencies = [ dependencies = [
"glam", "glam",
"id",
] ]
[[package]] [[package]]

View File

@ -12,4 +12,4 @@ rbx_binary = "0.7.4"
rbx_dom_weak = "2.7.0" rbx_dom_weak = "2.7.0"
rbx_reflection_database = "0.2.10" rbx_reflection_database = "0.2.10"
rbx_xml = "0.13.3" rbx_xml = "0.13.3"
strafesnet_common = { git = "https://git.itzana.me/StrafesNET/common", rev = "5ee826d9487b5e2bea4b3cf99a68ce9a95d72f72" } strafesnet_common = { git = "https://git.itzana.me/StrafesNET/common", rev = "fccb13bc6080ea6db94ef9175affd4aef1d9249d" }

View File

@ -1,2 +1,26 @@
use std::io::Read;
mod rbx;
mod primitives; mod primitives;
pub mod rbx;
#[derive(Debug)]
pub enum Error{
RbxBinary(rbx_binary::DecodeError),
RbxXml(rbx_xml::DecodeError),
Io(std::io::Error),
UnknownFileFormat,
}
fn load_dom<R:Read>(input:R)->Result<rbx_dom_weak::WeakDom,Error>{
let mut buf=std::io::BufReader::new(input);
let peek=std::io::BufRead::fill_buf(&mut buf).map_err(Error::Io)?;
match &peek[0..8]{
b"<roblox!"=>return rbx_binary::from_reader(buf).map_err(Error::RbxBinary),
b"<roblox "=>return rbx_xml::from_reader_default(buf).map_err(Error::RbxXml),
_=>Err(Error::UnknownFileFormat),
}
}
pub fn read<R:Read,F:FnMut(&str)->Option<strafesnet_common::model::TextureId>>(input:R,acquire_id:F)->Result<strafesnet_common::map::CompleteMap,Error>{
Ok(rbx::convert(load_dom(input)?,acquire_id))
}

View File

@ -1,4 +1,4 @@
use strafesnet_common::model::{Color4,TextureCoordinate,IndexedModel,IndexedPolygon,IndexedGroup,IndexedVertex}; use strafesnet_common::model::{Color4,TextureCoordinate,Mesh,IndexedGraphicsGroup,IndexedPhysicsGroup,IndexedVertex,PolygonGroupId,PolygonGroup,PolygonList,IndexedVertexList,PositionId,TextureCoordinateId,NormalId,ColorId,VertexId,RenderConfigId};
use strafesnet_common::integer::Planar64Vec3; use strafesnet_common::integer::Planar64Vec3;
#[derive(Debug)] #[derive(Debug)]
@ -126,8 +126,8 @@ const CORNERWEDGE_DEFAULT_NORMALS:[Planar64Vec3;5]=[
Planar64Vec3::int( 0,-1, 0),//CornerWedge::Bottom Planar64Vec3::int( 0,-1, 0),//CornerWedge::Bottom
Planar64Vec3::int( 0, 0,-1),//CornerWedge::Front Planar64Vec3::int( 0, 0,-1),//CornerWedge::Front
]; ];
pub fn unit_sphere()->crate::model::IndexedModel{ pub fn unit_sphere(render:RenderConfigId)->Mesh{
unit_cube() unit_cube(render)
} }
#[derive(Default)] #[derive(Default)]
pub struct CubeFaceDescription([Option<FaceDescription>;6]); pub struct CubeFaceDescription([Option<FaceDescription>;6]);
@ -139,18 +139,19 @@ impl CubeFaceDescription{
self.0.into_iter().enumerate().filter_map(|v|v.1.map(|u|(v.0,u))) self.0.into_iter().enumerate().filter_map(|v|v.1.map(|u|(v.0,u)))
} }
} }
pub fn unit_cube()->crate::model::IndexedModel{ pub fn unit_cube(render:RenderConfigId)->Mesh{
let mut t=CubeFaceDescription::default(); let mut t=CubeFaceDescription::default();
t.insert(CubeFace::Right,FaceDescription::default()); t.insert(CubeFace::Right,FaceDescription::new_with_render_id(render));
t.insert(CubeFace::Top,FaceDescription::default()); t.insert(CubeFace::Top,FaceDescription::new_with_render_id(render));
t.insert(CubeFace::Back,FaceDescription::default()); t.insert(CubeFace::Back,FaceDescription::new_with_render_id(render));
t.insert(CubeFace::Left,FaceDescription::default()); t.insert(CubeFace::Left,FaceDescription::new_with_render_id(render));
t.insert(CubeFace::Bottom,FaceDescription::default()); t.insert(CubeFace::Bottom,FaceDescription::new_with_render_id(render));
t.insert(CubeFace::Front,FaceDescription::default()); t.insert(CubeFace::Front,FaceDescription::new_with_render_id(render));
generate_partial_unit_cube(t) generate_partial_unit_cube(t)
} }
pub fn unit_cylinder()->crate::model::IndexedModel{ pub fn unit_cylinder(render:RenderConfigId)->Mesh{
unit_cube() //lmao
unit_cube(render)
} }
#[derive(Default)] #[derive(Default)]
pub struct WedgeFaceDescription([Option<FaceDescription>;5]); pub struct WedgeFaceDescription([Option<FaceDescription>;5]);
@ -162,13 +163,13 @@ impl WedgeFaceDescription{
self.0.into_iter().enumerate().filter_map(|v|v.1.map(|u|(v.0,u))) self.0.into_iter().enumerate().filter_map(|v|v.1.map(|u|(v.0,u)))
} }
} }
pub fn unit_wedge()->crate::model::IndexedModel{ pub fn unit_wedge(render:RenderConfigId)->Mesh{
let mut t=WedgeFaceDescription::default(); let mut t=WedgeFaceDescription::default();
t.insert(WedgeFace::Right,FaceDescription::default()); t.insert(WedgeFace::Right,FaceDescription::new_with_render_id(render));
t.insert(WedgeFace::TopFront,FaceDescription::default()); t.insert(WedgeFace::TopFront,FaceDescription::new_with_render_id(render));
t.insert(WedgeFace::Back,FaceDescription::default()); t.insert(WedgeFace::Back,FaceDescription::new_with_render_id(render));
t.insert(WedgeFace::Left,FaceDescription::default()); t.insert(WedgeFace::Left,FaceDescription::new_with_render_id(render));
t.insert(WedgeFace::Bottom,FaceDescription::default()); t.insert(WedgeFace::Bottom,FaceDescription::new_with_render_id(render));
generate_partial_unit_wedge(t) generate_partial_unit_wedge(t)
} }
#[derive(Default)] #[derive(Default)]
@ -181,26 +182,26 @@ impl CornerWedgeFaceDescription{
self.0.into_iter().enumerate().filter_map(|v|v.1.map(|u|(v.0,u))) self.0.into_iter().enumerate().filter_map(|v|v.1.map(|u|(v.0,u)))
} }
} }
pub fn unit_cornerwedge()->crate::model::IndexedModel{ pub fn unit_cornerwedge(render:RenderConfigId)->Mesh{
let mut t=CornerWedgeFaceDescription::default(); let mut t=CornerWedgeFaceDescription::default();
t.insert(CornerWedgeFace::Right,FaceDescription::default()); t.insert(CornerWedgeFace::Right,FaceDescription::new_with_render_id(render));
t.insert(CornerWedgeFace::TopBack,FaceDescription::default()); t.insert(CornerWedgeFace::TopBack,FaceDescription::new_with_render_id(render));
t.insert(CornerWedgeFace::TopLeft,FaceDescription::default()); t.insert(CornerWedgeFace::TopLeft,FaceDescription::new_with_render_id(render));
t.insert(CornerWedgeFace::Bottom,FaceDescription::default()); t.insert(CornerWedgeFace::Bottom,FaceDescription::new_with_render_id(render));
t.insert(CornerWedgeFace::Front,FaceDescription::default()); t.insert(CornerWedgeFace::Front,FaceDescription::new_with_render_id(render));
generate_partial_unit_cornerwedge(t) generate_partial_unit_cornerwedge(t)
} }
#[derive(Clone)] #[derive(Clone)]
pub struct FaceDescription{ pub struct FaceDescription{
pub texture:Option<u32>, pub render:RenderConfigId,
pub transform:glam::Affine2, pub transform:glam::Affine2,
pub color:Color4, pub color:Color4,
} }
impl std::default::Default for FaceDescription{ impl FaceDescription{
fn default()->Self { pub fn new_with_render_id(render:RenderConfigId)->Self {
Self{ Self{
texture:None, render,
transform:glam::Affine2::IDENTITY, transform:glam::Affine2::IDENTITY,
color:Color4::new(1.0,1.0,1.0,0.0),//zero alpha to hide the default texture color:Color4::new(1.0,1.0,1.0,0.0),//zero alpha to hide the default texture
} }
@ -208,13 +209,15 @@ impl std::default::Default for FaceDescription{
} }
//TODO: it's probably better to use a shared vertex buffer between all primitives and use indexed rendering instead of generating a unique vertex buffer for each primitive. //TODO: it's probably better to use a shared vertex buffer between all primitives and use indexed rendering instead of generating a unique vertex buffer for each primitive.
//implementation: put all roblox primitives into one model.groups <- this won't work but I forget why //implementation: put all roblox primitives into one model.groups <- this won't work but I forget why
pub fn generate_partial_unit_cube(face_descriptions:CubeFaceDescription)->crate::model::IndexedModel{ pub fn generate_partial_unit_cube(face_descriptions:CubeFaceDescription)->Mesh{
let mut generated_pos=Vec::new(); let mut generated_pos=Vec::new();
let mut generated_tex=Vec::new(); let mut generated_tex=Vec::new();
let mut generated_normal=Vec::new(); let mut generated_normal=Vec::new();
let mut generated_color=Vec::new(); let mut generated_color=Vec::new();
let mut generated_vertices=Vec::new(); let mut generated_vertices=Vec::new();
let mut groups=Vec::new(); let mut polygon_groups=Vec::new();
let mut graphics_groups=Vec::new();
let mut physics_groups=vec![IndexedPhysicsGroup::default()];
let mut transforms=Vec::new(); let mut transforms=Vec::new();
//note that on a cube every vertex is guaranteed to be unique, so there's no need to hash them against existing vertices. //note that on a cube every vertex is guaranteed to be unique, so there's no need to hash them against existing vertices.
for (face_id,face_description) in face_descriptions.pairs(){ for (face_id,face_description) in face_descriptions.pairs(){
@ -242,46 +245,50 @@ pub fn generate_partial_unit_cube(face_descriptions:CubeFaceDescription)->crate:
let normal_index=generated_normal.len() as u32; let normal_index=generated_normal.len() as u32;
generated_normal.push(CUBE_DEFAULT_NORMALS[face_id]); generated_normal.push(CUBE_DEFAULT_NORMALS[face_id]);
//push vertices as they are needed //push vertices as they are needed
groups.push(IndexedGroup{ let group_id=PolygonGroupId::new(polygon_groups.len() as u32);
texture:face_description.texture, polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(vec![
polys:vec![IndexedPolygon{ CUBE_DEFAULT_POLYS[face_id].map(|tup|{
vertices:CUBE_DEFAULT_POLYS[face_id].map(|tup|{ let pos=CUBE_DEFAULT_VERTICES[tup[0] as usize];
let pos=CUBE_DEFAULT_VERTICES[tup[0] as usize]; let pos_index=if let Some(pos_index)=generated_pos.iter().position(|&p|p==pos){
let pos_index=if let Some(pos_index)=generated_pos.iter().position(|&p|p==pos){ pos_index
pos_index }else{
}else{ //create new pos_index
//create new pos_index let pos_index=generated_pos.len();
let pos_index=generated_pos.len(); generated_pos.push(pos);
generated_pos.push(pos); pos_index
pos_index } as u32;
} as u32; //always push vertex
//always push vertex let vertex=IndexedVertex{
let vertex=IndexedVertex{ pos:PositionId::new(pos_index),
pos:pos_index, tex:TextureCoordinateId::new(tup[1]+4*transform_index),
tex:tup[1]+4*transform_index, normal:NormalId::new(normal_index),
normal:normal_index, color:ColorId::new(color_index),
color:color_index, };
}; let vert_index=generated_vertices.len();
let vert_index=generated_vertices.len(); generated_vertices.push(vertex);
generated_vertices.push(vertex); VertexId::new(vert_index as u32)
vert_index as u32 }).to_vec(),
}).to_vec(), ])));
}], graphics_groups.push(IndexedGraphicsGroup{
render:face_description.render,
groups:vec![group_id],
}); });
physics_groups[0].groups.push(group_id);
} }
IndexedModel{ Mesh{
unique_pos:generated_pos, unique_pos:generated_pos,
unique_tex:generated_tex, unique_tex:generated_tex,
unique_normal:generated_normal, unique_normal:generated_normal,
unique_color:generated_color, unique_color:generated_color,
unique_vertices:generated_vertices, unique_vertices:generated_vertices,
groups, polygon_groups,
instances:Vec::new(), graphics_groups,
physics_groups,
} }
} }
//don't think too hard about the copy paste because this is all going into the map tool eventually... //don't think too hard about the copy paste because this is all going into the map tool eventually...
pub fn generate_partial_unit_wedge(face_descriptions:WedgeFaceDescription)->crate::model::IndexedModel{ pub fn generate_partial_unit_wedge(face_descriptions:WedgeFaceDescription)->Mesh{
let wedge_default_polys=vec![ let wedge_default_polys=[
// right (1, 0, 0) // right (1, 0, 0)
vec![ vec![
[6,2,0],//[vertex,tex,norm] [6,2,0],//[vertex,tex,norm]
@ -321,7 +328,9 @@ pub fn generate_partial_unit_wedge(face_descriptions:WedgeFaceDescription)->crat
let mut generated_normal=Vec::new(); let mut generated_normal=Vec::new();
let mut generated_color=Vec::new(); let mut generated_color=Vec::new();
let mut generated_vertices=Vec::new(); let mut generated_vertices=Vec::new();
let mut groups=Vec::new(); let mut polygon_groups=Vec::new();
let mut graphics_groups=Vec::new();
let mut physics_groups=vec![IndexedPhysicsGroup::default()];
let mut transforms=Vec::new(); let mut transforms=Vec::new();
//note that on a cube every vertex is guaranteed to be unique, so there's no need to hash them against existing vertices. //note that on a cube every vertex is guaranteed to be unique, so there's no need to hash them against existing vertices.
for (face_id,face_description) in face_descriptions.pairs(){ for (face_id,face_description) in face_descriptions.pairs(){
@ -349,46 +358,50 @@ pub fn generate_partial_unit_wedge(face_descriptions:WedgeFaceDescription)->crat
let normal_index=generated_normal.len() as u32; let normal_index=generated_normal.len() as u32;
generated_normal.push(WEDGE_DEFAULT_NORMALS[face_id]); generated_normal.push(WEDGE_DEFAULT_NORMALS[face_id]);
//push vertices as they are needed //push vertices as they are needed
groups.push(IndexedGroup{ let group_id=PolygonGroupId::new(polygon_groups.len() as u32);
texture:face_description.texture, polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(vec![
polys:vec![IndexedPolygon{ wedge_default_polys[face_id].iter().map(|tup|{
vertices:wedge_default_polys[face_id].iter().map(|tup|{ let pos=CUBE_DEFAULT_VERTICES[tup[0] as usize];
let pos=CUBE_DEFAULT_VERTICES[tup[0] as usize]; let pos_index=if let Some(pos_index)=generated_pos.iter().position(|&p|p==pos){
let pos_index=if let Some(pos_index)=generated_pos.iter().position(|&p|p==pos){ pos_index
pos_index }else{
}else{ //create new pos_index
//create new pos_index let pos_index=generated_pos.len();
let pos_index=generated_pos.len(); generated_pos.push(pos);
generated_pos.push(pos); pos_index
pos_index } as u32;
} as u32; //always push vertex
//always push vertex let vertex=IndexedVertex{
let vertex=IndexedVertex{ pos:PositionId::new(pos_index),
pos:pos_index, tex:TextureCoordinateId::new(tup[1]+4*transform_index),
tex:tup[1]+4*transform_index, normal:NormalId::new(normal_index),
normal:normal_index, color:ColorId::new(color_index),
color:color_index, };
}; let vert_index=generated_vertices.len();
let vert_index=generated_vertices.len(); generated_vertices.push(vertex);
generated_vertices.push(vertex); VertexId::new(vert_index as u32)
vert_index as u32 }).collect()
}).collect(), ])));
}], graphics_groups.push(IndexedGraphicsGroup{
render:face_description.render,
groups:vec![group_id],
}); });
physics_groups[0].groups.push(group_id);
} }
IndexedModel{ Mesh{
unique_pos:generated_pos, unique_pos:generated_pos,
unique_tex:generated_tex, unique_tex:generated_tex,
unique_normal:generated_normal, unique_normal:generated_normal,
unique_color:generated_color, unique_color:generated_color,
unique_vertices:generated_vertices, unique_vertices:generated_vertices,
groups, polygon_groups,
instances:Vec::new(), graphics_groups,
physics_groups,
} }
} }
pub fn generate_partial_unit_cornerwedge(face_descriptions:CornerWedgeFaceDescription)->crate::model::IndexedModel{ pub fn generate_partial_unit_cornerwedge(face_descriptions:CornerWedgeFaceDescription)->Mesh{
let cornerwedge_default_polys=vec![ let cornerwedge_default_polys=[
// right (1, 0, 0) // right (1, 0, 0)
vec![ vec![
[6,2,0],//[vertex,tex,norm] [6,2,0],//[vertex,tex,norm]
@ -426,7 +439,9 @@ pub fn generate_partial_unit_cornerwedge(face_descriptions:CornerWedgeFaceDescri
let mut generated_normal=Vec::new(); let mut generated_normal=Vec::new();
let mut generated_color=Vec::new(); let mut generated_color=Vec::new();
let mut generated_vertices=Vec::new(); let mut generated_vertices=Vec::new();
let mut groups=Vec::new(); let mut polygon_groups=Vec::new();
let mut graphics_groups=Vec::new();
let mut physics_groups=vec![IndexedPhysicsGroup::default()];
let mut transforms=Vec::new(); let mut transforms=Vec::new();
//note that on a cube every vertex is guaranteed to be unique, so there's no need to hash them against existing vertices. //note that on a cube every vertex is guaranteed to be unique, so there's no need to hash them against existing vertices.
for (face_id,face_description) in face_descriptions.pairs(){ for (face_id,face_description) in face_descriptions.pairs(){
@ -454,40 +469,44 @@ pub fn generate_partial_unit_cornerwedge(face_descriptions:CornerWedgeFaceDescri
let normal_index=generated_normal.len() as u32; let normal_index=generated_normal.len() as u32;
generated_normal.push(CORNERWEDGE_DEFAULT_NORMALS[face_id]); generated_normal.push(CORNERWEDGE_DEFAULT_NORMALS[face_id]);
//push vertices as they are needed //push vertices as they are needed
groups.push(IndexedGroup{ let group_id=PolygonGroupId::new(polygon_groups.len() as u32);
texture:face_description.texture, polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(vec![
polys:vec![IndexedPolygon{ cornerwedge_default_polys[face_id].iter().map(|tup|{
vertices:cornerwedge_default_polys[face_id].iter().map(|tup|{ let pos=CUBE_DEFAULT_VERTICES[tup[0] as usize];
let pos=CUBE_DEFAULT_VERTICES[tup[0] as usize]; let pos_index=if let Some(pos_index)=generated_pos.iter().position(|&p|p==pos){
let pos_index=if let Some(pos_index)=generated_pos.iter().position(|&p|p==pos){ pos_index
pos_index }else{
}else{ //create new pos_index
//create new pos_index let pos_index=generated_pos.len();
let pos_index=generated_pos.len(); generated_pos.push(pos);
generated_pos.push(pos); pos_index
pos_index } as u32;
} as u32; //always push vertex
//always push vertex let vertex=IndexedVertex{
let vertex=IndexedVertex{ pos:PositionId::new(pos_index),
pos:pos_index, tex:TextureCoordinateId::new(tup[1]+4*transform_index),
tex:tup[1]+4*transform_index, normal:NormalId::new(normal_index),
normal:normal_index, color:ColorId::new(color_index),
color:color_index, };
}; let vert_index=generated_vertices.len();
let vert_index=generated_vertices.len(); generated_vertices.push(vertex);
generated_vertices.push(vertex); VertexId::new(vert_index as u32)
vert_index as u32 }).collect(),
}).collect(), ])));
}], graphics_groups.push(IndexedGraphicsGroup{
render:face_description.render,
groups:vec![group_id],
}); });
physics_groups[0].groups.push(group_id);
} }
IndexedModel{ Mesh{
unique_pos:generated_pos, unique_pos:generated_pos,
unique_tex:generated_tex, unique_tex:generated_tex,
unique_normal:generated_normal, unique_normal:generated_normal,
unique_color:generated_color, unique_color:generated_color,
unique_vertices:generated_vertices, unique_vertices:generated_vertices,
groups, polygon_groups,
instances:Vec::new(), graphics_groups,
physics_groups,
} }
} }

View File

@ -1,6 +1,14 @@
use std::collections::HashMap;
use crate::primitives; use crate::primitives;
use strafesnet_common::gameplay_attributes; use strafesnet_common::map;
use strafesnet_common::model;
use strafesnet_common::gameplay_modes;
use strafesnet_common::gameplay_style;
use strafesnet_common::gameplay_attributes as attr;
use strafesnet_common::integer::{Planar64,Planar64Vec3,Planar64Mat3,Planar64Affine3}; use strafesnet_common::integer::{Planar64,Planar64Vec3,Planar64Mat3,Planar64Affine3};
use strafesnet_common::model::RenderConfig;
use strafesnet_common::model::RenderConfigId;
use strafesnet_common::updatable::Updatable;
fn class_is_a(class: &str, superclass: &str) -> bool { fn class_is_a(class: &str, superclass: &str) -> bool {
if class==superclass { if class==superclass {
@ -40,91 +48,265 @@ fn planar64_affine3_from_roblox(cf:&rbx_dom_weak::types::CFrame,size:&rbx_dom_we
Planar64Vec3::try_from([cf.position.x,cf.position.y,cf.position.z]).unwrap() Planar64Vec3::try_from([cf.position.x,cf.position.y,cf.position.z]).unwrap()
) )
} }
fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,force_intersecting:bool)->model::CollisionAttributes{ struct ModeBuilder{
let mut general=model::GameMechanicAttributes::default(); mode:gameplay_modes::Mode,
let mut intersecting=model::IntersectingAttributes::default(); final_stage_id_from_builder_stage_id:HashMap<gameplay_modes::StageId,gameplay_modes::StageId>,
let mut contacting=model::ContactingAttributes::default(); }
#[derive(Default)]
struct ModesBuilder{
modes:HashMap<gameplay_modes::ModeId,gameplay_modes::Mode>,
stages:HashMap<gameplay_modes::ModeId,HashMap<gameplay_modes::StageId,gameplay_modes::Stage>>,
mode_updates:Vec<(gameplay_modes::ModeId,gameplay_modes::ModeUpdate)>,
stage_updates:Vec<(gameplay_modes::ModeId,gameplay_modes::StageId,gameplay_modes::StageUpdate)>,
}
impl ModesBuilder{
fn build(mut self)->gameplay_modes::Modes{
//collect modes and stages into contiguous arrays
let mut unique_modes:Vec<(gameplay_modes::ModeId,gameplay_modes::Mode)>
=self.modes.into_iter().collect();
unique_modes.sort_by(|a,b|a.0.cmp(&b.0));
let (mut modes,final_mode_id_from_builder_mode_id):(Vec<ModeBuilder>,HashMap<gameplay_modes::ModeId,gameplay_modes::ModeId>)
=unique_modes.into_iter().enumerate()
.map(|(final_mode_id,(builder_mode_id,mut mode))|{
(
ModeBuilder{
final_stage_id_from_builder_stage_id:self.stages.remove(&builder_mode_id).map_or_else(||HashMap::new(),|stages|{
let mut unique_stages:Vec<(gameplay_modes::StageId,gameplay_modes::Stage)>
=stages.into_iter().collect();
unique_stages.sort_by(|a,b|a.0.cmp(&b.0));
unique_stages.into_iter().enumerate()
.map(|(final_stage_id,(builder_stage_id,stage))|{
mode.push_stage(stage);
(builder_stage_id,gameplay_modes::StageId::new(final_stage_id as u32))
}).collect()
}),
mode,
},
(
builder_mode_id,
gameplay_modes::ModeId::new(final_mode_id as u32)
)
)
}).unzip();
//TODO: failure messages or errors or something
//push stage updates
for (builder_mode_id,builder_stage_id,stage_update) in self.stage_updates{
if let Some(final_mode_id)=final_mode_id_from_builder_mode_id.get(&builder_mode_id){
if let Some(mode)=modes.get_mut(final_mode_id.get() as usize){
if let Some(&final_stage_id)=mode.final_stage_id_from_builder_stage_id.get(&builder_stage_id){
if let Some(stage)=mode.mode.get_stage_mut(final_stage_id){
stage.update(stage_update);
}
}
}
}
}
//push mode updates
for (builder_mode_id,mut mode_update) in self.mode_updates{
if let Some(final_mode_id)=final_mode_id_from_builder_mode_id.get(&builder_mode_id){
if let Some(mode)=modes.get_mut(final_mode_id.get() as usize){
//map stage id on stage elements
mode_update.map_stage_element_ids(|stage_id|
//walk down one stage id at a time until a stage is found
//TODO use better logic like BTreeMap::upper_bound instead of walking
// final_stage_id_from_builder_stage_id.upper_bound(Bound::Included(&stage_id))
// .value().copied().unwrap_or(gameplay_modes::StageId::FIRST)
(0..=stage_id.get()).rev().find_map(|builder_stage_id|
//map the stage element to that stage
mode.final_stage_id_from_builder_stage_id.get(&gameplay_modes::StageId::new(builder_stage_id)).copied()
).unwrap_or(gameplay_modes::StageId::FIRST)
);
mode.mode.update(mode_update);
}
}
}
gameplay_modes::Modes::new(modes.into_iter().map(|mode_builder|mode_builder.mode).collect())
}
fn insert_mode(&mut self,mode_id:gameplay_modes::ModeId,mode:gameplay_modes::Mode){
assert!(self.modes.insert(mode_id,mode).is_none(),"Cannot replace existing mode");
}
fn insert_stage(&mut self,mode_id:gameplay_modes::ModeId,stage_id:gameplay_modes::StageId,stage:gameplay_modes::Stage){
assert!(self.stages.entry(mode_id).or_insert(HashMap::new()).insert(stage_id,stage).is_none(),"Cannot replace existing stage");
}
fn push_mode_update(&mut self,mode_id:gameplay_modes::ModeId,mode_update:gameplay_modes::ModeUpdate){
self.mode_updates.push((mode_id,mode_update));
}
fn push_stage_update(&mut self,mode_id:gameplay_modes::ModeId,stage_id:gameplay_modes::StageId,stage_update:gameplay_modes::StageUpdate){
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{
let name=object.name.as_str();
let mut general=attr::GeneralAttributes::default();
let mut intersecting=attr::IntersectingAttributes::default();
let mut contacting=attr::ContactingAttributes::default();
let mut force_can_collide=can_collide; let mut force_can_collide=can_collide;
let mut force_intersecting=false;
match name{ match name{
"Water"=>{ "Water"=>{
force_can_collide=false; force_can_collide=false;
//TODO: read stupid CustomPhysicalProperties //TODO: read stupid CustomPhysicalProperties
intersecting.water=Some(model::IntersectingWater{density:Planar64::ONE,viscosity:Planar64::ONE/10,velocity}); intersecting.water=Some(attr::IntersectingWater{density:Planar64::ONE,viscosity:Planar64::ONE/10,velocity});
}, },
"Accelerator"=>{ "Accelerator"=>{
//although the new game supports collidable accelerators, this is a roblox compatability map loader //although the new game supports collidable accelerators, this is a roblox compatability map loader
force_can_collide=false; force_can_collide=false;
general.accelerator=Some(model::GameMechanicAccelerator{acceleration:velocity}); general.accelerator=Some(attr::Accelerator{acceleration:velocity});
}, },
// "UnorderedCheckpoint"=>general.teleport_behaviour=Some(model::TeleportBehaviour::StageElement(model::GameMechanicStageElement{ // "UnorderedCheckpoint"=>general.teleport_behaviour=Some(model::TeleportBehaviour::StageElement(attr::StageElement{
// mode_id:0, // mode_id:0,
// stage_id:0, // stage_id:0,
// force:false, // force:false,
// behaviour:model::StageElementBehaviour::Unordered // behaviour:model::StageElementBehaviour::Unordered
// })), // })),
"SetVelocity"=>general.trajectory=Some(model::GameMechanicSetTrajectory::Velocity(velocity)), "SetVelocity"=>general.trajectory=Some(attr::SetTrajectory::Velocity(velocity)),
"MapFinish"=>{force_can_collide=false;general.zone=Some(model::GameMechanicZone{mode_id:0,behaviour:model::ZoneBehaviour::Finish})}, "MapStart"=>{
"MapAnticheat"=>{force_can_collide=false;general.zone=Some(model::GameMechanicZone{mode_id:0,behaviour:model::ZoneBehaviour::Anitcheat})}, force_can_collide=false;
"Platform"=>general.teleport_behaviour=Some(model::TeleportBehaviour::StageElement(model::GameMechanicStageElement{ force_intersecting=true;
mode_id:0, modes_builder.insert_mode(
stage_id:0, gameplay_modes::ModeId::MAIN,
force:false, gameplay_modes::Mode::new(
behaviour:model::StageElementBehaviour::Platform, gameplay_style::StyleModifiers::roblox_bhop(),
})), model_id
)
);
},
"MapFinish"=>{
force_can_collide=false;
force_intersecting=true;
modes_builder.push_mode_update(
gameplay_modes::ModeId::MAIN,
gameplay_modes::ModeUpdate::zone(
model_id,
gameplay_modes::Zone::Finish,
),
);
},
"MapAnticheat"=>{
force_can_collide=false;
force_intersecting=true;
modes_builder.push_mode_update(
gameplay_modes::ModeId::MAIN,
gameplay_modes::ModeUpdate::zone(
model_id,
gameplay_modes::Zone::Anticheat,
),
);
},
"Platform"=>{
modes_builder.push_mode_update(
gameplay_modes::ModeId::MAIN,
gameplay_modes::ModeUpdate::element(
model_id,
gameplay_modes::StageElement::new(gameplay_modes::StageId::FIRST,false,gameplay_modes::StageElementBehaviour::Platform),//roblox does not know which stage the platform belongs to
),
);
},
other=>{ other=>{
if let Some(captures)=lazy_regex::regex!(r"^(Force)?(Spawn|SpawnAt|Trigger|Teleport|Platform)(\d+)$") let regman=lazy_regex::regex!(r"^(BonusStart|WormholeOut)(\d+)$");
if let Some(captures)=regman.captures(other){
match &captures[1]{
"BonusStart"=>{
force_can_collide=false;
force_intersecting=true;
modes_builder.insert_mode(
gameplay_modes::ModeId::new(captures[2].parse::<u32>().unwrap()),
gameplay_modes::Mode::new(
gameplay_style::StyleModifiers::roblox_bhop(),
model_id
)
);
},
"WormholeOut"=>{
//the PhysicsModelId has to exist for it to be teleported to!
force_intersecting=true;
//this object is not special in strafe client, but the roblox mapping needs to be converted to model id
assert!(wormhole_id_to_out_model.insert(captures[2].parse::<u32>().unwrap(),model_id).is_none(),"Cannot have multiple WormholeOut with same id");
},
_=>(),
}
}else if let Some(captures)=lazy_regex::regex!(r"^(Force)?(Spawn|SpawnAt|Trigger|Teleport|Platform)(\d+)$")
.captures(other){ .captures(other){
general.teleport_behaviour=Some(model::TeleportBehaviour::StageElement(model::GameMechanicStageElement{ force_intersecting=true;
mode_id:0, let stage_id=gameplay_modes::StageId::new(captures[3].parse::<u32>().unwrap());
stage_id:captures[3].parse::<u32>().unwrap(), let stage_element=gameplay_modes::StageElement::new(
force:match captures.get(1){ //stage_id:
stage_id,
//force:
match captures.get(1){
Some(m)=>m.as_str()=="Force", Some(m)=>m.as_str()=="Force",
None=>false, None=>false,
}, },
behaviour:match &captures[2]{ //behaviour:
"Spawn"|"SpawnAt"=>model::StageElementBehaviour::SpawnAt, match &captures[2]{
"Spawn"=>{
modes_builder.insert_stage(
gameplay_modes::ModeId::MAIN,
stage_id,
gameplay_modes::Stage::new(model_id),
);
//TODO: let denormalize handle this
gameplay_modes::StageElementBehaviour::SpawnAt
},
"SpawnAt"=>gameplay_modes::StageElementBehaviour::SpawnAt,
//cancollide false so you don't hit the side //cancollide false so you don't hit the side
//NOT a decoration //NOT a decoration
"Trigger"=>{force_can_collide=false;model::StageElementBehaviour::Trigger}, "Trigger"=>{force_can_collide=false;gameplay_modes::StageElementBehaviour::Trigger},
"Teleport"=>{force_can_collide=false;model::StageElementBehaviour::Teleport}, "Teleport"=>{force_can_collide=false;gameplay_modes::StageElementBehaviour::Teleport},
"Platform"=>model::StageElementBehaviour::Platform, "Platform"=>gameplay_modes::StageElementBehaviour::Platform,
_=>panic!("regex1[2] messed up bad"), _=>panic!("regex1[2] messed up bad"),
}
}));
}else if let Some(captures)=lazy_regex::regex!(r"^(Force)?(Jump)(\d+)$")
.captures(other){
general.teleport_behaviour=Some(model::TeleportBehaviour::StageElement(model::GameMechanicStageElement{
mode_id:0,
stage_id:0,
force:match captures.get(1){
Some(m)=>m.as_str()=="Force",
None=>false,
}, },
behaviour:match &captures[2]{ );
"Jump"=>model::StageElementBehaviour::JumpLimit(captures[3].parse::<u32>().unwrap()), modes_builder.push_mode_update(
_=>panic!("regex4[1] messed up bad"), gameplay_modes::ModeId::MAIN,
} gameplay_modes::ModeUpdate::element(
})); model_id,
stage_element,
),
);
}else if let Some(captures)=lazy_regex::regex!(r"^(Jump|WormholeIn)(\d+)$")
.captures(other){
match &captures[1]{
"Jump"=>modes_builder.push_mode_update(
gameplay_modes::ModeId::MAIN,
gameplay_modes::ModeUpdate::jump_limit(
model_id,
//jump_limit:
captures[2].parse::<u32>().unwrap()
),
),
"WormholeIn"=>{
force_can_collide=false;
force_intersecting=true;
assert!(wormhole_in_model_to_id.insert(model_id,captures[2].parse::<u32>().unwrap()).is_none(),"Impossible");
},
_=>panic!("regex2[1] messed up bad"),
}
}else if let Some(captures)=lazy_regex::regex!(r"^Bonus(Finish|Anticheat)(\d+)$") }else if let Some(captures)=lazy_regex::regex!(r"^Bonus(Finish|Anticheat)(\d+)$")
.captures(other){ .captures(other){
force_can_collide=false; force_can_collide=false;
match &captures[1]{ force_intersecting=true;
"Finish"=>general.zone=Some(model::GameMechanicZone{mode_id:captures[2].parse::<u32>().unwrap(),behaviour:model::ZoneBehaviour::Finish}), modes_builder.push_mode_update(
"Anticheat"=>general.zone=Some(model::GameMechanicZone{mode_id:captures[2].parse::<u32>().unwrap(),behaviour:model::ZoneBehaviour::Anitcheat}), gameplay_modes::ModeId::new(captures[2].parse::<u32>().unwrap()),
_=>panic!("regex2[1] messed up bad"), gameplay_modes::ModeUpdate::zone(
} model_id,
}else if let Some(captures)=lazy_regex::regex!(r"^(WormholeIn)(\d+)$") //zone:
.captures(other){ match &captures[1]{
force_can_collide=false; "Finish"=>gameplay_modes::Zone::Finish,
match &captures[1]{ "Anticheat"=>gameplay_modes::Zone::Anticheat,
"WormholeIn"=>general.teleport_behaviour=Some(model::TeleportBehaviour::Wormhole(model::GameMechanicWormhole{destination_model_id:captures[2].parse::<u32>().unwrap()})), _=>panic!("regex3[1] messed up bad"),
_=>panic!("regex3[1] messed up bad"), },
} ),
);
} }
// else if let Some(captures)=lazy_regex::regex!(r"^(OrderedCheckpoint)(\d+)$") // else if let Some(captures)=lazy_regex::regex!(r"^Stage(\d+)OrderedCheckpoint(\d+)$")
// .captures(other){ // .captures(other){
// match &captures[1]{ // match &captures[1]{
// "OrderedCheckpoint"=>general.checkpoint=Some(model::GameMechanicCheckpoint::Ordered{mode_id:0,checkpoint_id:captures[2].parse::<u32>().unwrap()}), // "OrderedCheckpoint"=>modes_builder.push_stage_update(
// gameplay_modes::ModeId::MAIN,
// gameplay_modes::StageId::new(0),
// gameplay_modes::StageUpdate::ordered_checkpoint(captures[2].parse::<u32>().unwrap()),
// ),
// _=>panic!("regex3[1] messed up bad"), // _=>panic!("regex3[1] messed up bad"),
// } // }
// } // }
@ -132,45 +314,29 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,force_interse
} }
//need some way to skip this //need some way to skip this
if velocity!=Planar64Vec3::ZERO{ if velocity!=Planar64Vec3::ZERO{
general.booster=Some(model::GameMechanicBooster::Velocity(velocity)); general.booster=Some(attr::Booster::Velocity(velocity));
} }
match force_can_collide{ match force_can_collide{
true=>{ true=>{
match name{ match name{
"Bounce"=>contacting.contact_behaviour=Some(model::ContactingBehaviour::Elastic(u32::MAX)), "Bounce"=>contacting.contact_behaviour=Some(attr::ContactingBehaviour::Elastic(u32::MAX)),
"Surf"=>contacting.contact_behaviour=Some(model::ContactingBehaviour::Surf), "Surf"=>contacting.contact_behaviour=Some(attr::ContactingBehaviour::Surf),
"Ladder"=>contacting.contact_behaviour=Some(model::ContactingBehaviour::Ladder(model::ContactingLadder{sticky:true})), "Ladder"=>contacting.contact_behaviour=Some(attr::ContactingBehaviour::Ladder(attr::ContactingLadder{sticky:true})),
_=>(), _=>(),
} }
model::CollisionAttributes::Contact{contacting,general} attr::CollisionAttributes::Contact{contacting,general}
}, },
false=>if force_intersecting false=>if force_intersecting
||general.any() ||general.any()
||intersecting.any() ||intersecting.any()
{ {
model::CollisionAttributes::Intersect{intersecting,general} attr::CollisionAttributes::Intersect{intersecting,general}
}else{ }else{
model::CollisionAttributes::Decoration attr::CollisionAttributes::Decoration
}, },
} }
} }
struct RobloxAssetId(u64);
struct RobloxAssetIdParseErr;
impl std::str::FromStr for RobloxAssetId {
type Err=RobloxAssetIdParseErr;
fn from_str(s: &str) -> Result<Self, Self::Err>{
let regman=lazy_regex::regex!(r"(\d+)$");
if let Some(captures) = regman.captures(s) {
if captures.len()==2{//captures[0] is all captures concatenated, and then each individual capture
if let Ok(id) = captures[0].parse::<u64>() {
return Ok(Self(id));
}
}
}
Err(RobloxAssetIdParseErr)
}
}
#[derive(Clone,Copy,PartialEq)] #[derive(Clone,Copy,PartialEq)]
struct RobloxTextureTransform{ struct RobloxTextureTransform{
offset_u:f32, offset_u:f32,
@ -180,12 +346,12 @@ struct RobloxTextureTransform{
} }
impl std::cmp::Eq for RobloxTextureTransform{}//???? impl std::cmp::Eq for RobloxTextureTransform{}//????
impl std::default::Default for RobloxTextureTransform{ impl std::default::Default for RobloxTextureTransform{
fn default() -> Self { fn default()->Self{
Self{offset_u:0.0,offset_v:0.0,scale_u:1.0,scale_v:1.0} Self{offset_u:0.0,offset_v:0.0,scale_u:1.0,scale_v:1.0}
} }
} }
impl std::hash::Hash for RobloxTextureTransform { impl std::hash::Hash for RobloxTextureTransform{
fn hash<H: std::hash::Hasher>(&self, state: &mut H) { fn hash<H:std::hash::Hasher>(&self,state:&mut H) {
self.offset_u.to_ne_bytes().hash(state); self.offset_u.to_ne_bytes().hash(state);
self.offset_v.to_ne_bytes().hash(state); self.offset_v.to_ne_bytes().hash(state);
self.scale_u.to_ne_bytes().hash(state); self.scale_u.to_ne_bytes().hash(state);
@ -194,16 +360,16 @@ impl std::hash::Hash for RobloxTextureTransform {
} }
#[derive(Clone,PartialEq)] #[derive(Clone,PartialEq)]
struct RobloxFaceTextureDescription{ struct RobloxFaceTextureDescription{
texture:u32, render:RenderConfigId,
color:glam::Vec4, color:glam::Vec4,
transform:RobloxTextureTransform, transform:RobloxTextureTransform,
} }
impl std::cmp::Eq for RobloxFaceTextureDescription{}//???? impl std::cmp::Eq for RobloxFaceTextureDescription{}//????
impl std::hash::Hash for RobloxFaceTextureDescription { impl std::hash::Hash for RobloxFaceTextureDescription{
fn hash<H: std::hash::Hasher>(&self, state: &mut H) { fn hash<H:std::hash::Hasher>(&self,state:&mut H){
self.texture.hash(state); self.render.hash(state);
self.transform.hash(state); self.transform.hash(state);
for &el in self.color.as_ref().iter() { for &el in self.color.as_ref().iter(){
el.to_ne_bytes().hash(state); el.to_ne_bytes().hash(state);
} }
} }
@ -211,7 +377,7 @@ impl std::hash::Hash for RobloxFaceTextureDescription {
impl RobloxFaceTextureDescription{ impl RobloxFaceTextureDescription{
fn to_face_description(&self)->primitives::FaceDescription{ fn to_face_description(&self)->primitives::FaceDescription{
primitives::FaceDescription{ primitives::FaceDescription{
texture:Some(self.texture), render:self.render,
transform:glam::Affine2::from_translation( transform:glam::Affine2::from_translation(
glam::vec2(self.transform.offset_u,self.transform.offset_v) glam::vec2(self.transform.offset_u,self.transform.offset_v)
) )
@ -233,15 +399,29 @@ enum RobloxBasePartDescription{
Wedge(RobloxWedgeDescription), Wedge(RobloxWedgeDescription),
CornerWedge(RobloxCornerWedgeDescription), CornerWedge(RobloxCornerWedgeDescription),
} }
pub fn generate_indexed_models(dom:rbx_dom_weak::WeakDom) -> model::IndexedModelInstances{ struct ModelOwnedAttributes{
//IndexedModelInstances includes textures mesh:model::MeshId,
let mut spawn_point=Planar64Vec3::ZERO; attributes:attr::CollisionAttributes,
color:model::Color4,//transparency is in here
transform:Planar64Affine3,
}
pub fn convert<F:FnMut(&str)->Option<model::TextureId>>(dom:rbx_dom_weak::WeakDom,mut acquire_id:F)->map::CompleteMap{
let mut modes_builder=ModesBuilder::default();
let mut indexed_models=Vec::new(); let mut models1=Vec::new();
let mut model_id_from_description=std::collections::HashMap::<RobloxBasePartDescription,usize>::new(); let mut meshes=Vec::new();
let mut indexed_model_id_from_description=HashMap::new();
let mut texture_id_from_asset_id=std::collections::HashMap::<u64,u32>::new(); let mut unique_attributes=Vec::new();
let mut asset_id_from_texture_id=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();
//TODO: some sort of thing like RobloxResources that describes where to get each resource
//this would be another dependency built for downloading resources to keep this one clean
let mut unique_render_groups=vec![RenderConfig::default()];
let textureless_render_group=RenderConfigId::new(0);
let mut object_refs=Vec::new(); let mut object_refs=Vec::new();
let mut temp_objects=Vec::new(); let mut temp_objects=Vec::new();
@ -278,31 +458,8 @@ pub fn generate_indexed_models(dom:rbx_dom_weak::WeakDom) -> model::IndexedModel
continue; continue;
} }
//push TempIndexedAttributes //at this point a new model is going to be generated for sure.
let mut force_intersecting=false; let model_id=model::ModelId::new(models1.len() as u32);
let mut temp_indexing_attributes=Vec::new();
if let Some(attr)=match &object.name[..]{
"MapStart"=>{
spawn_point=model_transform.transform_point3(Planar64Vec3::ZERO)+Planar64Vec3::Y*5/2;
Some(model::TempIndexedAttributes::Start(model::TempAttrStart{mode_id:0}))
},
other=>{
let regman=lazy_regex::regex!(r"^(BonusStart|Spawn|ForceSpawn|WormholeOut)(\d+)$");
if let Some(captures) = regman.captures(other) {
match &captures[1]{
"BonusStart"=>Some(model::TempIndexedAttributes::Start(model::TempAttrStart{mode_id:captures[2].parse::<u32>().unwrap()})),
"Spawn"|"ForceSpawn"=>Some(model::TempIndexedAttributes::Spawn(model::TempAttrSpawn{mode_id:0,stage_id:captures[2].parse::<u32>().unwrap()})),
"WormholeOut"=>Some(model::TempIndexedAttributes::Wormhole(model::TempAttrWormhole{wormhole_id:captures[2].parse::<u32>().unwrap()})),
_=>None,
}
}else{
None
}
}
}{
force_intersecting=true;
temp_indexing_attributes.push(attr);
}
//TODO: also detect "CylinderMesh" etc here //TODO: also detect "CylinderMesh" etc here
let shape=match &object.class[..]{ let shape=match &object.class[..]{
@ -346,15 +503,14 @@ pub fn generate_indexed_models(dom:rbx_dom_weak::WeakDom) -> model::IndexedModel
decal.properties.get("Color3"), decal.properties.get("Color3"),
decal.properties.get("Transparency"), decal.properties.get("Transparency"),
) { ) {
if let Ok(asset_id)=content.clone().into_string().parse::<RobloxAssetId>(){ if let Some(texture_id)=acquire_id(content.as_ref()){
let texture_id=if let Some(&texture_id)=texture_id_from_asset_id.get(&asset_id.0){ //this is equivalent to a get_or_create pattern because there is a singular no-texture RenderId
texture_id //so RenderId==TextureId+1
}else{ //not the most failsafe code but this is just for the map tool lmao
let texture_id=asset_id_from_texture_id.len() as u32; if unique_render_groups.len()==texture_id.get() as usize+1{
texture_id_from_asset_id.insert(asset_id.0,texture_id); unique_render_groups.push(RenderConfig::texture(texture_id));
asset_id_from_texture_id.push(asset_id.0);
texture_id
}; };
let render_id=RenderConfigId::new(texture_id.get()+1);
let normal_id=normalid.to_u32(); let normal_id=normalid.to_u32();
if normal_id<6{ if normal_id<6{
let (roblox_texture_color,roblox_texture_transform)=if decal.class=="Texture"{ let (roblox_texture_color,roblox_texture_transform)=if decal.class=="Texture"{
@ -394,7 +550,7 @@ pub fn generate_indexed_models(dom:rbx_dom_weak::WeakDom) -> model::IndexedModel
(glam::Vec4::ONE,RobloxTextureTransform::default()) (glam::Vec4::ONE,RobloxTextureTransform::default())
}; };
part_texture_description[normal_id as usize]=Some(RobloxFaceTextureDescription{ part_texture_description[normal_id as usize]=Some(RobloxFaceTextureDescription{
texture:texture_id, render:render_id,
color:roblox_texture_color, color:roblox_texture_color,
transform:roblox_texture_transform, transform:roblox_texture_transform,
}); });
@ -436,13 +592,13 @@ pub fn generate_indexed_models(dom:rbx_dom_weak::WeakDom) -> model::IndexedModel
]), ]),
}; };
//make new model if unit cube has not been created before //make new model if unit cube has not been created before
let model_id=if let Some(&model_id)=model_id_from_description.get(&basepart_texture_description){ let indexed_model_id=if let Some(&indexed_model_id)=indexed_model_id_from_description.get(&basepart_texture_description){
//push to existing texture model //push to existing texture model
model_id indexed_model_id
}else{ }else{
let model_id=indexed_models.len(); let indexed_model_id=model::MeshId::new(meshes.len() as u32);
model_id_from_description.insert(basepart_texture_description.clone(),model_id);//borrow checker going crazy indexed_model_id_from_description.insert(basepart_texture_description.clone(),indexed_model_id);//borrow checker going crazy
indexed_models.push(match basepart_texture_description{ meshes.push(match basepart_texture_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)=>{
@ -460,7 +616,7 @@ pub fn generate_indexed_models(dom:rbx_dom_weak::WeakDom) -> model::IndexedModel
}, },
match roblox_face_description{ match roblox_face_description{
Some(roblox_texture_transform)=>roblox_texture_transform.to_face_description(), Some(roblox_texture_transform)=>roblox_texture_transform.to_face_description(),
None=>primitives::FaceDescription::default(), None=>primitives::FaceDescription::new_with_render_id(textureless_render_group),
}); });
} }
primitives::generate_partial_unit_cube(cube_face_description) primitives::generate_partial_unit_cube(cube_face_description)
@ -479,7 +635,7 @@ pub fn generate_indexed_models(dom:rbx_dom_weak::WeakDom) -> model::IndexedModel
}, },
match roblox_face_description{ match roblox_face_description{
Some(roblox_texture_transform)=>roblox_texture_transform.to_face_description(), Some(roblox_texture_transform)=>roblox_texture_transform.to_face_description(),
None=>primitives::FaceDescription::default(), None=>primitives::FaceDescription::new_with_render_id(textureless_render_group),
}); });
} }
primitives::generate_partial_unit_wedge(wedge_face_description) primitives::generate_partial_unit_wedge(wedge_face_description)
@ -498,27 +654,68 @@ pub fn generate_indexed_models(dom:rbx_dom_weak::WeakDom) -> model::IndexedModel
}, },
match roblox_face_description{ match roblox_face_description{
Some(roblox_texture_transform)=>roblox_texture_transform.to_face_description(), Some(roblox_texture_transform)=>roblox_texture_transform.to_face_description(),
None=>primitives::FaceDescription::default(), None=>primitives::FaceDescription::new_with_render_id(textureless_render_group),
}); });
} }
primitives::generate_partial_unit_cornerwedge(cornerwedge_face_description) primitives::generate_partial_unit_cornerwedge(cornerwedge_face_description)
}, },
}); });
model_id indexed_model_id
}; };
indexed_models[model_id].instances.push(model::ModelInstance { let attributes=get_attributes(
&object,
*can_collide,
Planar64Vec3::try_from([velocity.x,velocity.y,velocity.z]).unwrap(),
model_id,
&mut modes_builder,
&mut wormhole_in_model_to_id,
&mut wormhole_id_to_out_model,
);
models1.push(ModelOwnedAttributes{
mesh:indexed_model_id,
transform:model_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), color:glam::vec4(color3.r as f32/255f32, color3.g as f32/255f32, color3.b as f32/255f32, 1.0-*transparency),
attributes:get_attributes(&object.name,*can_collide,Planar64Vec3::try_from([velocity.x,velocity.y,velocity.z]).unwrap(),force_intersecting), attributes,
temp_indexing:temp_indexing_attributes,
}); });
} }
} }
} }
model::IndexedModelInstances{ let models=models1.into_iter().enumerate().map(|(model_id,mut model1)|{
textures:asset_id_from_texture_id.iter().map(|t|t.to_string()).collect(), let model_id=model::ModelId::new(model_id as u32);
models:indexed_models, //update attributes with wormhole id
spawn_point, //TODO: errors/prints
modes:Vec::new(), 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){
match &mut model1.attributes{
attr::CollisionAttributes::Contact{contacting:_,general}
|attr::CollisionAttributes::Intersect{intersecting:_,general}
=>general.wormhole=Some(attr::Wormhole{destination_model:wormhole_out_model_id}),
attr::CollisionAttributes::Decoration=>println!("Not a wormhole"),
}
}
}
//index the attributes
let attributes_id=if let Some(&attributes_id)=attributes_id_from_attributes.get(&model1.attributes){
attributes_id
}else{
let attributes_id=attr::CollisionAttributesId::new(unique_attributes.len() as u32);
attributes_id_from_attributes.insert(model1.attributes.clone(),attributes_id);
unique_attributes.push(model1.attributes);
attributes_id
};
model::Model{
mesh:model1.mesh,
transform:model1.transform,
color:model1.color,
attributes:attributes_id,
}
}).collect();
map::CompleteMap{
render_configs:unique_render_groups,//asset_id_from_texture_id.iter().map(|t|t.to_string()).collect(),
meshes,
models,
modes:modes_builder.build(),
attributes:unique_attributes,
} }
} }