diff --git a/lib/bsp_loader/src/bsp.rs b/lib/bsp_loader/src/bsp.rs index def11ee..d999f4a 100644 --- a/lib/bsp_loader/src/bsp.rs +++ b/lib/bsp_loader/src/bsp.rs @@ -1,127 +1,150 @@ -use strafesnet_common::{map,model,integer,gameplay_attributes}; +use strafesnet_common::{gameplay_attributes, integer, map, model}; -const VALVE_SCALE:f32=1.0/16.0; -fn valve_transform([x,y,z]:[f32;3])->integer::Planar64Vec3{ - integer::vec3::try_from_f32_array([x*VALVE_SCALE,z*VALVE_SCALE,-y*VALVE_SCALE]).unwrap() +const VALVE_SCALE: f32 = 1.0 / 16.0; +fn valve_transform([x, y, z]: [f32; 3]) -> integer::Planar64Vec3 { + integer::vec3::try_from_f32_array([x * VALVE_SCALE, z * VALVE_SCALE, -y * VALVE_SCALE]).unwrap() } -pub fn convert_bsp( - bsp:&vbsp::Bsp, - mut acquire_render_config_id:AcquireRenderConfigId, - mut acquire_mesh_id:AcquireMeshId -)->PartialMap1 +pub fn convert_bsp( + bsp: &vbsp::Bsp, + mut acquire_render_config_id: AcquireRenderConfigId, + mut acquire_mesh_id: AcquireMeshId, +) -> PartialMap1 where - AcquireRenderConfigId:FnMut(Option<&str>)->model::RenderConfigId, - AcquireMeshId:FnMut(&str)->model::MeshId, + AcquireRenderConfigId: FnMut(Option<&str>) -> model::RenderConfigId, + AcquireMeshId: FnMut(&str) -> model::MeshId, { - //figure out real attributes later - let mut unique_attributes=Vec::new(); - unique_attributes.push(gameplay_attributes::CollisionAttributes::Decoration); - const TEMP_TOUCH_ME_ATTRIBUTE:gameplay_attributes::CollisionAttributesId=gameplay_attributes::CollisionAttributesId::new(0); + //figure out real attributes later + let mut unique_attributes = Vec::new(); + unique_attributes.push(gameplay_attributes::CollisionAttributes::Decoration); + const TEMP_TOUCH_ME_ATTRIBUTE: gameplay_attributes::CollisionAttributesId = + gameplay_attributes::CollisionAttributesId::new(0); - let mut prop_mesh_count=0; - //declare all prop models to Loader - let prop_models=bsp.static_props().map(|prop|{ - //get or create mesh_id - let mesh_id=acquire_mesh_id(prop.model()); - //not the most failsafe code but this is just for the map tool lmao - if prop_mesh_count==mesh_id.get(){ - prop_mesh_count+=1; - }; - let placement=prop.as_prop_placement(); - model::Model{ - mesh:mesh_id, - attributes:TEMP_TOUCH_ME_ATTRIBUTE, - transform:integer::Planar64Affine3::new( - integer::mat3::try_from_f32_array_2d(( - glam::Mat3A::from_diagonal(glam::Vec3::splat(placement.scale)) + let mut prop_mesh_count = 0; + //declare all prop models to Loader + let prop_models = bsp + .static_props() + .map(|prop| { + //get or create mesh_id + let mesh_id = acquire_mesh_id(prop.model()); + //not the most failsafe code but this is just for the map tool lmao + if prop_mesh_count == mesh_id.get() { + prop_mesh_count += 1; + }; + let placement = prop.as_prop_placement(); + model::Model { + mesh: mesh_id, + attributes: TEMP_TOUCH_ME_ATTRIBUTE, + transform: integer::Planar64Affine3::new( + integer::mat3::try_from_f32_array_2d( + (glam::Mat3A::from_diagonal(glam::Vec3::splat(placement.scale)) //TODO: figure this out - *glam::Mat3A::from_quat(glam::Quat::from_array(placement.rotation.into())) - ).to_cols_array_2d()).unwrap(), - valve_transform(placement.origin.into()), - ), - color:glam::Vec4::ONE, - } - }).collect(); + *glam::Mat3A::from_quat(glam::Quat::from_array(placement.rotation.into()))) + .to_cols_array_2d(), + ) + .unwrap(), + valve_transform(placement.origin.into()), + ), + color: glam::Vec4::ONE, + } + }) + .collect(); - //TODO: make the main map one single mesh with a bunch of different physics groups and graphics groups + //TODO: make the main map one single mesh with a bunch of different physics groups and graphics groups - //the generated MeshIds in here will collide with the Loader Mesh Ids - //but I can't think of a good workaround other than just remapping one later. - let world_meshes:Vec=bsp.models().map(|world_model|{ - //non-deduplicated - let mut spam_pos=Vec::new(); - let mut spam_tex=Vec::new(); - let mut spam_normal=Vec::new(); - let mut spam_vertices=Vec::new(); - let mut graphics_groups=Vec::new(); - let mut physics_group=model::IndexedPhysicsGroup::default(); - let polygon_groups=world_model.faces().enumerate().map(|(polygon_group_id,face)|{ - let polygon_group_id=model::PolygonGroupId::new(polygon_group_id as u32); - let face_texture=face.texture(); - let face_texture_data=face_texture.texture_data(); - //this would be better as a 4x2 matrix - let texture_transform_u=glam::Vec4::from_array(face_texture.texture_transforms_u)/(face_texture_data.width as f32); - let texture_transform_v=glam::Vec4::from_array(face_texture.texture_transforms_v)/(face_texture_data.height as f32); + //the generated MeshIds in here will collide with the Loader Mesh Ids + //but I can't think of a good workaround other than just remapping one later. + let world_meshes: Vec = bsp + .models() + .map(|world_model| { + //non-deduplicated + let mut spam_pos = Vec::new(); + let mut spam_tex = Vec::new(); + let mut spam_normal = Vec::new(); + let mut spam_vertices = Vec::new(); + let mut graphics_groups = Vec::new(); + let mut physics_group = model::IndexedPhysicsGroup::default(); + let polygon_groups = world_model + .faces() + .enumerate() + .map(|(polygon_group_id, face)| { + let polygon_group_id = model::PolygonGroupId::new(polygon_group_id as u32); + let face_texture = face.texture(); + let face_texture_data = face_texture.texture_data(); + //this would be better as a 4x2 matrix + let texture_transform_u = + glam::Vec4::from_array(face_texture.texture_transforms_u) + / (face_texture_data.width as f32); + let texture_transform_v = + glam::Vec4::from_array(face_texture.texture_transforms_v) + / (face_texture_data.height as f32); - //this automatically figures out what the texture is trying to do and creates - //a render config for it, and then returns the id to that render config - let render_id=acquire_render_config_id(Some(face_texture_data.name())); + //this automatically figures out what the texture is trying to do and creates + //a render config for it, and then returns the id to that render config + let render_id = acquire_render_config_id(Some(face_texture_data.name())); - //normal - let normal=face.normal(); - let normal_idx=spam_normal.len() as u32; - spam_normal.push(valve_transform(normal.into())); - let mut polygon_iter=face.vertex_positions().map(|vertex_position|{ - //world_model.origin seems to always be 0,0,0 - let vertex_xyz=(world_model.origin+vertex_position).into(); - let pos_idx=spam_pos.len(); - spam_pos.push(valve_transform(vertex_xyz)); + //normal + let normal = face.normal(); + let normal_idx = spam_normal.len() as u32; + spam_normal.push(valve_transform(normal.into())); + let mut polygon_iter = face.vertex_positions().map(|vertex_position| { + //world_model.origin seems to always be 0,0,0 + let vertex_xyz = (world_model.origin + vertex_position).into(); + let pos_idx = spam_pos.len(); + spam_pos.push(valve_transform(vertex_xyz)); - //calculate texture coordinates - let pos=glam::Vec3::from_array(vertex_xyz).extend(1.0); - let tex=glam::vec2(texture_transform_u.dot(pos),texture_transform_v.dot(pos)); - let tex_idx=spam_tex.len() as u32; - spam_tex.push(tex); + //calculate texture coordinates + let pos = glam::Vec3::from_array(vertex_xyz).extend(1.0); + let tex = + glam::vec2(texture_transform_u.dot(pos), texture_transform_v.dot(pos)); + let tex_idx = spam_tex.len() as u32; + spam_tex.push(tex); - let vertex_id=model::VertexId::new(spam_vertices.len() as u32); - spam_vertices.push(model::IndexedVertex{ - pos:model::PositionId::new(pos_idx as u32), - tex:model::TextureCoordinateId::new(tex_idx as u32), - normal:model::NormalId::new(normal_idx), - color:model::ColorId::new(0), - }); - vertex_id - }); - let polygon_list=std::iter::from_fn(move||{ - match (polygon_iter.next(),polygon_iter.next(),polygon_iter.next()){ - (Some(v1),Some(v2),Some(v3))=>Some(vec![v1,v2,v3]), - //ignore extra vertices, not sure what to do in this case, failing the whole conversion could be appropriate - _=>None, - } - }).collect(); - if face.is_visible(){ - //TODO: deduplicate graphics groups by render id - graphics_groups.push(model::IndexedGraphicsGroup{ - render:render_id, - groups:vec![polygon_group_id], - }) - } - physics_group.groups.push(polygon_group_id); - model::PolygonGroup::PolygonList(model::PolygonList::new(polygon_list)) - }).collect(); - model::Mesh{ - unique_pos:spam_pos, - unique_tex:spam_tex, - unique_normal:spam_normal, - unique_color:vec![glam::Vec4::ONE], - unique_vertices:spam_vertices, - polygon_groups, - graphics_groups, - physics_groups:vec![physics_group], - } - }).collect(); + let vertex_id = model::VertexId::new(spam_vertices.len() as u32); + spam_vertices.push(model::IndexedVertex { + pos: model::PositionId::new(pos_idx as u32), + tex: model::TextureCoordinateId::new(tex_idx as u32), + normal: model::NormalId::new(normal_idx), + color: model::ColorId::new(0), + }); + vertex_id + }); + let polygon_list = std::iter::from_fn(move || { + match ( + polygon_iter.next(), + polygon_iter.next(), + polygon_iter.next(), + ) { + (Some(v1), Some(v2), Some(v3)) => Some(vec![v1, v2, v3]), + //ignore extra vertices, not sure what to do in this case, failing the whole conversion could be appropriate + _ => None, + } + }) + .collect(); + if face.is_visible() { + //TODO: deduplicate graphics groups by render id + graphics_groups.push(model::IndexedGraphicsGroup { + render: render_id, + groups: vec![polygon_group_id], + }) + } + physics_group.groups.push(polygon_group_id); + model::PolygonGroup::PolygonList(model::PolygonList::new(polygon_list)) + }) + .collect(); + model::Mesh { + unique_pos: spam_pos, + unique_tex: spam_tex, + unique_normal: spam_normal, + unique_color: vec![glam::Vec4::ONE], + unique_vertices: spam_vertices, + polygon_groups, + graphics_groups, + physics_groups: vec![physics_group], + } + }) + .collect(); - let world_models:Vec= + let world_models:Vec= //one instance of the main world mesh std::iter::once(( //world_model @@ -158,35 +181,35 @@ where } }).collect(); - PartialMap1{ - attributes:unique_attributes, - world_meshes, - prop_models, - world_models, - modes:strafesnet_common::gameplay_modes::Modes::new(Vec::new()), - } + PartialMap1 { + attributes: unique_attributes, + world_meshes, + prop_models, + world_models, + modes: strafesnet_common::gameplay_modes::Modes::new(Vec::new()), + } } //partially constructed map types -pub struct PartialMap1{ - attributes:Vec, - prop_models:Vec, - world_meshes:Vec, - world_models:Vec, - modes:strafesnet_common::gameplay_modes::Modes, +pub struct PartialMap1 { + attributes: Vec, + prop_models: Vec, + world_meshes: Vec, + world_models: Vec, + modes: strafesnet_common::gameplay_modes::Modes, } -impl PartialMap1{ - pub fn add_prop_meshes( - self, - prop_meshes:impl IntoIterator, - mut acquire_render_config_id:AcquireRenderConfigId, - )->PartialMap2 - where - AcquireRenderConfigId:FnMut(Option<&str>)->model::RenderConfigId, - { - PartialMap2{ - attributes:self.attributes, - prop_meshes:prop_meshes.into_iter().filter_map(|(mesh_id,model_data)| +impl PartialMap1 { + pub fn add_prop_meshes( + self, + prop_meshes: impl IntoIterator, + mut acquire_render_config_id: AcquireRenderConfigId, + ) -> PartialMap2 + where + AcquireRenderConfigId: FnMut(Option<&str>) -> model::RenderConfigId, + { + PartialMap2 { + attributes: self.attributes, + prop_meshes: prop_meshes.into_iter().filter_map(|(mesh_id,model_data)| //this will generate new render ids and texture ids match convert_mesh(model_data,&mut acquire_render_config_id){ Ok(mesh)=>Some((mesh_id,mesh)), @@ -196,138 +219,175 @@ impl PartialMap1{ } } ).collect(), - prop_models:self.prop_models, - world_meshes:self.world_meshes, - world_models:self.world_models, - modes:self.modes, - } - } + prop_models: self.prop_models, + world_meshes: self.world_meshes, + world_models: self.world_models, + modes: self.modes, + } + } } -pub struct PartialMap2{ - attributes:Vec, - prop_meshes:Vec<(model::MeshId,model::Mesh)>, - prop_models:Vec, - world_meshes:Vec, - world_models:Vec, - modes:strafesnet_common::gameplay_modes::Modes, +pub struct PartialMap2 { + attributes: Vec, + prop_meshes: Vec<(model::MeshId, model::Mesh)>, + prop_models: Vec, + world_meshes: Vec, + world_models: Vec, + modes: strafesnet_common::gameplay_modes::Modes, } -impl PartialMap2{ - pub fn add_render_configs_and_textures( - mut self, - render_configs:impl IntoIterator, - textures:impl IntoIterator)>, - )->map::CompleteMap{ - //merge mesh and model lists, flatten and remap all ids - let mesh_id_offset=self.world_meshes.len(); - println!("prop_meshes.len()={}",self.prop_meshes.len()); - let (mut prop_meshes,prop_mesh_id_map):(Vec,std::collections::HashMap) - =self.prop_meshes.into_iter().enumerate().map(|(new_mesh_id,(old_mesh_id,mesh))|{ - (mesh,(old_mesh_id,model::MeshId::new((mesh_id_offset+new_mesh_id) as u32))) - }).unzip(); - self.world_meshes.append(&mut prop_meshes); - //there is no modes or runtime behaviour with references to the model ids currently - //so just relentlessly cull them if the mesh is missing - self.world_models.extend(self.prop_models.into_iter().filter_map(|mut model| - prop_mesh_id_map.get(&model.mesh).map(|&new_mesh_id|{ - model.mesh=new_mesh_id; - model - }) - )); - //let mut models=Vec::new(); - let (textures,texture_id_map):(Vec>,std::collections::HashMap) - =textures.into_iter() - //.filter_map(f) cull unused textures - .enumerate().map(|(new_texture_id,(old_texture_id,texture))|{ - (texture,(old_texture_id,model::TextureId::new(new_texture_id as u32))) - }).unzip(); - let render_configs=render_configs.into_iter().map(|(render_config_id,mut render_config)|{ - //this may generate duplicate no-texture render configs but idc - render_config.texture=render_config.texture.and_then(|texture_id| - texture_id_map.get(&texture_id).copied() - ); - render_config - }).collect(); - map::CompleteMap{ - modes:self.modes, - attributes:self.attributes, - meshes:self.world_meshes, - models:self.world_models, - textures, - render_configs, - } - } +impl PartialMap2 { + pub fn add_render_configs_and_textures( + mut self, + render_configs: impl IntoIterator, + textures: impl IntoIterator)>, + ) -> map::CompleteMap { + //merge mesh and model lists, flatten and remap all ids + let mesh_id_offset = self.world_meshes.len(); + println!("prop_meshes.len()={}", self.prop_meshes.len()); + let (mut prop_meshes, prop_mesh_id_map): ( + Vec, + std::collections::HashMap, + ) = self + .prop_meshes + .into_iter() + .enumerate() + .map(|(new_mesh_id, (old_mesh_id, mesh))| { + ( + mesh, + ( + old_mesh_id, + model::MeshId::new((mesh_id_offset + new_mesh_id) as u32), + ), + ) + }) + .unzip(); + self.world_meshes.append(&mut prop_meshes); + //there is no modes or runtime behaviour with references to the model ids currently + //so just relentlessly cull them if the mesh is missing + self.world_models + .extend(self.prop_models.into_iter().filter_map(|mut model| { + prop_mesh_id_map.get(&model.mesh).map(|&new_mesh_id| { + model.mesh = new_mesh_id; + model + }) + })); + //let mut models=Vec::new(); + let (textures, texture_id_map): ( + Vec>, + std::collections::HashMap, + ) = textures + .into_iter() + //.filter_map(f) cull unused textures + .enumerate() + .map(|(new_texture_id, (old_texture_id, texture))| { + ( + texture, + (old_texture_id, model::TextureId::new(new_texture_id as u32)), + ) + }) + .unzip(); + let render_configs = render_configs + .into_iter() + .map(|(render_config_id, mut render_config)| { + //this may generate duplicate no-texture render configs but idc + render_config.texture = render_config + .texture + .and_then(|texture_id| texture_id_map.get(&texture_id).copied()); + render_config + }) + .collect(); + map::CompleteMap { + modes: self.modes, + attributes: self.attributes, + meshes: self.world_meshes, + models: self.world_models, + textures, + render_configs, + } + } } fn convert_mesh( - model_data:crate::data::ModelData, - acquire_render_config_id:&mut AcquireRenderConfigId, -)->Result + model_data: crate::data::ModelData, + acquire_render_config_id: &mut AcquireRenderConfigId, +) -> Result where - AcquireRenderConfigId:FnMut(Option<&str>)->model::RenderConfigId, + AcquireRenderConfigId: FnMut(Option<&str>) -> model::RenderConfigId, { - let model=model_data.read_model()?; - let texture_paths=model.texture_directories(); - if texture_paths.len()!=1{ - println!("WARNING: multiple texture paths"); - } - let skin=model.skin_tables().nth(0).unwrap(); + let model = model_data.read_model()?; + let texture_paths = model.texture_directories(); + if texture_paths.len() != 1 { + println!("WARNING: multiple texture paths"); + } + let skin = model.skin_tables().nth(0).unwrap(); - let mut spam_pos=Vec::with_capacity(model.vertices().len()); - let mut spam_normal=Vec::with_capacity(model.vertices().len()); - let mut spam_tex=Vec::with_capacity(model.vertices().len()); - let mut spam_vertices=Vec::with_capacity(model.vertices().len()); - for (i,vertex) in model.vertices().iter().enumerate(){ - spam_pos.push(valve_transform(vertex.position.into())); - spam_normal.push(valve_transform(vertex.normal.into())); - spam_tex.push(glam::Vec2::from_array(vertex.texture_coordinates)); - spam_vertices.push(model::IndexedVertex{ - pos:model::PositionId::new(i as u32), - tex:model::TextureCoordinateId::new(i as u32), - normal:model::NormalId::new(i as u32), - color:model::ColorId::new(0), - }); - } - let mut graphics_groups=Vec::new(); - let mut physics_groups=Vec::new(); - let polygon_groups=model.meshes().enumerate().map(|(polygon_group_id,mesh)|{ - let polygon_group_id=model::PolygonGroupId::new(polygon_group_id as u32); + let mut spam_pos = Vec::with_capacity(model.vertices().len()); + let mut spam_normal = Vec::with_capacity(model.vertices().len()); + let mut spam_tex = Vec::with_capacity(model.vertices().len()); + let mut spam_vertices = Vec::with_capacity(model.vertices().len()); + for (i, vertex) in model.vertices().iter().enumerate() { + spam_pos.push(valve_transform(vertex.position.into())); + spam_normal.push(valve_transform(vertex.normal.into())); + spam_tex.push(glam::Vec2::from_array(vertex.texture_coordinates)); + spam_vertices.push(model::IndexedVertex { + pos: model::PositionId::new(i as u32), + tex: model::TextureCoordinateId::new(i as u32), + normal: model::NormalId::new(i as u32), + color: model::ColorId::new(0), + }); + } + let mut graphics_groups = Vec::new(); + let mut physics_groups = Vec::new(); + let polygon_groups = model + .meshes() + .enumerate() + .map(|(polygon_group_id, mesh)| { + let polygon_group_id = model::PolygonGroupId::new(polygon_group_id as u32); - let render_id=if let (Some(texture_path),Some(texture_name))=(texture_paths.get(0),skin.texture(mesh.material_index())){ - let mut path=std::path::PathBuf::from(texture_path.as_str()); - path.push(texture_name); - acquire_render_config_id(path.as_os_str().to_str()) - }else{ - acquire_render_config_id(None) - }; + let render_id = if let (Some(texture_path), Some(texture_name)) = + (texture_paths.get(0), skin.texture(mesh.material_index())) + { + let mut path = std::path::PathBuf::from(texture_path.as_str()); + path.push(texture_name); + acquire_render_config_id(path.as_os_str().to_str()) + } else { + acquire_render_config_id(None) + }; - graphics_groups.push(model::IndexedGraphicsGroup{ - render:render_id, - groups:vec![polygon_group_id], - }); - physics_groups.push(model::IndexedPhysicsGroup{ - groups:vec![polygon_group_id], - }); - model::PolygonGroup::PolygonList(model::PolygonList::new( - //looking at the code, it would seem that the strips are pre-deindexed into triangle lists when calling this function - mesh.vertex_strip_indices().flat_map(|mut strip| - std::iter::from_fn(move||{ - match (strip.next(),strip.next(),strip.next()){ - (Some(v1),Some(v2),Some(v3))=>Some([v1,v2,v3].map(|vertex_id|model::VertexId::new(vertex_id as u32)).to_vec()), - //ignore extra vertices, not sure what to do in this case, failing the whole conversion could be appropriate - _=>None, - } - }) - ).collect() - )) - }).collect(); - Ok(model::Mesh{ - unique_pos:spam_pos, - unique_normal:spam_normal, - unique_tex:spam_tex, - unique_color:vec![glam::Vec4::ONE], - unique_vertices:spam_vertices, - polygon_groups, - graphics_groups, - physics_groups, - }) + graphics_groups.push(model::IndexedGraphicsGroup { + render: render_id, + groups: vec![polygon_group_id], + }); + physics_groups.push(model::IndexedPhysicsGroup { + groups: vec![polygon_group_id], + }); + model::PolygonGroup::PolygonList(model::PolygonList::new( + //looking at the code, it would seem that the strips are pre-deindexed into triangle lists when calling this function + mesh.vertex_strip_indices() + .flat_map(|mut strip| { + std::iter::from_fn(move || { + match (strip.next(), strip.next(), strip.next()) { + (Some(v1), Some(v2), Some(v3)) => Some( + [v1, v2, v3] + .map(|vertex_id| model::VertexId::new(vertex_id as u32)) + .to_vec(), + ), + //ignore extra vertices, not sure what to do in this case, failing the whole conversion could be appropriate + _ => None, + } + }) + }) + .collect(), + )) + }) + .collect(); + Ok(model::Mesh { + unique_pos: spam_pos, + unique_normal: spam_normal, + unique_tex: spam_tex, + unique_color: vec![glam::Vec4::ONE], + unique_vertices: spam_vertices, + polygon_groups, + graphics_groups, + physics_groups, + }) } diff --git a/lib/bsp_loader/src/data.rs b/lib/bsp_loader/src/data.rs index 83ca678..a330920 100644 --- a/lib/bsp_loader/src/data.rs +++ b/lib/bsp_loader/src/data.rs @@ -1,60 +1,60 @@ pub struct Bsp(vbsp::Bsp); -impl Bsp{ - pub const fn new(value:vbsp::Bsp)->Self{ - Self(value) - } +impl Bsp { + pub const fn new(value: vbsp::Bsp) -> Self { + Self(value) + } } -impl AsRef for Bsp{ - fn as_ref(&self)->&vbsp::Bsp{ - &self.0 - } +impl AsRef for Bsp { + fn as_ref(&self) -> &vbsp::Bsp { + &self.0 + } } pub struct MdlData(Vec); -impl MdlData{ - pub const fn new(value:Vec)->Self{ - Self(value) - } +impl MdlData { + pub const fn new(value: Vec) -> Self { + Self(value) + } } -impl AsRef<[u8]> for MdlData{ - fn as_ref(&self)->&[u8]{ - self.0.as_ref() - } +impl AsRef<[u8]> for MdlData { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } } pub struct VtxData(Vec); -impl VtxData{ - pub const fn new(value:Vec)->Self{ - Self(value) - } +impl VtxData { + pub const fn new(value: Vec) -> Self { + Self(value) + } } -impl AsRef<[u8]> for VtxData{ - fn as_ref(&self)->&[u8]{ - self.0.as_ref() - } +impl AsRef<[u8]> for VtxData { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } } pub struct VvdData(Vec); -impl VvdData{ - pub const fn new(value:Vec)->Self{ - Self(value) - } +impl VvdData { + pub const fn new(value: Vec) -> Self { + Self(value) + } } -impl AsRef<[u8]> for VvdData{ - fn as_ref(&self)->&[u8]{ - self.0.as_ref() - } +impl AsRef<[u8]> for VvdData { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } } -pub struct ModelData{ - pub mdl:MdlData, - pub vtx:VtxData, - pub vvd:VvdData, +pub struct ModelData { + pub mdl: MdlData, + pub vtx: VtxData, + pub vvd: VvdData, +} +impl ModelData { + pub fn read_model(&self) -> Result { + Ok(vmdl::Model::from_parts( + vmdl::mdl::Mdl::read(self.mdl.as_ref())?, + vmdl::vtx::Vtx::read(self.vtx.as_ref())?, + vmdl::vvd::Vvd::read(self.vvd.as_ref())?, + )) + } } -impl ModelData{ - pub fn read_model(&self)->Result{ - Ok(vmdl::Model::from_parts( - vmdl::mdl::Mdl::read(self.mdl.as_ref())?, - vmdl::vtx::Vtx::read(self.vtx.as_ref())?, - vmdl::vvd::Vvd::read(self.vvd.as_ref())?, - )) - } -} \ No newline at end of file diff --git a/lib/bsp_loader/src/lib.rs b/lib/bsp_loader/src/lib.rs index e8d4d91..884511e 100644 --- a/lib/bsp_loader/src/lib.rs +++ b/lib/bsp_loader/src/lib.rs @@ -4,34 +4,36 @@ pub mod data; pub use data::Bsp; #[derive(Debug)] -pub enum ReadError{ - Bsp(vbsp::BspError), - Io(std::io::Error), +pub enum ReadError { + Bsp(vbsp::BspError), + Io(std::io::Error), } -impl std::fmt::Display for ReadError{ - fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ - write!(f,"{self:?}") - } +impl std::fmt::Display for ReadError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{self:?}") + } } -impl std::error::Error for ReadError{} +impl std::error::Error for ReadError {} -pub fn read(mut input:R)->Result{ - let mut s=Vec::new(); +pub fn read(mut input: R) -> Result { + let mut s = Vec::new(); - //TODO: mmap - input.read_to_end(&mut s).map_err(ReadError::Io)?; + //TODO: mmap + input.read_to_end(&mut s).map_err(ReadError::Io)?; - vbsp::Bsp::read(s.as_slice()).map(Bsp::new).map_err(ReadError::Bsp) + vbsp::Bsp::read(s.as_slice()) + .map(Bsp::new) + .map_err(ReadError::Bsp) } -pub fn convert( - bsp:&Bsp, - acquire_render_config_id:AcquireRenderConfigId, - acquire_mesh_id:AcquireMeshId -)->bsp::PartialMap1 +pub fn convert( + bsp: &Bsp, + acquire_render_config_id: AcquireRenderConfigId, + acquire_mesh_id: AcquireMeshId, +) -> bsp::PartialMap1 where - AcquireRenderConfigId:FnMut(Option<&str>)->strafesnet_common::model::RenderConfigId, - AcquireMeshId:FnMut(&str)->strafesnet_common::model::MeshId, + AcquireRenderConfigId: FnMut(Option<&str>) -> strafesnet_common::model::RenderConfigId, + AcquireMeshId: FnMut(&str) -> strafesnet_common::model::MeshId, { - bsp::convert_bsp(bsp.as_ref(),acquire_render_config_id,acquire_mesh_id) -} \ No newline at end of file + bsp::convert_bsp(bsp.as_ref(), acquire_render_config_id, acquire_mesh_id) +} diff --git a/lib/common/src/aabb.rs b/lib/common/src/aabb.rs index 5e244d2..77a9ef8 100644 --- a/lib/common/src/aabb.rs +++ b/lib/common/src/aabb.rs @@ -1,56 +1,59 @@ -use crate::integer::{vec3,Planar64Vec3}; +use crate::integer::{vec3, Planar64Vec3}; #[derive(Clone)] -pub struct Aabb{ - min:Planar64Vec3, - max:Planar64Vec3, +pub struct Aabb { + min: Planar64Vec3, + max: Planar64Vec3, } -impl Default for Aabb{ - fn default()->Self{ - Self{min:vec3::MAX,max:vec3::MIN} - } +impl Default for Aabb { + fn default() -> Self { + Self { + min: vec3::MAX, + max: vec3::MIN, + } + } } -impl Aabb{ - pub const fn new(min:Planar64Vec3,max:Planar64Vec3)->Self{ - Self{min,max} - } - pub const fn max(&self)->Planar64Vec3{ - self.max - } - pub const fn min(&self)->Planar64Vec3{ - self.min - } - pub fn grow(&mut self,point:Planar64Vec3){ - self.min=self.min.min(point); - self.max=self.max.max(point); - } - pub fn join(&mut self,aabb:&Aabb){ - self.min=self.min.min(aabb.min); - self.max=self.max.max(aabb.max); - } - pub fn inflate(&mut self,hs:Planar64Vec3){ - self.min-=hs; - self.max+=hs; - } - pub fn intersects(&self,aabb:&Aabb)->bool{ - let bvec=self.min.lt(aabb.max)&aabb.min.lt(self.max); - bvec.all() - } - pub fn size(&self)->Planar64Vec3{ - self.max-self.min - } - pub fn center(&self)->Planar64Vec3{ - self.min+(self.max-self.min)>>1 - } - //probably use floats for area & volume because we don't care about precision - // pub fn area_weight(&self)->f32{ - // let d=self.max-self.min; - // d.x*d.y+d.y*d.z+d.z*d.x - // } - // pub fn volume(&self)->f32{ - // let d=self.max-self.min; - // d.x*d.y*d.z - // } +impl Aabb { + pub const fn new(min: Planar64Vec3, max: Planar64Vec3) -> Self { + Self { min, max } + } + pub const fn max(&self) -> Planar64Vec3 { + self.max + } + pub const fn min(&self) -> Planar64Vec3 { + self.min + } + pub fn grow(&mut self, point: Planar64Vec3) { + self.min = self.min.min(point); + self.max = self.max.max(point); + } + pub fn join(&mut self, aabb: &Aabb) { + self.min = self.min.min(aabb.min); + self.max = self.max.max(aabb.max); + } + pub fn inflate(&mut self, hs: Planar64Vec3) { + self.min -= hs; + self.max += hs; + } + pub fn intersects(&self, aabb: &Aabb) -> bool { + let bvec = self.min.lt(aabb.max) & aabb.min.lt(self.max); + bvec.all() + } + pub fn size(&self) -> Planar64Vec3 { + self.max - self.min + } + pub fn center(&self) -> Planar64Vec3 { + self.min + (self.max - self.min) >> 1 + } + //probably use floats for area & volume because we don't care about precision + // pub fn area_weight(&self)->f32{ + // let d=self.max-self.min; + // d.x*d.y+d.y*d.z+d.z*d.x + // } + // pub fn volume(&self)->f32{ + // let d=self.max-self.min; + // d.x*d.y*d.z + // } } diff --git a/lib/common/src/bvh.rs b/lib/common/src/bvh.rs index 66d0b2e..a0afc4f 100644 --- a/lib/common/src/bvh.rs +++ b/lib/common/src/bvh.rs @@ -10,185 +10,217 @@ use crate::aabb::Aabb; //sort the centerpoints on each axis (3 lists) //bv is put into octant based on whether it is upper or lower in each list -pub enum RecursiveContent{ - Branch(Vec), - Leaf(T), +pub enum RecursiveContent { + Branch(Vec), + Leaf(T), } -impl Default for RecursiveContent{ - fn default()->Self{ - Self::Branch(Vec::new()) - } +impl Default for RecursiveContent { + fn default() -> Self { + Self::Branch(Vec::new()) + } } -pub struct BvhNode{ - content:RecursiveContent,T>, - aabb:Aabb, +pub struct BvhNode { + content: RecursiveContent, T>, + aabb: Aabb, } -impl Default for BvhNode{ - fn default()->Self{ - Self{ - content:Default::default(), - aabb:Aabb::default(), - } - } +impl Default for BvhNode { + fn default() -> Self { + Self { + content: Default::default(), + aabb: Aabb::default(), + } + } } -pub struct BvhWeightNode{ - content:RecursiveContent,T>, - weight:W, - aabb:Aabb, +pub struct BvhWeightNode { + content: RecursiveContent, T>, + weight: W, + aabb: Aabb, } -impl BvhNode{ - pub fn the_tester(&self,aabb:&Aabb,f:&mut F){ - match &self.content{ - RecursiveContent::Leaf(model)=>f(model), - RecursiveContent::Branch(children)=>for child in children{ - //this test could be moved outside the match statement - //but that would test the root node aabb - //you're probably not going to spend a lot of time outside the map, - //so the test is extra work for nothing - if aabb.intersects(&child.aabb){ - child.the_tester(aabb,f); - } - }, - } - } - pub fn into_visitor(self,f:&mut F){ - match self.content{ - RecursiveContent::Leaf(model)=>f(model), - RecursiveContent::Branch(children)=>for child in children{ - child.into_visitor(f) - }, - } - } - pub fn weigh_contents,F:Fn(&T)->W>(self,f:&F)->BvhWeightNode{ - match self.content{ - RecursiveContent::Leaf(model)=>BvhWeightNode{ - weight:f(&model), - content:RecursiveContent::Leaf(model), - aabb:self.aabb, - }, - RecursiveContent::Branch(children)=>{ - let branch:Vec>=children.into_iter().map(|child| - child.weigh_contents(f) - ).collect(); - BvhWeightNode{ - weight:branch.iter().map(|node|node.weight).sum(), - content:RecursiveContent::Branch(branch), - aabb:self.aabb, - } - }, - } - } +impl BvhNode { + pub fn the_tester(&self, aabb: &Aabb, f: &mut F) { + match &self.content { + RecursiveContent::Leaf(model) => f(model), + RecursiveContent::Branch(children) => { + for child in children { + //this test could be moved outside the match statement + //but that would test the root node aabb + //you're probably not going to spend a lot of time outside the map, + //so the test is extra work for nothing + if aabb.intersects(&child.aabb) { + child.the_tester(aabb, f); + } + } + } + } + } + pub fn into_visitor(self, f: &mut F) { + match self.content { + RecursiveContent::Leaf(model) => f(model), + RecursiveContent::Branch(children) => { + for child in children { + child.into_visitor(f) + } + } + } + } + pub fn weigh_contents, F: Fn(&T) -> W>( + self, + f: &F, + ) -> BvhWeightNode { + match self.content { + RecursiveContent::Leaf(model) => BvhWeightNode { + weight: f(&model), + content: RecursiveContent::Leaf(model), + aabb: self.aabb, + }, + RecursiveContent::Branch(children) => { + let branch: Vec> = children + .into_iter() + .map(|child| child.weigh_contents(f)) + .collect(); + BvhWeightNode { + weight: branch.iter().map(|node| node.weight).sum(), + content: RecursiveContent::Branch(branch), + aabb: self.aabb, + } + } + } + } } -impl BvhWeightNode{ - pub const fn weight(&self)->&W{ - &self.weight - } - pub const fn aabb(&self)->&Aabb{ - &self.aabb - } - pub fn into_content(self)->RecursiveContent,T>{ - self.content - } - pub fn into_visitor(self,f:&mut F){ - match self.content{ - RecursiveContent::Leaf(model)=>f(model), - RecursiveContent::Branch(children)=>for child in children{ - child.into_visitor(f) - }, - } - } +impl BvhWeightNode { + pub const fn weight(&self) -> &W { + &self.weight + } + pub const fn aabb(&self) -> &Aabb { + &self.aabb + } + pub fn into_content(self) -> RecursiveContent, T> { + self.content + } + pub fn into_visitor(self, f: &mut F) { + match self.content { + RecursiveContent::Leaf(model) => f(model), + RecursiveContent::Branch(children) => { + for child in children { + child.into_visitor(f) + } + } + } + } } -pub fn generate_bvh(boxen:Vec<(T,Aabb)>)->BvhNode{ - generate_bvh_node(boxen,false) +pub fn generate_bvh(boxen: Vec<(T, Aabb)>) -> BvhNode { + generate_bvh_node(boxen, false) } -fn generate_bvh_node(boxen:Vec<(T,Aabb)>,force:bool)->BvhNode{ - let n=boxen.len(); - if force||n<20{ - let mut aabb=Aabb::default(); - let nodes=boxen.into_iter().map(|b|{ - aabb.join(&b.1); - BvhNode{ - content:RecursiveContent::Leaf(b.0), - aabb:b.1, - } - }).collect(); - BvhNode{ - content:RecursiveContent::Branch(nodes), - aabb, - } - }else{ - let mut sort_x=Vec::with_capacity(n); - let mut sort_y=Vec::with_capacity(n); - let mut sort_z=Vec::with_capacity(n); - for (i,(_,aabb)) in boxen.iter().enumerate(){ - let center=aabb.center(); - sort_x.push((i,center.x)); - sort_y.push((i,center.y)); - sort_z.push((i,center.z)); - } - sort_x.sort_by(|tup0,tup1|tup0.1.cmp(&tup1.1)); - sort_y.sort_by(|tup0,tup1|tup0.1.cmp(&tup1.1)); - sort_z.sort_by(|tup0,tup1|tup0.1.cmp(&tup1.1)); - let h=n/2; - let median_x=sort_x[h].1; - let median_y=sort_y[h].1; - let median_z=sort_z[h].1; - //locate a run of values equal to the median - //partition point gives the first index for which the predicate evaluates to false - let first_index_eq_median_x=sort_x.partition_point(|&(_,x)|x(boxen: Vec<(T, Aabb)>, force: bool) -> BvhNode { + let n = boxen.len(); + if force || n < 20 { + let mut aabb = Aabb::default(); + let nodes = boxen + .into_iter() + .map(|b| { + aabb.join(&b.1); + BvhNode { + content: RecursiveContent::Leaf(b.0), + aabb: b.1, + } + }) + .collect(); + BvhNode { + content: RecursiveContent::Branch(nodes), + aabb, + } + } else { + let mut sort_x = Vec::with_capacity(n); + let mut sort_y = Vec::with_capacity(n); + let mut sort_z = Vec::with_capacity(n); + for (i, (_, aabb)) in boxen.iter().enumerate() { + let center = aabb.center(); + sort_x.push((i, center.x)); + sort_y.push((i, center.y)); + sort_z.push((i, center.z)); + } + sort_x.sort_by(|tup0, tup1| tup0.1.cmp(&tup1.1)); + sort_y.sort_by(|tup0, tup1| tup0.1.cmp(&tup1.1)); + sort_z.sort_by(|tup0, tup1| tup0.1.cmp(&tup1.1)); + let h = n / 2; + let median_x = sort_x[h].1; + let median_y = sort_y[h].1; + let median_z = sort_z[h].1; + //locate a run of values equal to the median + //partition point gives the first index for which the predicate evaluates to false + let first_index_eq_median_x = sort_x.partition_point(|&(_, x)| x < median_x); + let first_index_eq_median_y = sort_y.partition_point(|&(_, y)| y < median_y); + let first_index_eq_median_z = sort_z.partition_point(|&(_, z)| z < median_z); + let first_index_gt_median_x = sort_x.partition_point(|&(_, x)| x <= median_x); + let first_index_gt_median_y = sort_y.partition_point(|&(_, y)| y <= median_y); + let first_index_gt_median_z = sort_z.partition_point(|&(_, z)| z <= median_z); + //pick which side median value copies go into such that both sides are as balanced as possible based on distance from n/2 + let partition_point_x = + if n.abs_diff(2 * first_index_eq_median_x) < n.abs_diff(2 * first_index_gt_median_x) { + first_index_eq_median_x + } else { + first_index_gt_median_x + }; + let partition_point_y = + if n.abs_diff(2 * first_index_eq_median_y) < n.abs_diff(2 * first_index_gt_median_y) { + first_index_eq_median_y + } else { + first_index_gt_median_y + }; + let partition_point_z = + if n.abs_diff(2 * first_index_eq_median_z) < n.abs_diff(2 * first_index_gt_median_z) { + first_index_eq_median_z + } else { + first_index_gt_median_z + }; + //this ids which octant the boxen is put in + let mut octant = vec![0; n]; + for &(i, _) in &sort_x[partition_point_x..] { + octant[i] += 1 << 0; + } + for &(i, _) in &sort_y[partition_point_y..] { + octant[i] += 1 << 1; + } + for &(i, _) in &sort_z[partition_point_z..] { + octant[i] += 1 << 2; + } + //generate lists for unique octant values + let mut list_list = Vec::with_capacity(8); + let mut octant_list = Vec::with_capacity(8); + for (i, (data, aabb)) in boxen.into_iter().enumerate() { + let octant_id = octant[i]; + let list_id = if let Some(list_id) = octant_list.iter().position(|&id| id == octant_id) + { + list_id + } else { + let list_id = list_list.len(); + octant_list.push(octant_id); + list_list.push(Vec::new()); + list_id + }; + list_list[list_id].push((data, aabb)); + } + let mut aabb = Aabb::default(); + if list_list.len() == 1 { + generate_bvh_node(list_list.remove(0), true) + } else { + BvhNode { + content: RecursiveContent::Branch( + list_list + .into_iter() + .map(|b| { + let node = generate_bvh_node(b, false); + aabb.join(&node.aabb); + node + }) + .collect(), + ), + aabb, + } + } + } } diff --git a/lib/common/src/controls_bitflag.rs b/lib/common/src/controls_bitflag.rs index a1eaf31..7b9ed4b 100644 --- a/lib/common/src/controls_bitflag.rs +++ b/lib/common/src/controls_bitflag.rs @@ -1,25 +1,25 @@ -bitflags::bitflags!{ - #[derive(Clone,Copy,Debug,Default)] - pub struct Controls:u32{ - const MoveForward=1<<0; - const MoveLeft=1<<1; - const MoveBackward=1<<2; - const MoveRight=1<<3; - const MoveUp=1<<4; - const MoveDown=1<<5; - const LookUp=1<<6; - const LookLeft=1<<7; - const LookDown=1<<8; - const LookRight=1<<9; - const Jump=1<<10; - const Crouch=1<<11; - const Sprint=1<<12; - const Zoom=1<<13; - const Use=1<<14;//Interact with object - const PrimaryAction=1<<15;//LBM/Shoot/Melee - const SecondaryAction=1<<16;//RMB/ADS/Block +bitflags::bitflags! { + #[derive(Clone,Copy,Debug,Default)] + pub struct Controls:u32{ + const MoveForward=1<<0; + const MoveLeft=1<<1; + const MoveBackward=1<<2; + const MoveRight=1<<3; + const MoveUp=1<<4; + const MoveDown=1<<5; + const LookUp=1<<6; + const LookLeft=1<<7; + const LookDown=1<<8; + const LookRight=1<<9; + const Jump=1<<10; + const Crouch=1<<11; + const Sprint=1<<12; + const Zoom=1<<13; + const Use=1<<14;//Interact with object + const PrimaryAction=1<<15;//LBM/Shoot/Melee + const SecondaryAction=1<<16;//RMB/ADS/Block - const WASD=Self::MoveForward.union(Self::MoveLeft).union(Self::MoveBackward).union(Self::MoveRight).bits(); - const WASDQE=Self::MoveForward.union(Self::MoveLeft).union(Self::MoveBackward).union(Self::MoveRight).union(Self::MoveUp).union(Self::MoveDown).bits(); - } + const WASD=Self::MoveForward.union(Self::MoveLeft).union(Self::MoveBackward).union(Self::MoveRight).bits(); + const WASDQE=Self::MoveForward.union(Self::MoveLeft).union(Self::MoveBackward).union(Self::MoveRight).union(Self::MoveUp).union(Self::MoveDown).bits(); + } } diff --git a/lib/common/src/gameplay_attributes.rs b/lib/common/src/gameplay_attributes.rs index 79dae16..e77f3e7 100644 --- a/lib/common/src/gameplay_attributes.rs +++ b/lib/common/src/gameplay_attributes.rs @@ -1,88 +1,106 @@ +use crate::integer::{AbsoluteTime, Planar64, Planar64Vec3}; use crate::model; -use crate::integer::{AbsoluteTime,Planar64,Planar64Vec3}; //you have this effect while in contact -#[derive(Clone,Hash,Eq,PartialEq)] -pub struct ContactingLadder{ - pub sticky:bool +#[derive(Clone, Hash, Eq, PartialEq)] +pub struct ContactingLadder { + pub sticky: bool, } -#[derive(Clone,Hash,Eq,PartialEq)] -pub enum ContactingBehaviour{ - Surf, - Ladder(ContactingLadder), - NoJump, - Cling,//usable as a zipline, or other weird and wonderful things - Elastic(u32),//[1/2^32,1] 0=None (elasticity+1)/2^32 +#[derive(Clone, Hash, Eq, PartialEq)] +pub enum ContactingBehaviour { + Surf, + Ladder(ContactingLadder), + NoJump, + Cling, //usable as a zipline, or other weird and wonderful things + Elastic(u32), //[1/2^32,1] 0=None (elasticity+1)/2^32 } //you have this effect while intersecting -#[derive(Clone,Hash,Eq,PartialEq)] -pub struct IntersectingWater{ - pub viscosity:Planar64, - pub density:Planar64, - pub velocity:Planar64Vec3, +#[derive(Clone, Hash, Eq, PartialEq)] +pub struct IntersectingWater { + pub viscosity: Planar64, + pub density: Planar64, + pub velocity: Planar64Vec3, } //All models can be given these attributes -#[derive(Clone,Hash,Eq,PartialEq)] -pub struct Accelerator{ - pub acceleration:Planar64Vec3 +#[derive(Clone, Hash, Eq, PartialEq)] +pub struct Accelerator { + pub acceleration: Planar64Vec3, } -#[derive(Clone,Hash,Eq,PartialEq)] -pub enum Booster{ - //Affine(crate::integer::Planar64Affine3),//capable of SetVelocity,DotVelocity,normal booster,bouncy part,redirect velocity, and much more - Velocity(Planar64Vec3),//straight up boost velocity adds to your current velocity - Energy{direction:Planar64Vec3,energy:Planar64},//increase energy in direction - AirTime(AbsoluteTime),//increase airtime, invariant across mass and gravity changes - Height(Planar64),//increase height, invariant across mass and gravity changes +#[derive(Clone, Hash, Eq, PartialEq)] +pub enum Booster { + //Affine(crate::integer::Planar64Affine3),//capable of SetVelocity,DotVelocity,normal booster,bouncy part,redirect velocity, and much more + Velocity(Planar64Vec3), //straight up boost velocity adds to your current velocity + Energy { + direction: Planar64Vec3, + energy: Planar64, + }, //increase energy in direction + AirTime(AbsoluteTime), //increase airtime, invariant across mass and gravity changes + Height(Planar64), //increase height, invariant across mass and gravity changes } -impl Booster{ - pub fn boost(&self,velocity:Planar64Vec3)->Planar64Vec3{ - match self{ - &Booster::Velocity(boost_velocity)=>velocity+boost_velocity, - &Booster::Energy{..}=>{ - todo!() - //let d=direction.dot(velocity); - //TODO: think about negative - //velocity+direction.with_length((d*d+energy).sqrt()-d) - }, - Booster::AirTime(_)=>todo!(), - Booster::Height(_)=>todo!(), - } - } +impl Booster { + pub fn boost(&self, velocity: Planar64Vec3) -> Planar64Vec3 { + match self { + &Booster::Velocity(boost_velocity) => velocity + boost_velocity, + &Booster::Energy { .. } => { + todo!() + //let d=direction.dot(velocity); + //TODO: think about negative + //velocity+direction.with_length((d*d+energy).sqrt()-d) + } + Booster::AirTime(_) => todo!(), + Booster::Height(_) => todo!(), + } + } } -#[derive(Clone,Hash,Eq,PartialEq)] -pub enum TrajectoryChoice{ - HighArcLongDuration,//underhand lob at target: less horizontal speed and more air time - LowArcShortDuration,//overhand throw at target: more horizontal speed and less air time +#[derive(Clone, Hash, Eq, PartialEq)] +pub enum TrajectoryChoice { + HighArcLongDuration, //underhand lob at target: less horizontal speed and more air time + LowArcShortDuration, //overhand throw at target: more horizontal speed and less air time } -#[derive(Clone,Hash,Eq,PartialEq)] -pub enum SetTrajectory{ - //Speed-type SetTrajectory - AirTime(AbsoluteTime),//air time (relative to gravity direction) is invariant across mass and gravity changes - Height(Planar64),//boost height (relative to gravity direction) is invariant across mass and gravity changes - DotVelocity{direction:Planar64Vec3,dot:Planar64},//set your velocity in a specific direction without touching other directions - //Velocity-type SetTrajectory - TargetPointTime{//launch on a trajectory that will land at a target point in a set amount of time - target_point:Planar64Vec3, - time:AbsoluteTime,//short time = fast and direct, long time = launch high in the air, negative time = wrong way - }, - TargetPointSpeed{//launch at a fixed speed and land at a target point - target_point:Planar64Vec3, - speed:Planar64,//if speed is too low this will fail to reach the target. The closest-passing trajectory will be chosen instead - trajectory_choice:TrajectoryChoice, - }, - Velocity(Planar64Vec3),//SetVelocity +#[derive(Clone, Hash, Eq, PartialEq)] +pub enum SetTrajectory { + //Speed-type SetTrajectory + AirTime(AbsoluteTime), //air time (relative to gravity direction) is invariant across mass and gravity changes + Height(Planar64), //boost height (relative to gravity direction) is invariant across mass and gravity changes + DotVelocity { + direction: Planar64Vec3, + dot: Planar64, + }, //set your velocity in a specific direction without touching other directions + //Velocity-type SetTrajectory + TargetPointTime { + //launch on a trajectory that will land at a target point in a set amount of time + target_point: Planar64Vec3, + time: AbsoluteTime, //short time = fast and direct, long time = launch high in the air, negative time = wrong way + }, + TargetPointSpeed { + //launch at a fixed speed and land at a target point + target_point: Planar64Vec3, + speed: Planar64, //if speed is too low this will fail to reach the target. The closest-passing trajectory will be chosen instead + trajectory_choice: TrajectoryChoice, + }, + Velocity(Planar64Vec3), //SetVelocity } -impl SetTrajectory{ - pub const fn is_absolute(&self)->bool{ - match self{ - SetTrajectory::AirTime(_) - |SetTrajectory::Height(_) - |SetTrajectory::DotVelocity{direction:_,dot:_}=>false, - SetTrajectory::TargetPointTime{target_point:_,time:_} - |SetTrajectory::TargetPointSpeed{target_point:_,speed:_,trajectory_choice:_} - |SetTrajectory::Velocity(_)=>true, - } - } +impl SetTrajectory { + pub const fn is_absolute(&self) -> bool { + match self { + SetTrajectory::AirTime(_) + | SetTrajectory::Height(_) + | SetTrajectory::DotVelocity { + direction: _, + dot: _, + } => false, + SetTrajectory::TargetPointTime { + target_point: _, + time: _, + } + | SetTrajectory::TargetPointSpeed { + target_point: _, + speed: _, + trajectory_choice: _, + } + | SetTrajectory::Velocity(_) => true, + } + } } // enum TrapCondition{ // FasterThan(Planar64), @@ -90,85 +108,85 @@ impl SetTrajectory{ // InRange(Planar64,Planar64), // OutsideRange(Planar64,Planar64), // } -#[derive(Clone,Hash,Eq,PartialEq)] -pub struct Wormhole{ - //destination does not need to be another wormhole - //this defines a one way portal to a destination model transform - //two of these can create a two way wormhole - pub destination_model:model::ModelId, - //(position,angles)*=origin.transform.inverse()*destination.transform +#[derive(Clone, Hash, Eq, PartialEq)] +pub struct Wormhole { + //destination does not need to be another wormhole + //this defines a one way portal to a destination model transform + //two of these can create a two way wormhole + pub destination_model: model::ModelId, + //(position,angles)*=origin.transform.inverse()*destination.transform } //attributes listed in order of handling -#[derive(Default,Clone,Hash,Eq,PartialEq)] -pub struct GeneralAttributes{ - pub booster:Option, - pub trajectory:Option, - pub wormhole:Option, - pub accelerator:Option, +#[derive(Default, Clone, Hash, Eq, PartialEq)] +pub struct GeneralAttributes { + pub booster: Option, + pub trajectory: Option, + pub wormhole: Option, + pub accelerator: Option, } -impl GeneralAttributes{ - pub const fn any(&self)->bool{ - self.booster.is_some() - ||self.trajectory.is_some() - ||self.wormhole.is_some() - ||self.accelerator.is_some() - } - pub fn is_wrcp(&self)->bool{ - self.trajectory.as_ref().map_or(false,|t|t.is_absolute()) - /* - &&match &self.teleport_behaviour{ - Some(TeleportBehaviour::StageElement( - StageElement{ - mode_id, - stage_id:_, - force:true, - behaviour:StageElementBehaviour::Trigger|StageElementBehaviour::Teleport - } - ))=>current_mode_id==*mode_id, - _=>false, - } - */ - } +impl GeneralAttributes { + pub const fn any(&self) -> bool { + self.booster.is_some() + || self.trajectory.is_some() + || self.wormhole.is_some() + || self.accelerator.is_some() + } + pub fn is_wrcp(&self) -> bool { + self.trajectory.as_ref().map_or(false, |t| t.is_absolute()) + /* + &&match &self.teleport_behaviour{ + Some(TeleportBehaviour::StageElement( + StageElement{ + mode_id, + stage_id:_, + force:true, + behaviour:StageElementBehaviour::Trigger|StageElementBehaviour::Teleport + } + ))=>current_mode_id==*mode_id, + _=>false, + } + */ + } } -#[derive(Default,Clone,Hash,Eq,PartialEq)] -pub struct ContactingAttributes{ - //friction? - pub contact_behaviour:Option, +#[derive(Default, Clone, Hash, Eq, PartialEq)] +pub struct ContactingAttributes { + //friction? + pub contact_behaviour: Option, } -impl ContactingAttributes{ - pub const fn any(&self)->bool{ - self.contact_behaviour.is_some() - } +impl ContactingAttributes { + pub const fn any(&self) -> bool { + self.contact_behaviour.is_some() + } } -#[derive(Default,Clone,Hash,Eq,PartialEq)] -pub struct IntersectingAttributes{ - pub water:Option, +#[derive(Default, Clone, Hash, Eq, PartialEq)] +pub struct IntersectingAttributes { + pub water: Option, } -impl IntersectingAttributes{ - pub const fn any(&self)->bool{ - self.water.is_some() - } +impl IntersectingAttributes { + pub const fn any(&self) -> bool { + self.water.is_some() + } } -#[derive(Clone,Copy,id::Id,Hash,Eq,PartialEq)] +#[derive(Clone, Copy, id::Id, Hash, Eq, PartialEq)] pub struct CollisionAttributesId(u32); -#[derive(Clone,Default,Hash,Eq,PartialEq)] -pub struct ContactAttributes{ - pub contacting:ContactingAttributes, - pub general:GeneralAttributes, +#[derive(Clone, Default, Hash, Eq, PartialEq)] +pub struct ContactAttributes { + pub contacting: ContactingAttributes, + pub general: GeneralAttributes, } -#[derive(Clone,Default,Hash,Eq,PartialEq)] -pub struct IntersectAttributes{ - pub intersecting:IntersectingAttributes, - pub general:GeneralAttributes, +#[derive(Clone, Default, Hash, Eq, PartialEq)] +pub struct IntersectAttributes { + pub intersecting: IntersectingAttributes, + pub general: GeneralAttributes, } -#[derive(Clone,Hash,Eq,PartialEq)] -pub enum CollisionAttributes{ - Decoration,//visual only - Contact(ContactAttributes),//track whether you are contacting the object - Intersect(IntersectAttributes),//track whether you are intersecting the object +#[derive(Clone, Hash, Eq, PartialEq)] +pub enum CollisionAttributes { + Decoration, //visual only + Contact(ContactAttributes), //track whether you are contacting the object + Intersect(IntersectAttributes), //track whether you are intersecting the object } -impl CollisionAttributes{ - pub fn contact_default()->Self{ - Self::Contact(ContactAttributes::default()) - } +impl CollisionAttributes { + pub fn contact_default() -> Self { + Self::Contact(ContactAttributes::default()) + } } diff --git a/lib/common/src/gameplay_modes.rs b/lib/common/src/gameplay_modes.rs index d0d204b..8c5fdd7 100644 --- a/lib/common/src/gameplay_modes.rs +++ b/lib/common/src/gameplay_modes.rs @@ -1,332 +1,358 @@ -use std::collections::{HashSet,HashMap}; -use crate::model::ModelId; use crate::gameplay_style; +use crate::model::ModelId; use crate::updatable::Updatable; +use std::collections::{HashMap, HashSet}; #[derive(Clone)] -pub struct StageElement{ - stage_id:StageId,//which stage spawn to send to - force:bool,//allow setting to lower spawn id i.e. 7->3 - behaviour:StageElementBehaviour, - jump_limit:Option, +pub struct StageElement { + stage_id: StageId, //which stage spawn to send to + force: bool, //allow setting to lower spawn id i.e. 7->3 + behaviour: StageElementBehaviour, + jump_limit: Option, } -impl StageElement{ - #[inline] - pub const fn new(stage_id:StageId,force:bool,behaviour:StageElementBehaviour,jump_limit:Option)->Self{ - Self{ - stage_id, - force, - behaviour, - jump_limit, - } - } - #[inline] - pub const fn stage_id(&self)->StageId{ - self.stage_id - } - #[inline] - pub const fn force(&self)->bool{ - self.force - } - #[inline] - pub const fn behaviour(&self)->StageElementBehaviour{ - self.behaviour - } - #[inline] - pub const fn jump_limit(&self)->Option{ - self.jump_limit - } +impl StageElement { + #[inline] + pub const fn new( + stage_id: StageId, + force: bool, + behaviour: StageElementBehaviour, + jump_limit: Option, + ) -> Self { + Self { + stage_id, + force, + behaviour, + jump_limit, + } + } + #[inline] + pub const fn stage_id(&self) -> StageId { + self.stage_id + } + #[inline] + pub const fn force(&self) -> bool { + self.force + } + #[inline] + pub const fn behaviour(&self) -> StageElementBehaviour { + self.behaviour + } + #[inline] + pub const fn jump_limit(&self) -> Option { + self.jump_limit + } } -#[derive(Clone,Copy,Hash,Eq,PartialEq)] -pub enum StageElementBehaviour{ - SpawnAt,//must be standing on top to get effect. except cancollide false - Trigger, - Teleport, - Platform, - //Check(point) acts like a trigger if you haven't hit all the checkpoints on previous stages yet. - //Note that all stage elements act like this, this is just the isolated behaviour. - Check, - Checkpoint,//this is a combined behaviour for Ordered & Unordered in case a model is used multiple times or for both. +#[derive(Clone, Copy, Hash, Eq, PartialEq)] +pub enum StageElementBehaviour { + SpawnAt, //must be standing on top to get effect. except cancollide false + Trigger, + Teleport, + Platform, + //Check(point) acts like a trigger if you haven't hit all the checkpoints on previous stages yet. + //Note that all stage elements act like this, this is just the isolated behaviour. + Check, + Checkpoint, //this is a combined behaviour for Ordered & Unordered in case a model is used multiple times or for both. } -#[derive(Clone,Copy,Debug,Hash,id::Id,Eq,PartialEq)] +#[derive(Clone, Copy, Debug, Hash, id::Id, Eq, PartialEq)] pub struct CheckpointId(u32); -impl CheckpointId{ - pub const FIRST:Self=Self(0); +impl CheckpointId { + pub const FIRST: Self = Self(0); } -#[derive(Clone,Copy,Debug,Hash,id::Id,Eq,PartialEq,Ord,PartialOrd)] +#[derive(Clone, Copy, Debug, Hash, id::Id, Eq, PartialEq, Ord, PartialOrd)] pub struct StageId(u32); -impl StageId{ - pub const FIRST:Self=Self(0); +impl StageId { + pub const FIRST: Self = Self(0); } #[derive(Clone)] -pub struct Stage{ - spawn:ModelId, - //open world support lol - ordered_checkpoints_count:u32, - unordered_checkpoints_count:u32, - //currently loaded checkpoint models - ordered_checkpoints:HashMap, - unordered_checkpoints:HashSet, +pub struct Stage { + spawn: ModelId, + //open world support lol + ordered_checkpoints_count: u32, + unordered_checkpoints_count: u32, + //currently loaded checkpoint models + ordered_checkpoints: HashMap, + unordered_checkpoints: HashSet, } -impl Stage{ - pub fn new( - spawn:ModelId, - ordered_checkpoints_count:u32, - unordered_checkpoints_count:u32, - ordered_checkpoints:HashMap, - unordered_checkpoints:HashSet, - )->Self{ - Self{ - spawn, - ordered_checkpoints_count, - unordered_checkpoints_count, - ordered_checkpoints, - unordered_checkpoints, - } - } - pub fn empty(spawn:ModelId)->Self{ - Self{ - spawn, - ordered_checkpoints_count:0, - unordered_checkpoints_count:0, - ordered_checkpoints:HashMap::new(), - unordered_checkpoints:HashSet::new(), - } - } - #[inline] - pub const fn spawn(&self)->ModelId{ - self.spawn - } - #[inline] - pub const fn ordered_checkpoints_count(&self)->u32{ - self.ordered_checkpoints_count - } - #[inline] - pub const fn unordered_checkpoints_count(&self)->u32{ - self.unordered_checkpoints_count - } - pub fn into_inner(self)->(HashMap,HashSet){ - (self.ordered_checkpoints,self.unordered_checkpoints) - } - /// Returns true if the stage has no checkpoints. - #[inline] - pub const fn is_empty(&self)->bool{ - self.is_complete(0,0) - } - #[inline] - pub const fn is_complete(&self,ordered_checkpoints_count:u32,unordered_checkpoints_count:u32)->bool{ - self.ordered_checkpoints_count==ordered_checkpoints_count&&self.unordered_checkpoints_count==unordered_checkpoints_count - } - #[inline] - pub fn is_next_ordered_checkpoint(&self,next_ordered_checkpoint_id:CheckpointId,model_id:ModelId)->bool{ - self.ordered_checkpoints.get(&next_ordered_checkpoint_id).is_some_and(|&next_checkpoint|model_id==next_checkpoint) - } - #[inline] - pub fn is_unordered_checkpoint(&self,model_id:ModelId)->bool{ - self.unordered_checkpoints.contains(&model_id) - } +impl Stage { + pub fn new( + spawn: ModelId, + ordered_checkpoints_count: u32, + unordered_checkpoints_count: u32, + ordered_checkpoints: HashMap, + unordered_checkpoints: HashSet, + ) -> Self { + Self { + spawn, + ordered_checkpoints_count, + unordered_checkpoints_count, + ordered_checkpoints, + unordered_checkpoints, + } + } + pub fn empty(spawn: ModelId) -> Self { + Self { + spawn, + ordered_checkpoints_count: 0, + unordered_checkpoints_count: 0, + ordered_checkpoints: HashMap::new(), + unordered_checkpoints: HashSet::new(), + } + } + #[inline] + pub const fn spawn(&self) -> ModelId { + self.spawn + } + #[inline] + pub const fn ordered_checkpoints_count(&self) -> u32 { + self.ordered_checkpoints_count + } + #[inline] + pub const fn unordered_checkpoints_count(&self) -> u32 { + self.unordered_checkpoints_count + } + pub fn into_inner(self) -> (HashMap, HashSet) { + (self.ordered_checkpoints, self.unordered_checkpoints) + } + /// Returns true if the stage has no checkpoints. + #[inline] + pub const fn is_empty(&self) -> bool { + self.is_complete(0, 0) + } + #[inline] + pub const fn is_complete( + &self, + ordered_checkpoints_count: u32, + unordered_checkpoints_count: u32, + ) -> bool { + self.ordered_checkpoints_count == ordered_checkpoints_count + && self.unordered_checkpoints_count == unordered_checkpoints_count + } + #[inline] + pub fn is_next_ordered_checkpoint( + &self, + next_ordered_checkpoint_id: CheckpointId, + model_id: ModelId, + ) -> bool { + self.ordered_checkpoints + .get(&next_ordered_checkpoint_id) + .is_some_and(|&next_checkpoint| model_id == next_checkpoint) + } + #[inline] + pub fn is_unordered_checkpoint(&self, model_id: ModelId) -> bool { + self.unordered_checkpoints.contains(&model_id) + } } #[derive(Default)] -pub struct StageUpdate{ - //other behaviour models of this stage can have - ordered_checkpoints:HashMap, - unordered_checkpoints:HashSet, +pub struct StageUpdate { + //other behaviour models of this stage can have + ordered_checkpoints: HashMap, + unordered_checkpoints: HashSet, } -impl Updatable for Stage{ - fn update(&mut self,update:StageUpdate){ - self.ordered_checkpoints.extend(update.ordered_checkpoints); - self.unordered_checkpoints.extend(update.unordered_checkpoints); - } +impl Updatable for Stage { + fn update(&mut self, update: StageUpdate) { + self.ordered_checkpoints.extend(update.ordered_checkpoints); + self.unordered_checkpoints + .extend(update.unordered_checkpoints); + } } -#[derive(Clone,Copy,Hash,Eq,PartialEq)] -pub enum Zone{ - Start, - Finish, - Anticheat, +#[derive(Clone, Copy, Hash, Eq, PartialEq)] +pub enum Zone { + Start, + Finish, + Anticheat, } -#[derive(Clone,Copy,Debug,Hash,id::Id,Eq,PartialEq,Ord,PartialOrd)] +#[derive(Clone, Copy, Debug, Hash, id::Id, Eq, PartialEq, Ord, PartialOrd)] pub struct ModeId(u32); -impl ModeId{ - pub const MAIN:Self=Self(0); - pub const BONUS:Self=Self(1); +impl ModeId { + pub const MAIN: Self = Self(0); + pub const BONUS: Self = Self(1); } #[derive(Clone)] -pub struct Mode{ - style:gameplay_style::StyleModifiers, - start:ModelId,//when you press reset you go here - zones:HashMap, - stages:Vec,//when you load the map you go to stages[0].spawn - //mutually exlusive stage element behaviour - elements:HashMap, +pub struct Mode { + style: gameplay_style::StyleModifiers, + start: ModelId, //when you press reset you go here + zones: HashMap, + stages: Vec, //when you load the map you go to stages[0].spawn + //mutually exlusive stage element behaviour + elements: HashMap, } -impl Mode{ - pub fn new( - style:gameplay_style::StyleModifiers, - start:ModelId, - zones:HashMap, - stages:Vec, - elements:HashMap, - )->Self{ - Self{ - style, - start, - zones, - stages, - elements, - } - } - pub fn empty(style:gameplay_style::StyleModifiers,start:ModelId)->Self{ - Self{ - style, - start, - zones:HashMap::new(), - stages:Vec::new(), - elements:HashMap::new(), - } - } - pub fn into_inner(self)->( - gameplay_style::StyleModifiers, - ModelId, - HashMap, - Vec, - HashMap, - ){ - ( - self.style, - self.start, - self.zones, - self.stages, - self.elements, - ) - } - pub const fn get_start(&self)->ModelId{ - self.start - } - pub const fn get_style(&self)->&gameplay_style::StyleModifiers{ - &self.style - } - pub fn push_stage(&mut self,stage:Stage){ - self.stages.push(stage) - } - pub fn get_stage_mut(&mut self,stage:StageId)->Option<&mut Stage>{ - self.stages.get_mut(stage.0 as usize) - } - pub fn get_spawn_model_id(&self,stage:StageId)->Option{ - self.stages.get(stage.0 as usize).map(|s|s.spawn) - } - pub fn get_zone(&self,model_id:ModelId)->Option<&Zone>{ - self.zones.get(&model_id) - } - pub fn get_stage(&self,stage_id:StageId)->Option<&Stage>{ - self.stages.get(stage_id.0 as usize) - } - pub fn get_element(&self,model_id:ModelId)->Option<&StageElement>{ - self.elements.get(&model_id) - } - //TODO: put this in the SNF - pub fn denormalize_data(&mut self){ - //expand and index normalized data - self.zones.insert(self.start,Zone::Start); - for (stage_id,stage) in self.stages.iter().enumerate(){ - self.elements.insert(stage.spawn,StageElement{ - stage_id:StageId(stage_id as u32), - force:false, - behaviour:StageElementBehaviour::SpawnAt, - jump_limit:None, - }); - for (_,&model) in &stage.ordered_checkpoints{ - self.elements.insert(model,StageElement{ - stage_id:StageId(stage_id as u32), - force:false, - behaviour:StageElementBehaviour::Checkpoint, - jump_limit:None, - }); - } - for &model in &stage.unordered_checkpoints{ - self.elements.insert(model,StageElement{ - stage_id:StageId(stage_id as u32), - force:false, - behaviour:StageElementBehaviour::Checkpoint, - jump_limit:None, - }); - } - } - } +impl Mode { + pub fn new( + style: gameplay_style::StyleModifiers, + start: ModelId, + zones: HashMap, + stages: Vec, + elements: HashMap, + ) -> Self { + Self { + style, + start, + zones, + stages, + elements, + } + } + pub fn empty(style: gameplay_style::StyleModifiers, start: ModelId) -> Self { + Self { + style, + start, + zones: HashMap::new(), + stages: Vec::new(), + elements: HashMap::new(), + } + } + pub fn into_inner( + self, + ) -> ( + gameplay_style::StyleModifiers, + ModelId, + HashMap, + Vec, + HashMap, + ) { + ( + self.style, + self.start, + self.zones, + self.stages, + self.elements, + ) + } + pub const fn get_start(&self) -> ModelId { + self.start + } + pub const fn get_style(&self) -> &gameplay_style::StyleModifiers { + &self.style + } + pub fn push_stage(&mut self, stage: Stage) { + self.stages.push(stage) + } + pub fn get_stage_mut(&mut self, stage: StageId) -> Option<&mut Stage> { + self.stages.get_mut(stage.0 as usize) + } + pub fn get_spawn_model_id(&self, stage: StageId) -> Option { + self.stages.get(stage.0 as usize).map(|s| s.spawn) + } + pub fn get_zone(&self, model_id: ModelId) -> Option<&Zone> { + self.zones.get(&model_id) + } + pub fn get_stage(&self, stage_id: StageId) -> Option<&Stage> { + self.stages.get(stage_id.0 as usize) + } + pub fn get_element(&self, model_id: ModelId) -> Option<&StageElement> { + self.elements.get(&model_id) + } + //TODO: put this in the SNF + pub fn denormalize_data(&mut self) { + //expand and index normalized data + self.zones.insert(self.start, Zone::Start); + for (stage_id, stage) in self.stages.iter().enumerate() { + self.elements.insert( + stage.spawn, + StageElement { + stage_id: StageId(stage_id as u32), + force: false, + behaviour: StageElementBehaviour::SpawnAt, + jump_limit: None, + }, + ); + for (_, &model) in &stage.ordered_checkpoints { + self.elements.insert( + model, + StageElement { + stage_id: StageId(stage_id as u32), + force: false, + behaviour: StageElementBehaviour::Checkpoint, + jump_limit: None, + }, + ); + } + for &model in &stage.unordered_checkpoints { + self.elements.insert( + model, + StageElement { + stage_id: StageId(stage_id as u32), + force: false, + behaviour: StageElementBehaviour::Checkpoint, + jump_limit: None, + }, + ); + } + } + } } //this would be nice as a macro #[derive(Default)] -pub struct ModeUpdate{ - zones:HashMap, - stages:HashMap, - //mutually exlusive stage element behaviour - elements:HashMap, +pub struct ModeUpdate { + zones: HashMap, + stages: HashMap, + //mutually exlusive stage element behaviour + elements: HashMap, } -impl Updatable for Mode{ - fn update(&mut self,update:ModeUpdate){ - self.zones.extend(update.zones); - for (stage,stage_update) in update.stages{ - if let Some(stage)=self.stages.get_mut(stage.0 as usize){ - stage.update(stage_update); - } - } - self.elements.extend(update.elements); - } +impl Updatable for Mode { + fn update(&mut self, update: ModeUpdate) { + self.zones.extend(update.zones); + for (stage, stage_update) in update.stages { + if let Some(stage) = self.stages.get_mut(stage.0 as usize) { + stage.update(stage_update); + } + } + self.elements.extend(update.elements); + } } -impl ModeUpdate{ - pub fn zone(model_id:ModelId,zone:Zone)->Self{ - let mut mu=Self::default(); - mu.zones.insert(model_id,zone); - mu - } - pub fn stage(stage_id:StageId,stage_update:StageUpdate)->Self{ - let mut mu=Self::default(); - mu.stages.insert(stage_id,stage_update); - mu - } - pub fn element(model_id:ModelId,element:StageElement)->Self{ - let mut mu=Self::default(); - mu.elements.insert(model_id,element); - mu - } - pub fn map_stage_element_idsStageId>(&mut self,f:F){ - for (_,stage_element) in self.elements.iter_mut(){ - stage_element.stage_id=f(stage_element.stage_id); - } - } +impl ModeUpdate { + pub fn zone(model_id: ModelId, zone: Zone) -> Self { + let mut mu = Self::default(); + mu.zones.insert(model_id, zone); + mu + } + pub fn stage(stage_id: StageId, stage_update: StageUpdate) -> Self { + let mut mu = Self::default(); + mu.stages.insert(stage_id, stage_update); + mu + } + pub fn element(model_id: ModelId, element: StageElement) -> Self { + let mut mu = Self::default(); + mu.elements.insert(model_id, element); + mu + } + pub fn map_stage_element_ids StageId>(&mut self, f: F) { + for (_, stage_element) in self.elements.iter_mut() { + stage_element.stage_id = f(stage_element.stage_id); + } + } } -#[derive(Default,Clone)] -pub struct Modes{ - pub modes:Vec, +#[derive(Default, Clone)] +pub struct Modes { + pub modes: Vec, } -impl Modes{ - pub const fn new(modes:Vec)->Self{ - Self{ - modes, - } - } - pub fn into_inner(self)->Vec{ - self.modes - } - pub fn push_mode(&mut self,mode:Mode){ - self.modes.push(mode) - } - pub fn get_mode(&self,mode:ModeId)->Option<&Mode>{ - self.modes.get(mode.0 as usize) - } +impl Modes { + pub const fn new(modes: Vec) -> Self { + Self { modes } + } + pub fn into_inner(self) -> Vec { + self.modes + } + pub fn push_mode(&mut self, mode: Mode) { + self.modes.push(mode) + } + pub fn get_mode(&self, mode: ModeId) -> Option<&Mode> { + self.modes.get(mode.0 as usize) + } } -pub struct ModesUpdate{ - modes:HashMap, +pub struct ModesUpdate { + modes: HashMap, } -impl Updatable for Modes{ - fn update(&mut self,update:ModesUpdate){ - for (mode,mode_update) in update.modes{ - if let Some(mode)=self.modes.get_mut(mode.0 as usize){ - mode.update(mode_update); - } - } - } +impl Updatable for Modes { + fn update(&mut self, update: ModesUpdate) { + for (mode, mode_update) in update.modes { + if let Some(mode) = self.modes.get_mut(mode.0 as usize) { + mode.update(mode_update); + } + } + } } diff --git a/lib/common/src/gameplay_style.rs b/lib/common/src/gameplay_style.rs index 60d7fb4..cdca38d 100644 --- a/lib/common/src/gameplay_style.rs +++ b/lib/common/src/gameplay_style.rs @@ -1,612 +1,644 @@ -const VALVE_SCALE:Planar64=Planar64::raw(1<<28);// 1/16 +const VALVE_SCALE: Planar64 = Planar64::raw(1 << 28); // 1/16 -use crate::integer::{int,vec3::int as int3,AbsoluteTime,Ratio64,Planar64,Planar64Vec3}; use crate::controls_bitflag::Controls; +use crate::integer::{int, vec3::int as int3, AbsoluteTime, Planar64, Planar64Vec3, Ratio64}; use crate::physics::Time as PhysicsTime; -#[derive(Clone,Debug)] -pub struct StyleModifiers{ - //controls which are allowed to pass into gameplay (usually all) - pub controls_mask:Controls, - //controls which are masked from control state (e.g. !jump in scroll style) - pub controls_mask_state:Controls, - //strafing - pub strafe:Option, - //player gets a controllable rocket force - pub rocket:Option, - //flying - //pub move_type:MoveType::Fly(FlySettings) - //MoveType::Physics(PhysicsSettings) -> PhysicsSettings (strafe,rocket,jump,walk,ladder,swim,gravity) - //jumping is allowed - pub jump:Option, - //standing & walking is allowed - pub walk:Option, - //laddering is allowed - pub ladder:Option, - //water propulsion - pub swim:Option, - //maximum slope before sloped surfaces become frictionless - pub gravity:Planar64Vec3, - //hitbox - pub hitbox:Hitbox, - //camera location relative to the center (0,0,0) of the hitbox - pub camera_offset:Planar64Vec3, - //unused - pub mass:Planar64, +#[derive(Clone, Debug)] +pub struct StyleModifiers { + //controls which are allowed to pass into gameplay (usually all) + pub controls_mask: Controls, + //controls which are masked from control state (e.g. !jump in scroll style) + pub controls_mask_state: Controls, + //strafing + pub strafe: Option, + //player gets a controllable rocket force + pub rocket: Option, + //flying + //pub move_type:MoveType::Fly(FlySettings) + //MoveType::Physics(PhysicsSettings) -> PhysicsSettings (strafe,rocket,jump,walk,ladder,swim,gravity) + //jumping is allowed + pub jump: Option, + //standing & walking is allowed + pub walk: Option, + //laddering is allowed + pub ladder: Option, + //water propulsion + pub swim: Option, + //maximum slope before sloped surfaces become frictionless + pub gravity: Planar64Vec3, + //hitbox + pub hitbox: Hitbox, + //camera location relative to the center (0,0,0) of the hitbox + pub camera_offset: Planar64Vec3, + //unused + pub mass: Planar64, } -impl std::default::Default for StyleModifiers{ - fn default()->Self{ - Self::roblox_bhop() - } +impl std::default::Default for StyleModifiers { + fn default() -> Self { + Self::roblox_bhop() + } } -#[derive(Clone,Debug)] -pub enum JumpCalculation{ - Max,//Roblox: jumped_speed=max(velocity.boost(),velocity.jump()) - BoostThenJump,//jumped_speed=velocity.boost().jump() - JumpThenBoost,//jumped_speed=velocity.jump().boost() +#[derive(Clone, Debug)] +pub enum JumpCalculation { + Max, //Roblox: jumped_speed=max(velocity.boost(),velocity.jump()) + BoostThenJump, //jumped_speed=velocity.boost().jump() + JumpThenBoost, //jumped_speed=velocity.jump().boost() } -#[derive(Clone,Debug)] -pub enum JumpImpulse{ - Time(AbsoluteTime),//jump time is invariant across mass and gravity changes - Height(Planar64),//jump height is invariant across mass and gravity changes - Linear(Planar64),//jump velocity is invariant across mass and gravity changes - Energy(Planar64),// :) +#[derive(Clone, Debug)] +pub enum JumpImpulse { + Time(AbsoluteTime), //jump time is invariant across mass and gravity changes + Height(Planar64), //jump height is invariant across mass and gravity changes + Linear(Planar64), //jump velocity is invariant across mass and gravity changes + Energy(Planar64), // :) } //Jumping acts on dot(walks_state.normal,body.velocity) //Energy means it adds energy //Linear means it linearly adds on -impl JumpImpulse{ - pub fn jump( - &self, - velocity:Planar64Vec3, - jump_dir:Planar64Vec3, - gravity:&Planar64Vec3, - mass:Planar64, - )->Planar64Vec3{ - match self{ - &JumpImpulse::Time(time)=>velocity-(*gravity*time).map(|t|t.divide().fix_1()), - &JumpImpulse::Height(height)=>{ - //height==-v.y*v.y/(2*g.y); - //use energy to determine max height - let gg=gravity.length_squared(); - let g=gg.sqrt().fix_1(); - let v_g=gravity.dot(velocity); - //do it backwards - let radicand=v_g*v_g+(g*height*2).fix_4(); - velocity-(*gravity*(radicand.sqrt().fix_2()+v_g)/gg).divide().fix_1() - }, - &JumpImpulse::Linear(jump_speed)=>velocity+(jump_dir*jump_speed/jump_dir.length()).divide().fix_1(), - &JumpImpulse::Energy(energy)=>{ - //calculate energy - //let e=gravity.dot(velocity); - //add - //you get the idea - todo!() - }, - } - } - //TODO: remove this and implement JumpCalculation properly - pub fn get_jump_deltav(&self,gravity:&Planar64Vec3,mass:Planar64)->Planar64{ - //gravity.length() is actually the proper calculation because the jump is always opposite the gravity direction - match self{ - &JumpImpulse::Time(time)=>(gravity.length().fix_1()*time/2).divide().fix_1(), - &JumpImpulse::Height(height)=>(gravity.length()*height*2).sqrt().fix_1(), - &JumpImpulse::Linear(deltav)=>deltav, - &JumpImpulse::Energy(energy)=>(energy.sqrt()*2/mass.sqrt()).divide().fix_1(), - } - } +impl JumpImpulse { + pub fn jump( + &self, + velocity: Planar64Vec3, + jump_dir: Planar64Vec3, + gravity: &Planar64Vec3, + mass: Planar64, + ) -> Planar64Vec3 { + match self { + &JumpImpulse::Time(time) => velocity - (*gravity * time).map(|t| t.divide().fix_1()), + &JumpImpulse::Height(height) => { + //height==-v.y*v.y/(2*g.y); + //use energy to determine max height + let gg = gravity.length_squared(); + let g = gg.sqrt().fix_1(); + let v_g = gravity.dot(velocity); + //do it backwards + let radicand = v_g * v_g + (g * height * 2).fix_4(); + velocity + - (*gravity * (radicand.sqrt().fix_2() + v_g) / gg) + .divide() + .fix_1() + } + &JumpImpulse::Linear(jump_speed) => { + velocity + (jump_dir * jump_speed / jump_dir.length()).divide().fix_1() + } + &JumpImpulse::Energy(energy) => { + //calculate energy + //let e=gravity.dot(velocity); + //add + //you get the idea + todo!() + } + } + } + //TODO: remove this and implement JumpCalculation properly + pub fn get_jump_deltav(&self, gravity: &Planar64Vec3, mass: Planar64) -> Planar64 { + //gravity.length() is actually the proper calculation because the jump is always opposite the gravity direction + match self { + &JumpImpulse::Time(time) => (gravity.length().fix_1() * time / 2).divide().fix_1(), + &JumpImpulse::Height(height) => (gravity.length() * height * 2).sqrt().fix_1(), + &JumpImpulse::Linear(deltav) => deltav, + &JumpImpulse::Energy(energy) => (energy.sqrt() * 2 / mass.sqrt()).divide().fix_1(), + } + } } -#[derive(Clone,Debug)] -pub struct JumpSettings{ - //information used to calculate jump power - pub impulse:JumpImpulse, - //information used to calculate jump behaviour - pub calculation:JumpCalculation, - //limit the minimum jump power when combined with downwards momentum - //This is true in both roblox and source - pub limit_minimum:bool, +#[derive(Clone, Debug)] +pub struct JumpSettings { + //information used to calculate jump power + pub impulse: JumpImpulse, + //information used to calculate jump behaviour + pub calculation: JumpCalculation, + //limit the minimum jump power when combined with downwards momentum + //This is true in both roblox and source + pub limit_minimum: bool, } -impl JumpSettings{ - pub fn jumped_velocity( - &self, - style:&StyleModifiers, - jump_dir:Planar64Vec3, - rel_velocity:Planar64Vec3, - booster:Option<&crate::gameplay_attributes::Booster>, - )->Planar64Vec3{ - let jump_speed=self.impulse.get_jump_deltav(&style.gravity,style.mass); - match (self.limit_minimum,&self.calculation){ - (true,JumpCalculation::Max)=>{ - //the roblox calculation - let boost_vel=match booster{ - Some(booster)=>booster.boost(rel_velocity), - None=>rel_velocity, - }; - let j=boost_vel.dot(jump_dir); - let js=jump_speed.fix_2(); - if j{ - //the source calculation (?) - let boost_vel=match booster{ - Some(booster)=>booster.boost(rel_velocity), - None=>rel_velocity, - }; - let j=boost_vel.dot(jump_dir); - let js=jump_speed.fix_2(); - if j{ - //??? calculation - //max(boost_vel,jump_vel) - let boost_vel=match booster{ - Some(booster)=>booster.boost(rel_velocity), - None=>rel_velocity, - }; - let boost_dot=boost_vel.dot(jump_dir); - let js=jump_speed.fix_2(); - if boost_dot{ - let boost_vel=match booster{ - Some(booster)=>booster.boost(rel_velocity), - None=>rel_velocity, - }; - boost_vel+jump_dir.with_length(jump_speed).divide().fix_1() - }, - } - } +impl JumpSettings { + pub fn jumped_velocity( + &self, + style: &StyleModifiers, + jump_dir: Planar64Vec3, + rel_velocity: Planar64Vec3, + booster: Option<&crate::gameplay_attributes::Booster>, + ) -> Planar64Vec3 { + let jump_speed = self.impulse.get_jump_deltav(&style.gravity, style.mass); + match (self.limit_minimum, &self.calculation) { + (true, JumpCalculation::Max) => { + //the roblox calculation + let boost_vel = match booster { + Some(booster) => booster.boost(rel_velocity), + None => rel_velocity, + }; + let j = boost_vel.dot(jump_dir); + let js = jump_speed.fix_2(); + if j < js { + //weak booster: just do a regular jump + boost_vel + jump_dir.with_length(js - j).divide().fix_1() + } else { + //activate booster normally, jump does nothing + boost_vel + } + } + (true, _) => { + //the source calculation (?) + let boost_vel = match booster { + Some(booster) => booster.boost(rel_velocity), + None => rel_velocity, + }; + let j = boost_vel.dot(jump_dir); + let js = jump_speed.fix_2(); + if j < js { + //speed in direction of jump cannot be lower than amount + boost_vel + jump_dir.with_length(js - j).divide().fix_1() + } else { + //boost and jump add together + boost_vel + jump_dir.with_length(js).divide().fix_1() + } + } + (false, JumpCalculation::Max) => { + //??? calculation + //max(boost_vel,jump_vel) + let boost_vel = match booster { + Some(booster) => booster.boost(rel_velocity), + None => rel_velocity, + }; + let boost_dot = boost_vel.dot(jump_dir); + let js = jump_speed.fix_2(); + if boost_dot < js { + //weak boost is extended to jump speed + boost_vel + jump_dir.with_length(js - boost_dot).divide().fix_1() + } else { + //activate booster normally, jump does nothing + boost_vel + } + } + //the strafe client calculation + (false, _) => { + let boost_vel = match booster { + Some(booster) => booster.boost(rel_velocity), + None => rel_velocity, + }; + boost_vel + jump_dir.with_length(jump_speed).divide().fix_1() + } + } + } } -#[derive(Clone,Debug)] -pub struct ControlsActivation{ - //allowed keys - pub controls_mask:Controls, - //allow strafing only if any of the masked controls are held, eg W|S for shsw - pub controls_intersects:Controls, - //allow strafing only if all of the masked controls are held, eg W for hsw, w-only - pub controls_contains:Controls, - //Function(Boxbool>), +#[derive(Clone, Debug)] +pub struct ControlsActivation { + //allowed keys + pub controls_mask: Controls, + //allow strafing only if any of the masked controls are held, eg W|S for shsw + pub controls_intersects: Controls, + //allow strafing only if all of the masked controls are held, eg W for hsw, w-only + pub controls_contains: Controls, + //Function(Boxbool>), } -impl ControlsActivation{ - pub const fn mask(&self,controls:Controls)->Controls{ - controls.intersection(self.controls_mask) - } - pub const fn activates(&self,controls:Controls)->bool{ - (self.controls_intersects.is_empty()||controls.intersects(self.controls_intersects)) - &&controls.contains(self.controls_contains) - } - pub const fn full_3d()->Self{ - Self{ - controls_mask:Controls::WASDQE, - controls_intersects:Controls::WASDQE, - controls_contains:Controls::empty(), - } - } - //classical styles - //Normal - pub const fn full_2d()->Self{ - Self{ - controls_mask:Controls::WASD, - controls_intersects:Controls::WASD, - controls_contains:Controls::empty(), - } - } - //Sideways - pub const fn sideways()->Self{ - Self{ - controls_mask:Controls::MoveForward.union(Controls::MoveBackward), - controls_intersects:Controls::MoveForward.union(Controls::MoveBackward), - controls_contains:Controls::empty(), - } - } - //Half-Sideways - pub const fn half_sideways()->Self{ - Self{ - controls_mask:Controls::MoveForward.union(Controls::MoveLeft).union(Controls::MoveRight), - controls_intersects:Controls::MoveLeft.union(Controls::MoveRight), - controls_contains:Controls::MoveForward, - } - } - //Surf Half-Sideways - pub const fn surf_half_sideways()->Self{ - Self{ - controls_mask:Controls::MoveForward.union(Controls::MoveBackward).union(Controls::MoveLeft).union(Controls::MoveRight), - controls_intersects:Controls::MoveForward.union(Controls::MoveBackward), - controls_contains:Controls::empty(), - } - } - //W-Only - pub const fn w_only()->Self{ - Self{ - controls_mask:Controls::MoveForward, - controls_intersects:Controls::empty(), - controls_contains:Controls::MoveForward, - } - } - //A-Only - pub const fn a_only()->Self{ - Self{ - controls_mask:Controls::MoveLeft, - controls_intersects:Controls::empty(), - controls_contains:Controls::MoveLeft, - } - } - //Backwards +impl ControlsActivation { + pub const fn mask(&self, controls: Controls) -> Controls { + controls.intersection(self.controls_mask) + } + pub const fn activates(&self, controls: Controls) -> bool { + (self.controls_intersects.is_empty() || controls.intersects(self.controls_intersects)) + && controls.contains(self.controls_contains) + } + pub const fn full_3d() -> Self { + Self { + controls_mask: Controls::WASDQE, + controls_intersects: Controls::WASDQE, + controls_contains: Controls::empty(), + } + } + //classical styles + //Normal + pub const fn full_2d() -> Self { + Self { + controls_mask: Controls::WASD, + controls_intersects: Controls::WASD, + controls_contains: Controls::empty(), + } + } + //Sideways + pub const fn sideways() -> Self { + Self { + controls_mask: Controls::MoveForward.union(Controls::MoveBackward), + controls_intersects: Controls::MoveForward.union(Controls::MoveBackward), + controls_contains: Controls::empty(), + } + } + //Half-Sideways + pub const fn half_sideways() -> Self { + Self { + controls_mask: Controls::MoveForward + .union(Controls::MoveLeft) + .union(Controls::MoveRight), + controls_intersects: Controls::MoveLeft.union(Controls::MoveRight), + controls_contains: Controls::MoveForward, + } + } + //Surf Half-Sideways + pub const fn surf_half_sideways() -> Self { + Self { + controls_mask: Controls::MoveForward + .union(Controls::MoveBackward) + .union(Controls::MoveLeft) + .union(Controls::MoveRight), + controls_intersects: Controls::MoveForward.union(Controls::MoveBackward), + controls_contains: Controls::empty(), + } + } + //W-Only + pub const fn w_only() -> Self { + Self { + controls_mask: Controls::MoveForward, + controls_intersects: Controls::empty(), + controls_contains: Controls::MoveForward, + } + } + //A-Only + pub const fn a_only() -> Self { + Self { + controls_mask: Controls::MoveLeft, + controls_intersects: Controls::empty(), + controls_contains: Controls::MoveLeft, + } + } + //Backwards } -#[derive(Clone,Debug)] -pub struct StrafeSettings{ - pub enable:ControlsActivation, - pub mv:Planar64, - pub air_accel_limit:Option, - pub tick_rate:Ratio64, +#[derive(Clone, Debug)] +pub struct StrafeSettings { + pub enable: ControlsActivation, + pub mv: Planar64, + pub air_accel_limit: Option, + pub tick_rate: Ratio64, } -impl StrafeSettings{ - pub fn tick_velocity(&self,velocity:Planar64Vec3,control_dir:Planar64Vec3)->Option{ - let d=velocity.dot(control_dir); - let mv=self.mv.fix_2(); - match dSome(velocity+(control_dir*self.air_accel_limit.map_or(mv-d,|limit|limit.fix_2().min(mv-d))).fix_1()), - false=>None, - } - } - pub fn next_tick(&self,time:PhysicsTime)->PhysicsTime{ - PhysicsTime::from_nanos(self.tick_rate.rhs_div_int(self.tick_rate.mul_int(time.nanos())+1)) - } - pub const fn activates(&self,controls:Controls)->bool{ - self.enable.activates(controls) - } - pub const fn mask(&self,controls:Controls)->Controls{ - self.enable.mask(controls) - } +impl StrafeSettings { + pub fn tick_velocity( + &self, + velocity: Planar64Vec3, + control_dir: Planar64Vec3, + ) -> Option { + let d = velocity.dot(control_dir); + let mv = self.mv.fix_2(); + match d < mv { + true => Some( + velocity + + (control_dir + * self + .air_accel_limit + .map_or(mv - d, |limit| limit.fix_2().min(mv - d))) + .fix_1(), + ), + false => None, + } + } + pub fn next_tick(&self, time: PhysicsTime) -> PhysicsTime { + PhysicsTime::from_nanos( + self.tick_rate + .rhs_div_int(self.tick_rate.mul_int(time.nanos()) + 1), + ) + } + pub const fn activates(&self, controls: Controls) -> bool { + self.enable.activates(controls) + } + pub const fn mask(&self, controls: Controls) -> Controls { + self.enable.mask(controls) + } } -#[derive(Clone,Debug)] -pub struct PropulsionSettings{ - pub magnitude:Planar64, +#[derive(Clone, Debug)] +pub struct PropulsionSettings { + pub magnitude: Planar64, } -impl PropulsionSettings{ - pub fn acceleration(&self,control_dir:Planar64Vec3)->Planar64Vec3{ - (control_dir*self.magnitude).fix_1() - } +impl PropulsionSettings { + pub fn acceleration(&self, control_dir: Planar64Vec3) -> Planar64Vec3 { + (control_dir * self.magnitude).fix_1() + } } -#[derive(Clone,Debug)] -pub struct AccelerateSettings{ - pub accel:Planar64, - pub topspeed:Planar64, +#[derive(Clone, Debug)] +pub struct AccelerateSettings { + pub accel: Planar64, + pub topspeed: Planar64, } -#[derive(Clone,Debug)] -pub struct WalkSettings{ - pub accelerate:AccelerateSettings, - pub static_friction:Planar64, - pub kinetic_friction:Planar64, - //if a surf slope angle does not exist, then everything is slippery and walking is impossible - pub surf_dot:Planar64,//surf_dotPlanar64{ - //TODO: fallible walk accel - let diff_len=target_diff.length().fix_1(); - let friction=if diff_lenPlanar64Vec3{ - if control_dir==crate::integer::vec3::ZERO{ - return control_dir; - } - let nn=normal.length_squared(); - let mm=control_dir.length_squared(); - let nnmm=nn*mm; - let d=normal.dot(control_dir); - let dd=d*d; - if ddbool{ - //normal is not guaranteed to be unit length - let ny=normal.dot(up); - let h=normal.length().fix_1(); - //remember this is a normal vector - ny.is_positive()&&h*self.surf_dot Planar64 { + //TODO: fallible walk accel + let diff_len = target_diff.length().fix_1(); + let friction = if diff_len < self.accelerate.topspeed { + self.static_friction + } else { + self.kinetic_friction + }; + self.accelerate.accel.min((-gravity.y * friction).fix_1()) + } + pub fn get_walk_target_velocity( + &self, + control_dir: Planar64Vec3, + normal: Planar64Vec3, + ) -> Planar64Vec3 { + if control_dir == crate::integer::vec3::ZERO { + return control_dir; + } + let nn = normal.length_squared(); + let mm = control_dir.length_squared(); + let nnmm = nn * mm; + let d = normal.dot(control_dir); + let dd = d * d; + if dd < nnmm { + let cr = normal.cross(control_dir); + if cr == crate::integer::vec3::ZERO_2 { + crate::integer::vec3::ZERO + } else { + (cr.cross(normal) * self.accelerate.topspeed / ((nn * (nnmm - dd)).sqrt())) + .divide() + .fix_1() + } + } else { + crate::integer::vec3::ZERO + } + } + pub fn is_slope_walkable(&self, normal: Planar64Vec3, up: Planar64Vec3) -> bool { + //normal is not guaranteed to be unit length + let ny = normal.dot(up); + let h = normal.length().fix_1(); + //remember this is a normal vector + ny.is_positive() && h * self.surf_dot < ny + } } -#[derive(Clone,Debug)] -pub struct LadderSettings{ - pub accelerate:AccelerateSettings, - //how close to pushing directly into/out of the ladder normal - //does your input need to be to redirect straight up/down the ladder - pub dot:Planar64, +#[derive(Clone, Debug)] +pub struct LadderSettings { + pub accelerate: AccelerateSettings, + //how close to pushing directly into/out of the ladder normal + //does your input need to be to redirect straight up/down the ladder + pub dot: Planar64, } -impl LadderSettings{ - pub const fn accel(&self,target_diff:Planar64Vec3,gravity:Planar64Vec3)->Planar64{ - //TODO: fallible ladder accel - self.accelerate.accel - } - pub fn get_ladder_target_velocity(&self,mut control_dir:Planar64Vec3,normal:Planar64Vec3)->Planar64Vec3{ - if control_dir==crate::integer::vec3::ZERO{ - return control_dir; - } - let nn=normal.length_squared(); - let mm=control_dir.length_squared(); - let nnmm=nn*mm; - let d=normal.dot(control_dir); - let mut dd=d*d; - if (self.dot*self.dot*nnmm).fix_4() Planar64 { + //TODO: fallible ladder accel + self.accelerate.accel + } + pub fn get_ladder_target_velocity( + &self, + mut control_dir: Planar64Vec3, + normal: Planar64Vec3, + ) -> Planar64Vec3 { + if control_dir == crate::integer::vec3::ZERO { + return control_dir; + } + let nn = normal.length_squared(); + let mm = control_dir.length_squared(); + let nnmm = nn * mm; + let d = normal.dot(control_dir); + let mut dd = d * d; + if (self.dot * self.dot * nnmm).fix_4() < dd { + if d.is_negative() { + control_dir = Planar64Vec3::new([Planar64::ZERO, mm.fix_1(), Planar64::ZERO]); + } else { + control_dir = Planar64Vec3::new([Planar64::ZERO, -mm.fix_1(), Planar64::ZERO]); + } + dd = (normal.y * normal.y).fix_4(); + } + //n=d if you are standing on top of a ladder and press E. + //two fixes: + //- ladder movement is not allowed on walkable surfaces + //- fix the underlying issue + if dd < nnmm { + let cr = normal.cross(control_dir); + if cr == crate::integer::vec3::ZERO_2 { + crate::integer::vec3::ZERO + } else { + (cr.cross(normal) * self.accelerate.topspeed / ((nn * (nnmm - dd)).sqrt())) + .divide() + .fix_1() + } + } else { + crate::integer::vec3::ZERO + } + } } -#[derive(Clone,Debug)] -pub enum HitboxMesh{ - Box,//source - Cylinder,//roblox - //Sphere,//roblox old physics - //Point, - //Line, - //DualCone, +#[derive(Clone, Debug)] +pub enum HitboxMesh { + Box, //source + Cylinder, //roblox + //Sphere,//roblox old physics + //Point, + //Line, + //DualCone, } -#[derive(Clone,Debug)] -pub struct Hitbox{ - pub halfsize:Planar64Vec3, - pub mesh:HitboxMesh, +#[derive(Clone, Debug)] +pub struct Hitbox { + pub halfsize: Planar64Vec3, + pub mesh: HitboxMesh, } -impl Hitbox{ - pub fn roblox()->Self{ - Self{ - halfsize:int3(2,5,2)>>1, - mesh:HitboxMesh::Cylinder, - } - } - pub fn source()->Self{ - Self{ - halfsize:((int3(33,73,33)>>1)*VALVE_SCALE).fix_1(), - mesh:HitboxMesh::Box, - } - } +impl Hitbox { + pub fn roblox() -> Self { + Self { + halfsize: int3(2, 5, 2) >> 1, + mesh: HitboxMesh::Cylinder, + } + } + pub fn source() -> Self { + Self { + halfsize: ((int3(33, 73, 33) >> 1) * VALVE_SCALE).fix_1(), + mesh: HitboxMesh::Box, + } + } } -impl StyleModifiers{ - pub const RIGHT_DIR:Planar64Vec3=crate::integer::vec3::X; - pub const UP_DIR:Planar64Vec3=crate::integer::vec3::Y; - pub const FORWARD_DIR:Planar64Vec3=crate::integer::vec3::NEG_Z; +impl StyleModifiers { + pub const RIGHT_DIR: Planar64Vec3 = crate::integer::vec3::X; + pub const UP_DIR: Planar64Vec3 = crate::integer::vec3::Y; + pub const FORWARD_DIR: Planar64Vec3 = crate::integer::vec3::NEG_Z; - pub fn neo()->Self{ - Self{ - controls_mask:Controls::all(), - controls_mask_state:Controls::all(), - strafe:Some(StrafeSettings{ - enable:ControlsActivation::full_2d(), - air_accel_limit:None, - mv:int(3), - tick_rate:Ratio64::new(64,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(), - }), - jump:Some(JumpSettings{ - impulse:JumpImpulse::Energy(int(512)), - calculation:JumpCalculation::JumpThenBoost, - limit_minimum:false, - }), - gravity:int3(0,-80,0), - mass:int(1), - rocket:None, - walk:Some(WalkSettings{ - accelerate:AccelerateSettings{ - topspeed:int(16), - accel:int(80), - }, - static_friction:int(2), - kinetic_friction:int(3),//unrealistic: kinetic friction is typically lower than static - surf_dot:int(3)/4, - }), - ladder:Some(LadderSettings{ - accelerate:AccelerateSettings{ - topspeed:int(16), - accel:int(160), - }, - dot:(int(1)/2).sqrt(), - }), - swim:Some(PropulsionSettings{ - magnitude:int(12), - }), - hitbox:Hitbox::roblox(), - camera_offset:int3(0,2,0),//4.5-2.5=2 - } - } + pub fn neo() -> Self { + Self { + controls_mask: Controls::all(), + controls_mask_state: Controls::all(), + strafe: Some(StrafeSettings { + enable: ControlsActivation::full_2d(), + air_accel_limit: None, + mv: int(3), + tick_rate: Ratio64::new(64, AbsoluteTime::ONE_SECOND.get() as u64).unwrap(), + }), + jump: Some(JumpSettings { + impulse: JumpImpulse::Energy(int(512)), + calculation: JumpCalculation::JumpThenBoost, + limit_minimum: false, + }), + gravity: int3(0, -80, 0), + mass: int(1), + rocket: None, + walk: Some(WalkSettings { + accelerate: AccelerateSettings { + topspeed: int(16), + accel: int(80), + }, + static_friction: int(2), + kinetic_friction: int(3), //unrealistic: kinetic friction is typically lower than static + surf_dot: int(3) / 4, + }), + ladder: Some(LadderSettings { + accelerate: AccelerateSettings { + topspeed: int(16), + accel: int(160), + }, + dot: (int(1) / 2).sqrt(), + }), + swim: Some(PropulsionSettings { magnitude: int(12) }), + hitbox: Hitbox::roblox(), + camera_offset: int3(0, 2, 0), //4.5-2.5=2 + } + } - pub fn roblox_bhop()->Self{ - Self{ - controls_mask:Controls::all(), - controls_mask_state:Controls::all(), - strafe:Some(StrafeSettings{ - enable:ControlsActivation::full_2d(), - air_accel_limit:None, - mv:int(27)/10, - tick_rate:Ratio64::new(100,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(), - }), - jump:Some(JumpSettings{ - impulse:JumpImpulse::Time(AbsoluteTime::from_micros(715_588)), - calculation:JumpCalculation::Max, - limit_minimum:true, - }), - gravity:int3(0,-100,0), - mass:int(1), - rocket:None, - walk:Some(WalkSettings{ - accelerate:AccelerateSettings{ - topspeed:int(18), - accel:int(90), - }, - static_friction:int(2), - kinetic_friction:int(3),//unrealistic: kinetic friction is typically lower than static - surf_dot:int(3)/4,// normal.y=0.75 - }), - ladder:Some(LadderSettings{ - accelerate:AccelerateSettings{ - topspeed:int(18), - accel:int(180), - }, - dot:(int(1)/2).sqrt(), - }), - swim:Some(PropulsionSettings{ - magnitude:int(12), - }), - hitbox:Hitbox::roblox(), - camera_offset:int3(0,2,0),//4.5-2.5=2 - } - } - pub fn roblox_surf()->Self{ - Self{ - gravity:int3(0,-50,0), - ..Self::roblox_bhop() - } - } - pub fn roblox_rocket()->Self{ - Self{ - strafe:None, - rocket:Some(PropulsionSettings{ - magnitude:int(200), - }), - ..Self::roblox_bhop() - } - } + pub fn roblox_bhop() -> Self { + Self { + controls_mask: Controls::all(), + controls_mask_state: Controls::all(), + strafe: Some(StrafeSettings { + enable: ControlsActivation::full_2d(), + air_accel_limit: None, + mv: int(27) / 10, + tick_rate: Ratio64::new(100, AbsoluteTime::ONE_SECOND.get() as u64).unwrap(), + }), + jump: Some(JumpSettings { + impulse: JumpImpulse::Time(AbsoluteTime::from_micros(715_588)), + calculation: JumpCalculation::Max, + limit_minimum: true, + }), + gravity: int3(0, -100, 0), + mass: int(1), + rocket: None, + walk: Some(WalkSettings { + accelerate: AccelerateSettings { + topspeed: int(18), + accel: int(90), + }, + static_friction: int(2), + kinetic_friction: int(3), //unrealistic: kinetic friction is typically lower than static + surf_dot: int(3) / 4, // normal.y=0.75 + }), + ladder: Some(LadderSettings { + accelerate: AccelerateSettings { + topspeed: int(18), + accel: int(180), + }, + dot: (int(1) / 2).sqrt(), + }), + swim: Some(PropulsionSettings { magnitude: int(12) }), + hitbox: Hitbox::roblox(), + camera_offset: int3(0, 2, 0), //4.5-2.5=2 + } + } + pub fn roblox_surf() -> Self { + Self { + gravity: int3(0, -50, 0), + ..Self::roblox_bhop() + } + } + pub fn roblox_rocket() -> Self { + Self { + strafe: None, + rocket: Some(PropulsionSettings { + magnitude: int(200), + }), + ..Self::roblox_bhop() + } + } - pub fn source_bhop()->Self{ - Self{ - controls_mask:Controls::all()-Controls::MoveUp-Controls::MoveDown, - controls_mask_state:Controls::all(), - strafe:Some(StrafeSettings{ - enable:ControlsActivation::full_2d(), - air_accel_limit:Some(Planar64::raw(150<<28)*100), - mv:(Planar64::raw(30)*VALVE_SCALE).fix_1(), - tick_rate:Ratio64::new(100,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(), - }), - jump:Some(JumpSettings{ - impulse:JumpImpulse::Height((int(52)*VALVE_SCALE).fix_1()), - calculation:JumpCalculation::JumpThenBoost, - limit_minimum:true, - }), - gravity:(int3(0,-800,0)*VALVE_SCALE).fix_1(), - mass:int(1), - rocket:None, - walk:Some(WalkSettings{ - accelerate:AccelerateSettings{ - topspeed:int(18),//? - accel:int(90),//? - }, - static_friction:int(2),//? - kinetic_friction:int(3),//? - surf_dot:int(3)/4,// normal.y=0.75 - }), - ladder:Some(LadderSettings{ - accelerate:AccelerateSettings{ - topspeed:int(18),//? - accel:int(180),//? - }, - dot:(int(1)/2).sqrt(),//? - }), - swim:Some(PropulsionSettings{ - magnitude:int(12),//? - }), - hitbox:Hitbox::source(), - camera_offset:((int3(0,64,0)-(int3(0,73,0)>>1))*VALVE_SCALE).fix_1(), - } - } - pub fn source_surf()->Self{ - Self{ - controls_mask:Controls::all()-Controls::MoveUp-Controls::MoveDown, - controls_mask_state:Controls::all(), - strafe:Some(StrafeSettings{ - enable:ControlsActivation::full_2d(), - air_accel_limit:Some((int(150)*66*VALVE_SCALE).fix_1()), - mv:(int(30)*VALVE_SCALE).fix_1(), - tick_rate:Ratio64::new(66,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(), - }), - jump:Some(JumpSettings{ - impulse:JumpImpulse::Height((int(52)*VALVE_SCALE).fix_1()), - calculation:JumpCalculation::JumpThenBoost, - limit_minimum:true, - }), - gravity:(int3(0,-800,0)*VALVE_SCALE).fix_1(), - mass:int(1), - rocket:None, - walk:Some(WalkSettings{ - accelerate:AccelerateSettings{ - topspeed:int(18),//? - accel:int(90),//? - }, - static_friction:int(2),//? - kinetic_friction:int(3),//? - surf_dot:int(3)/4,// normal.y=0.75 - }), - ladder:Some(LadderSettings{ - accelerate:AccelerateSettings{ - topspeed:int(18),//? - accel:int(180),//? - }, - dot:(int(1)/2).sqrt(),//? - }), - swim:Some(PropulsionSettings{ - magnitude:int(12),//? - }), - hitbox:Hitbox::source(), - camera_offset:((int3(0,64,0)-(int3(0,73,0)>>1))*VALVE_SCALE).fix_1(), - } - } + pub fn source_bhop() -> Self { + Self { + controls_mask: Controls::all() - Controls::MoveUp - Controls::MoveDown, + controls_mask_state: Controls::all(), + strafe: Some(StrafeSettings { + enable: ControlsActivation::full_2d(), + air_accel_limit: Some(Planar64::raw(150 << 28) * 100), + mv: (Planar64::raw(30) * VALVE_SCALE).fix_1(), + tick_rate: Ratio64::new(100, AbsoluteTime::ONE_SECOND.get() as u64).unwrap(), + }), + jump: Some(JumpSettings { + impulse: JumpImpulse::Height((int(52) * VALVE_SCALE).fix_1()), + calculation: JumpCalculation::JumpThenBoost, + limit_minimum: true, + }), + gravity: (int3(0, -800, 0) * VALVE_SCALE).fix_1(), + mass: int(1), + rocket: None, + walk: Some(WalkSettings { + accelerate: AccelerateSettings { + topspeed: int(18), //? + accel: int(90), //? + }, + static_friction: int(2), //? + kinetic_friction: int(3), //? + surf_dot: int(3) / 4, // normal.y=0.75 + }), + ladder: Some(LadderSettings { + accelerate: AccelerateSettings { + topspeed: int(18), //? + accel: int(180), //? + }, + dot: (int(1) / 2).sqrt(), //? + }), + swim: Some(PropulsionSettings { + magnitude: int(12), //? + }), + hitbox: Hitbox::source(), + camera_offset: ((int3(0, 64, 0) - (int3(0, 73, 0) >> 1)) * VALVE_SCALE).fix_1(), + } + } + pub fn source_surf() -> Self { + Self { + controls_mask: Controls::all() - Controls::MoveUp - Controls::MoveDown, + controls_mask_state: Controls::all(), + strafe: Some(StrafeSettings { + enable: ControlsActivation::full_2d(), + air_accel_limit: Some((int(150) * 66 * VALVE_SCALE).fix_1()), + mv: (int(30) * VALVE_SCALE).fix_1(), + tick_rate: Ratio64::new(66, AbsoluteTime::ONE_SECOND.get() as u64).unwrap(), + }), + jump: Some(JumpSettings { + impulse: JumpImpulse::Height((int(52) * VALVE_SCALE).fix_1()), + calculation: JumpCalculation::JumpThenBoost, + limit_minimum: true, + }), + gravity: (int3(0, -800, 0) * VALVE_SCALE).fix_1(), + mass: int(1), + rocket: None, + walk: Some(WalkSettings { + accelerate: AccelerateSettings { + topspeed: int(18), //? + accel: int(90), //? + }, + static_friction: int(2), //? + kinetic_friction: int(3), //? + surf_dot: int(3) / 4, // normal.y=0.75 + }), + ladder: Some(LadderSettings { + accelerate: AccelerateSettings { + topspeed: int(18), //? + accel: int(180), //? + }, + dot: (int(1) / 2).sqrt(), //? + }), + swim: Some(PropulsionSettings { + magnitude: int(12), //? + }), + hitbox: Hitbox::source(), + camera_offset: ((int3(0, 64, 0) - (int3(0, 73, 0) >> 1)) * VALVE_SCALE).fix_1(), + } + } } diff --git a/lib/common/src/instruction.rs b/lib/common/src/instruction.rs index cefc580..a15f141 100644 --- a/lib/common/src/instruction.rs +++ b/lib/common/src/instruction.rs @@ -1,59 +1,66 @@ use crate::integer::Time; #[derive(Debug)] -pub struct TimedInstruction{ - pub time:Time, - pub instruction:I, +pub struct TimedInstruction { + pub time: Time, + pub instruction: I, } -pub trait InstructionEmitter{ - type Instruction; - type TimeInner; - fn next_instruction(&self,time_limit:Time)->Option>; +pub trait InstructionEmitter { + type Instruction; + type TimeInner; + fn next_instruction( + &self, + time_limit: Time, + ) -> Option>; } -pub trait InstructionConsumer{ - type Instruction; - type TimeInner; - fn process_instruction(&mut self, instruction:TimedInstruction); +pub trait InstructionConsumer { + type Instruction; + type TimeInner; + fn process_instruction( + &mut self, + instruction: TimedInstruction, + ); } //PROPER PRIVATE FIELDS!!! -pub struct InstructionCollector{ - time:Time, - instruction:Option, +pub struct InstructionCollector { + time: Time, + instruction: Option, } -impl InstructionCollector - where Time:Copy+PartialOrd, +impl InstructionCollector +where + Time: Copy + PartialOrd, { - pub const fn new(time:Time)->Self{ - Self{ - time, - instruction:None - } - } - #[inline] - pub const fn time(&self)->Time{ - self.time - } - pub fn collect(&mut self,instruction:Option>){ - match instruction{ - Some(unwrap_instruction)=>{ - if unwrap_instruction.time(), - } - } - pub fn instruction(self)->Option>{ - //STEAL INSTRUCTION AND DESTROY INSTRUCTIONCOLLECTOR - match self.instruction{ - Some(instruction)=>Some(TimedInstruction{ - time:self.time, - instruction - }), - None=>None, - } - } + pub const fn new(time: Time) -> Self { + Self { + time, + instruction: None, + } + } + #[inline] + pub const fn time(&self) -> Time { + self.time + } + pub fn collect(&mut self, instruction: Option>) { + match instruction { + Some(unwrap_instruction) => { + if unwrap_instruction.time < self.time { + self.time = unwrap_instruction.time; + self.instruction = Some(unwrap_instruction.instruction); + } + } + None => (), + } + } + pub fn instruction(self) -> Option> { + //STEAL INSTRUCTION AND DESTROY INSTRUCTIONCOLLECTOR + match self.instruction { + Some(instruction) => Some(TimedInstruction { + time: self.time, + instruction, + }), + None => None, + } + } } diff --git a/lib/common/src/integer.rs b/lib/common/src/integer.rs index b557d85..b6db040 100644 --- a/lib/common/src/integer.rs +++ b/lib/common/src/integer.rs @@ -1,679 +1,748 @@ -pub use fixed_wide::fixed::{Fixed,Fix}; -pub use ratio_ops::ratio::{Ratio,Divide}; +pub use fixed_wide::fixed::{Fix, Fixed}; +pub use ratio_ops::ratio::{Divide, Ratio}; //integer units /// specific example of a "default" time type -#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)] -pub enum TimeInner{} -pub type AbsoluteTime=Time; +#[derive(Clone, Copy, Hash, Eq, PartialEq, PartialOrd, Debug)] +pub enum TimeInner {} +pub type AbsoluteTime = Time; -#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)] -pub struct Time(i64,core::marker::PhantomData); -impl Time{ - pub const MIN:Self=Self::raw(i64::MIN); - pub const MAX:Self=Self::raw(i64::MAX); - pub const ZERO:Self=Self::raw(0); - pub const ONE_SECOND:Self=Self::raw(1_000_000_000); - pub const ONE_MILLISECOND:Self=Self::raw(1_000_000); - pub const ONE_MICROSECOND:Self=Self::raw(1_000); - pub const ONE_NANOSECOND:Self=Self::raw(1); - #[inline] - pub const fn raw(num:i64)->Self{ - Self(num,core::marker::PhantomData) - } - #[inline] - pub const fn get(self)->i64{ - self.0 - } - #[inline] - pub const fn from_secs(num:i64)->Self{ - Self::raw(Self::ONE_SECOND.0*num) - } - #[inline] - pub const fn from_millis(num:i64)->Self{ - Self::raw(Self::ONE_MILLISECOND.0*num) - } - #[inline] - pub const fn from_micros(num:i64)->Self{ - Self::raw(Self::ONE_MICROSECOND.0*num) - } - #[inline] - pub const fn from_nanos(num:i64)->Self{ - Self::raw(Self::ONE_NANOSECOND.0*num) - } - //should I have checked subtraction? force all time variables to be positive? - #[inline] - pub const fn nanos(self)->i64{ - self.0 - } - #[inline] - pub const fn to_ratio(self)->Ratio{ - Ratio::new(Planar64::raw(self.0),Planar64::raw(1_000_000_000)) - } - #[inline] - pub const fn coerce(self)->Time{ - Time::raw(self.0) - } +#[derive(Clone, Copy, Hash, Eq, PartialEq, PartialOrd, Debug)] +pub struct Time(i64, core::marker::PhantomData); +impl Time { + pub const MIN: Self = Self::raw(i64::MIN); + pub const MAX: Self = Self::raw(i64::MAX); + pub const ZERO: Self = Self::raw(0); + pub const ONE_SECOND: Self = Self::raw(1_000_000_000); + pub const ONE_MILLISECOND: Self = Self::raw(1_000_000); + pub const ONE_MICROSECOND: Self = Self::raw(1_000); + pub const ONE_NANOSECOND: Self = Self::raw(1); + #[inline] + pub const fn raw(num: i64) -> Self { + Self(num, core::marker::PhantomData) + } + #[inline] + pub const fn get(self) -> i64 { + self.0 + } + #[inline] + pub const fn from_secs(num: i64) -> Self { + Self::raw(Self::ONE_SECOND.0 * num) + } + #[inline] + pub const fn from_millis(num: i64) -> Self { + Self::raw(Self::ONE_MILLISECOND.0 * num) + } + #[inline] + pub const fn from_micros(num: i64) -> Self { + Self::raw(Self::ONE_MICROSECOND.0 * num) + } + #[inline] + pub const fn from_nanos(num: i64) -> Self { + Self::raw(Self::ONE_NANOSECOND.0 * num) + } + //should I have checked subtraction? force all time variables to be positive? + #[inline] + pub const fn nanos(self) -> i64 { + self.0 + } + #[inline] + pub const fn to_ratio(self) -> Ratio { + Ratio::new(Planar64::raw(self.0), Planar64::raw(1_000_000_000)) + } + #[inline] + pub const fn coerce(self) -> Time { + Time::raw(self.0) + } } -impl From for Time{ - #[inline] - fn from(value:Planar64)->Self{ - Self::raw((value*Planar64::raw(1_000_000_000)).fix_1().to_raw()) - } +impl From for Time { + #[inline] + fn from(value: Planar64) -> Self { + Self::raw((value * Planar64::raw(1_000_000_000)).fix_1().to_raw()) + } } -impl From> for Time - where - Num:core::ops::Mul, - N1:Divide, - T1:Fix, +impl From> for Time +where + Num: core::ops::Mul, + N1: Divide, + T1: Fix, { - #[inline] - fn from(value:Ratio)->Self{ - Self::raw((value*Planar64::raw(1_000_000_000)).divide().fix().to_raw()) - } + #[inline] + fn from(value: Ratio) -> Self { + Self::raw( + (value * Planar64::raw(1_000_000_000)) + .divide() + .fix() + .to_raw(), + ) + } } -impl std::fmt::Display for Time{ - #[inline] - fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ - write!(f,"{}s+{:09}ns",self.0/Self::ONE_SECOND.0,self.0%Self::ONE_SECOND.0) - } +impl std::fmt::Display for Time { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}s+{:09}ns", + self.0 / Self::ONE_SECOND.0, + self.0 % Self::ONE_SECOND.0 + ) + } } -impl std::default::Default for Time{ - fn default()->Self{ - Self::raw(0) - } +impl std::default::Default for Time { + fn default() -> Self { + Self::raw(0) + } } -impl std::ops::Neg for Time{ - type Output=Self; - #[inline] - fn neg(self)->Self::Output { - Self::raw(-self.0) - } +impl std::ops::Neg for Time { + type Output = Self; + #[inline] + fn neg(self) -> Self::Output { + Self::raw(-self.0) + } } macro_rules! impl_time_additive_operator { - ($trait:ty, $method:ident) => { - impl $trait for Time{ - type Output=Self; - #[inline] - fn $method(self,rhs:Self)->Self::Output { - Self::raw(self.0.$method(rhs.0)) - } - } - }; + ($trait:ty, $method:ident) => { + impl $trait for Time { + type Output = Self; + #[inline] + fn $method(self, rhs: Self) -> Self::Output { + Self::raw(self.0.$method(rhs.0)) + } + } + }; } -impl_time_additive_operator!(core::ops::Add,add); -impl_time_additive_operator!(core::ops::Sub,sub); -impl_time_additive_operator!(core::ops::Rem,rem); +impl_time_additive_operator!(core::ops::Add, add); +impl_time_additive_operator!(core::ops::Sub, sub); +impl_time_additive_operator!(core::ops::Rem, rem); macro_rules! impl_time_additive_assign_operator { - ($trait:ty, $method:ident) => { - impl $trait for Time{ - #[inline] - fn $method(&mut self,rhs:Self){ - self.0.$method(rhs.0) - } - } - }; + ($trait:ty, $method:ident) => { + impl $trait for Time { + #[inline] + fn $method(&mut self, rhs: Self) { + self.0.$method(rhs.0) + } + } + }; } -impl_time_additive_assign_operator!(core::ops::AddAssign,add_assign); -impl_time_additive_assign_operator!(core::ops::SubAssign,sub_assign); -impl_time_additive_assign_operator!(core::ops::RemAssign,rem_assign); -impl std::ops::Mul for Time{ - type Output=Ratio,fixed_wide::fixed::Fixed<2,64>>; - #[inline] - fn mul(self,rhs:Self)->Self::Output{ - Ratio::new(Fixed::raw(self.0)*Fixed::raw(rhs.0),Fixed::raw_digit(1_000_000_000i64.pow(2))) - } +impl_time_additive_assign_operator!(core::ops::AddAssign, add_assign); +impl_time_additive_assign_operator!(core::ops::SubAssign, sub_assign); +impl_time_additive_assign_operator!(core::ops::RemAssign, rem_assign); +impl std::ops::Mul for Time { + type Output = Ratio, fixed_wide::fixed::Fixed<2, 64>>; + #[inline] + fn mul(self, rhs: Self) -> Self::Output { + Ratio::new( + Fixed::raw(self.0) * Fixed::raw(rhs.0), + Fixed::raw_digit(1_000_000_000i64.pow(2)), + ) + } } -impl std::ops::Div for Time{ - type Output=Self; - #[inline] - fn div(self,rhs:i64)->Self::Output{ - Self::raw(self.0/rhs) - } +impl std::ops::Div for Time { + type Output = Self; + #[inline] + fn div(self, rhs: i64) -> Self::Output { + Self::raw(self.0 / rhs) + } } -impl std::ops::Mul for Time{ - type Output=Self; - #[inline] - fn mul(self,rhs:i64)->Self::Output{ - Self::raw(self.0*rhs) - } +impl std::ops::Mul for Time { + type Output = Self; + #[inline] + fn mul(self, rhs: i64) -> Self::Output { + Self::raw(self.0 * rhs) + } } -impl core::ops::Mul> for Planar64{ - type Output=Ratio,Planar64>; - fn mul(self,rhs:Time)->Self::Output{ - Ratio::new(self*Fixed::raw(rhs.0),Planar64::raw(1_000_000_000)) - } +impl core::ops::Mul> for Planar64 { + type Output = Ratio, Planar64>; + fn mul(self, rhs: Time) -> Self::Output { + Ratio::new(self * Fixed::raw(rhs.0), Planar64::raw(1_000_000_000)) + } } #[cfg(test)] -mod test_time{ - use super::*; - type Time=super::AbsoluteTime; - #[test] - fn time_from_planar64(){ - let a:Time=Planar64::from(1).into(); - assert_eq!(a,Time::ONE_SECOND); - } - #[test] - fn time_from_ratio(){ - let a:Time=Ratio::new(Planar64::from(1),Planar64::from(1)).into(); - assert_eq!(a,Time::ONE_SECOND); - } - #[test] - fn time_squared(){ - let a=Time::from_secs(2); - assert_eq!(a*a,Ratio::new(Fixed::<2,64>::raw_digit(1_000_000_000i64.pow(2))*4,Fixed::<2,64>::raw_digit(1_000_000_000i64.pow(2)))); - } - #[test] - fn time_times_planar64(){ - let a=Time::from_secs(2); - let b=Planar64::from(2); - assert_eq!(b*a,Ratio::new(Fixed::<2,64>::raw_digit(1_000_000_000*(1<<32))<<2,Fixed::<1,32>::raw_digit(1_000_000_000))); - } +mod test_time { + use super::*; + type Time = super::AbsoluteTime; + #[test] + fn time_from_planar64() { + let a: Time = Planar64::from(1).into(); + assert_eq!(a, Time::ONE_SECOND); + } + #[test] + fn time_from_ratio() { + let a: Time = Ratio::new(Planar64::from(1), Planar64::from(1)).into(); + assert_eq!(a, Time::ONE_SECOND); + } + #[test] + fn time_squared() { + let a = Time::from_secs(2); + assert_eq!( + a * a, + Ratio::new( + Fixed::<2, 64>::raw_digit(1_000_000_000i64.pow(2)) * 4, + Fixed::<2, 64>::raw_digit(1_000_000_000i64.pow(2)) + ) + ); + } + #[test] + fn time_times_planar64() { + let a = Time::from_secs(2); + let b = Planar64::from(2); + assert_eq!( + b * a, + Ratio::new( + Fixed::<2, 64>::raw_digit(1_000_000_000 * (1 << 32)) << 2, + Fixed::<1, 32>::raw_digit(1_000_000_000) + ) + ); + } } #[inline] -const fn gcd(mut a:u64,mut b:u64)->u64{ - while b!=0{ - (a,b)=(b,a.rem_euclid(b)); - }; - a +const fn gcd(mut a: u64, mut b: u64) -> u64 { + while b != 0 { + (a, b) = (b, a.rem_euclid(b)); + } + a } -#[derive(Clone,Copy,Debug,Hash)] -pub struct Ratio64{ - num:i64, - den:u64, +#[derive(Clone, Copy, Debug, Hash)] +pub struct Ratio64 { + num: i64, + den: u64, } -impl Ratio64{ - pub const ZERO:Self=Ratio64{num:0,den:1}; - pub const ONE:Self=Ratio64{num:1,den:1}; - #[inline] - pub const fn new(num:i64,den:u64)->Option{ - if den==0{ - None - }else{ - let d=gcd(num.unsigned_abs(),den); - Some(Self{num:num/(d as i64),den:den/d}) - } - } - #[inline] - pub const fn num(self)->i64{ - self.num - } - #[inline] - pub const fn den(self)->u64{ - self.den - } - #[inline] - pub const fn mul_int(&self,rhs:i64)->i64{ - rhs*self.num/(self.den as i64) - } - #[inline] - pub const fn rhs_div_int(&self,rhs:i64)->i64{ - rhs*(self.den as i64)/self.num - } - #[inline] - pub const fn mul_ref(&self,rhs:&Ratio64)->Ratio64{ - let (num,den)=(self.num*rhs.num,self.den*rhs.den); - let d=gcd(num.unsigned_abs(),den); - Self{ - num:num/(d as i64), - den:den/d, - } - } +impl Ratio64 { + pub const ZERO: Self = Ratio64 { num: 0, den: 1 }; + pub const ONE: Self = Ratio64 { num: 1, den: 1 }; + #[inline] + pub const fn new(num: i64, den: u64) -> Option { + if den == 0 { + None + } else { + let d = gcd(num.unsigned_abs(), den); + Some(Self { + num: num / (d as i64), + den: den / d, + }) + } + } + #[inline] + pub const fn num(self) -> i64 { + self.num + } + #[inline] + pub const fn den(self) -> u64 { + self.den + } + #[inline] + pub const fn mul_int(&self, rhs: i64) -> i64 { + rhs * self.num / (self.den as i64) + } + #[inline] + pub const fn rhs_div_int(&self, rhs: i64) -> i64 { + rhs * (self.den as i64) / self.num + } + #[inline] + pub const fn mul_ref(&self, rhs: &Ratio64) -> Ratio64 { + let (num, den) = (self.num * rhs.num, self.den * rhs.den); + let d = gcd(num.unsigned_abs(), den); + Self { + num: num / (d as i64), + den: den / d, + } + } } //from num_traits crate #[inline] fn integer_decode_f32(f: f32) -> (u64, i16, i8) { - let bits: u32 = f.to_bits(); - let sign: i8 = if bits >> 31 == 0 { 1 } else { -1 }; - let mut exponent: i16 = ((bits >> 23) & 0xff) as i16; - let mantissa = if exponent == 0 { - (bits & 0x7fffff) << 1 - } else { - (bits & 0x7fffff) | 0x800000 - }; - // Exponent bias + mantissa shift - exponent -= 127 + 23; - (mantissa as u64, exponent, sign) + let bits: u32 = f.to_bits(); + let sign: i8 = if bits >> 31 == 0 { 1 } else { -1 }; + let mut exponent: i16 = ((bits >> 23) & 0xff) as i16; + let mantissa = if exponent == 0 { + (bits & 0x7fffff) << 1 + } else { + (bits & 0x7fffff) | 0x800000 + }; + // Exponent bias + mantissa shift + exponent -= 127 + 23; + (mantissa as u64, exponent, sign) } #[inline] fn integer_decode_f64(f: f64) -> (u64, i16, i8) { - let bits: u64 = f.to_bits(); - let sign: i8 = if bits >> 63 == 0 { 1 } else { -1 }; - let mut exponent: i16 = ((bits >> 52) & 0x7ff) as i16; - let mantissa = if exponent == 0 { - (bits & 0xfffffffffffff) << 1 - } else { - (bits & 0xfffffffffffff) | 0x10000000000000 - }; - // Exponent bias + mantissa shift - exponent -= 1023 + 52; - (mantissa, exponent, sign) + let bits: u64 = f.to_bits(); + let sign: i8 = if bits >> 63 == 0 { 1 } else { -1 }; + let mut exponent: i16 = ((bits >> 52) & 0x7ff) as i16; + let mantissa = if exponent == 0 { + (bits & 0xfffffffffffff) << 1 + } else { + (bits & 0xfffffffffffff) | 0x10000000000000 + }; + // Exponent bias + mantissa shift + exponent -= 1023 + 52; + (mantissa, exponent, sign) } #[derive(Debug)] -pub enum Ratio64TryFromFloatError{ - Nan, - Infinite, - Subnormal, - HighlyNegativeExponent(i16), - HighlyPositiveExponent(i16), +pub enum Ratio64TryFromFloatError { + Nan, + Infinite, + Subnormal, + HighlyNegativeExponent(i16), + HighlyPositiveExponent(i16), } -const MAX_DENOMINATOR:u128=u64::MAX as u128; +const MAX_DENOMINATOR: u128 = u64::MAX as u128; #[inline] -fn ratio64_from_mes((m,e,s):(u64,i16,i8))->Result{ - if e< -127{ - //this can also just be zero - Err(Ratio64TryFromFloatError::HighlyNegativeExponent(e)) - }else if e< -63{ - //approximate input ratio within denominator limit - let mut target_num=m as u128; - let mut target_den=1u128<<-e; +fn ratio64_from_mes((m, e, s): (u64, i16, i8)) -> Result { + if e < -127 { + //this can also just be zero + Err(Ratio64TryFromFloatError::HighlyNegativeExponent(e)) + } else if e < -63 { + //approximate input ratio within denominator limit + let mut target_num = m as u128; + let mut target_den = 1u128 << -e; - let mut num=1; - let mut den=0; - let mut prev_num=0; - let mut prev_den=1; + let mut num = 1; + let mut den = 0; + let mut prev_num = 0; + let mut prev_den = 1; - while target_den!=0{ - let whole=target_num/target_den; - (target_num,target_den)=(target_den,target_num-whole*target_den); - let new_num=whole*num+prev_num; - let new_den=whole*den+prev_den; - if MAX_DENOMINATOR for Ratio64{ - type Error=Ratio64TryFromFloatError; - #[inline] - fn try_from(value:f32)->Result{ - match value.classify(){ - std::num::FpCategory::Nan=>Err(Self::Error::Nan), - std::num::FpCategory::Infinite=>Err(Self::Error::Infinite), - std::num::FpCategory::Zero=>Ok(Self::ZERO), - std::num::FpCategory::Subnormal - |std::num::FpCategory::Normal=>ratio64_from_mes(integer_decode_f32(value)), - } - } +impl TryFrom for Ratio64 { + type Error = Ratio64TryFromFloatError; + #[inline] + fn try_from(value: f32) -> Result { + match value.classify() { + std::num::FpCategory::Nan => Err(Self::Error::Nan), + std::num::FpCategory::Infinite => Err(Self::Error::Infinite), + std::num::FpCategory::Zero => Ok(Self::ZERO), + std::num::FpCategory::Subnormal | std::num::FpCategory::Normal => { + ratio64_from_mes(integer_decode_f32(value)) + } + } + } } -impl TryFrom for Ratio64{ - type Error=Ratio64TryFromFloatError; - #[inline] - fn try_from(value:f64)->Result{ - match value.classify(){ - std::num::FpCategory::Nan=>Err(Self::Error::Nan), - std::num::FpCategory::Infinite=>Err(Self::Error::Infinite), - std::num::FpCategory::Zero=>Ok(Self::ZERO), - std::num::FpCategory::Subnormal - |std::num::FpCategory::Normal=>ratio64_from_mes(integer_decode_f64(value)), - } - } +impl TryFrom for Ratio64 { + type Error = Ratio64TryFromFloatError; + #[inline] + fn try_from(value: f64) -> Result { + match value.classify() { + std::num::FpCategory::Nan => Err(Self::Error::Nan), + std::num::FpCategory::Infinite => Err(Self::Error::Infinite), + std::num::FpCategory::Zero => Ok(Self::ZERO), + std::num::FpCategory::Subnormal | std::num::FpCategory::Normal => { + ratio64_from_mes(integer_decode_f64(value)) + } + } + } } -impl std::ops::Mul for Ratio64{ - type Output=Ratio64; - #[inline] - fn mul(self,rhs:Ratio64)->Self::Output{ - let (num,den)=(self.num*rhs.num,self.den*rhs.den); - let d=gcd(num.unsigned_abs(),den); - Self{ - num:num/(d as i64), - den:den/d, - } - } +impl std::ops::Mul for Ratio64 { + type Output = Ratio64; + #[inline] + fn mul(self, rhs: Ratio64) -> Self::Output { + let (num, den) = (self.num * rhs.num, self.den * rhs.den); + let d = gcd(num.unsigned_abs(), den); + Self { + num: num / (d as i64), + den: den / d, + } + } } -impl std::ops::Mul for Ratio64{ - type Output=Ratio64; - #[inline] - fn mul(self,rhs:i64)->Self::Output { - Self{ - num:self.num*rhs, - den:self.den, - } - } +impl std::ops::Mul for Ratio64 { + type Output = Ratio64; + #[inline] + fn mul(self, rhs: i64) -> Self::Output { + Self { + num: self.num * rhs, + den: self.den, + } + } } -impl std::ops::Div for Ratio64{ - type Output=Ratio64; - #[inline] - fn div(self,rhs:u64)->Self::Output { - Self{ - num:self.num, - den:self.den*rhs, - } - } +impl std::ops::Div for Ratio64 { + type Output = Ratio64; + #[inline] + fn div(self, rhs: u64) -> Self::Output { + Self { + num: self.num, + den: self.den * rhs, + } + } } -#[derive(Clone,Copy,Debug,Hash)] -pub struct Ratio64Vec2{ - pub x:Ratio64, - pub y:Ratio64, +#[derive(Clone, Copy, Debug, Hash)] +pub struct Ratio64Vec2 { + pub x: Ratio64, + pub y: Ratio64, } -impl Ratio64Vec2{ - pub const ONE:Self=Self{x:Ratio64::ONE,y:Ratio64::ONE}; - #[inline] - pub const fn new(x:Ratio64,y:Ratio64)->Self{ - Self{x,y} - } - #[inline] - pub const fn mul_int(&self,rhs:glam::I64Vec2)->glam::I64Vec2{ - glam::i64vec2( - self.x.mul_int(rhs.x), - self.y.mul_int(rhs.y), - ) - } +impl Ratio64Vec2 { + pub const ONE: Self = Self { + x: Ratio64::ONE, + y: Ratio64::ONE, + }; + #[inline] + pub const fn new(x: Ratio64, y: Ratio64) -> Self { + Self { x, y } + } + #[inline] + pub const fn mul_int(&self, rhs: glam::I64Vec2) -> glam::I64Vec2 { + glam::i64vec2(self.x.mul_int(rhs.x), self.y.mul_int(rhs.y)) + } } -impl std::ops::Mul for Ratio64Vec2{ - type Output=Ratio64Vec2; - #[inline] - fn mul(self,rhs:i64)->Self::Output { - Self{ - x:self.x*rhs, - y:self.y*rhs, - } - } +impl std::ops::Mul for Ratio64Vec2 { + type Output = Ratio64Vec2; + #[inline] + fn mul(self, rhs: i64) -> Self::Output { + Self { + x: self.x * rhs, + y: self.y * rhs, + } + } } ///[-pi,pi) = [-2^31,2^31-1] -#[derive(Clone,Copy,Hash)] +#[derive(Clone, Copy, Hash)] pub struct Angle32(i32); -impl Angle32{ - const ANGLE32_TO_FLOAT64_RADIANS:f64=std::f64::consts::PI/((1i64<<31) as f64); - pub const FRAC_PI_2:Self=Self(1<<30); - pub const NEG_FRAC_PI_2:Self=Self(-1<<30); - pub const PI:Self=Self(-1<<31); - #[inline] - pub const fn wrap_from_i64(theta:i64)->Self{ - //take lower bits - //note: this was checked on compiler explorer and compiles to 1 instruction! - Self(i32::from_ne_bytes(((theta&((1<<32)-1)) as u32).to_ne_bytes())) - } - #[inline] - pub fn clamp_from_i64(theta:i64)->Self{ - //the assembly is a bit confusing for this, I thought it was checking the same thing twice - //but it's just checking and then overwriting the value for both upper and lower bounds. - Self(theta.clamp(i32::MIN as i64,i32::MAX as i64) as i32) - } - #[inline] - pub const fn get(&self)->i32{ - self.0 - } - /// Clamps the value towards the midpoint of the range. - /// Note that theta_min can be larger than theta_max and it will wrap clamp the other way around - #[inline] - pub fn clamp(&self,theta_min:Self,theta_max:Self)->Self{ - //((max-min as u32)/2 as i32)+min - let midpoint=(( - (theta_max.0 as u32) - .wrapping_sub(theta_min.0 as u32) - /2 - ) as i32)//(u32::MAX/2) as i32 ALWAYS works - .wrapping_add(theta_min.0); - //(theta-mid).clamp(max-mid,min-mid)+mid - Self( - self.0.wrapping_sub(midpoint) - .max(theta_min.0.wrapping_sub(midpoint)) - .min(theta_max.0.wrapping_sub(midpoint)) - .wrapping_add(midpoint) - ) - } - #[inline] - pub fn cos_sin(&self)->(Planar64,Planar64){ - /* - //cordic - let a=self.0 as u32; - //initialize based on the quadrant - let (mut x,mut y)=match (a&(1<<31)!=0,a&(1<<30)!=0){ - (false,false)=>( 1i64<<32, 0i64 ),//TR - (false,true )=>( 0i64 , 1i64<<32),//TL - (true ,false)=>(-1i64<<32, 0i64 ),//BL - (true ,true )=>( 0i64 ,-1i64<<32),//BR - }; - println!("x={} y={}",Planar64::raw(x),Planar64::raw(y)); - for i in 0..30{ - if a&(1<<(29-i))!=0{ - (x,y)=(x-(y>>i),y+(x>>i)); - } - println!("i={i} t={} x={} y={}",(a&(1<<(29-i))!=0) as u8,Planar64::raw(x),Planar64::raw(y)); - } - //don't forget the gain - (Planar64::raw(x),Planar64::raw(y)) - */ - let (s,c)=(self.0 as f64*Self::ANGLE32_TO_FLOAT64_RADIANS).sin_cos(); - (Planar64::raw((c*((1u64<<32) as f64)) as i64),Planar64::raw((s*((1u64<<32) as f64)) as i64)) - } +impl Angle32 { + const ANGLE32_TO_FLOAT64_RADIANS: f64 = std::f64::consts::PI / ((1i64 << 31) as f64); + pub const FRAC_PI_2: Self = Self(1 << 30); + pub const NEG_FRAC_PI_2: Self = Self(-1 << 30); + pub const PI: Self = Self(-1 << 31); + #[inline] + pub const fn wrap_from_i64(theta: i64) -> Self { + //take lower bits + //note: this was checked on compiler explorer and compiles to 1 instruction! + Self(i32::from_ne_bytes( + ((theta & ((1 << 32) - 1)) as u32).to_ne_bytes(), + )) + } + #[inline] + pub fn clamp_from_i64(theta: i64) -> Self { + //the assembly is a bit confusing for this, I thought it was checking the same thing twice + //but it's just checking and then overwriting the value for both upper and lower bounds. + Self(theta.clamp(i32::MIN as i64, i32::MAX as i64) as i32) + } + #[inline] + pub const fn get(&self) -> i32 { + self.0 + } + /// Clamps the value towards the midpoint of the range. + /// Note that theta_min can be larger than theta_max and it will wrap clamp the other way around + #[inline] + pub fn clamp(&self, theta_min: Self, theta_max: Self) -> Self { + //((max-min as u32)/2 as i32)+min + let midpoint = (((theta_max.0 as u32).wrapping_sub(theta_min.0 as u32) / 2) as i32) //(u32::MAX/2) as i32 ALWAYS works + .wrapping_add(theta_min.0); + //(theta-mid).clamp(max-mid,min-mid)+mid + Self( + self.0 + .wrapping_sub(midpoint) + .max(theta_min.0.wrapping_sub(midpoint)) + .min(theta_max.0.wrapping_sub(midpoint)) + .wrapping_add(midpoint), + ) + } + #[inline] + pub fn cos_sin(&self) -> (Planar64, Planar64) { + /* + //cordic + let a=self.0 as u32; + //initialize based on the quadrant + let (mut x,mut y)=match (a&(1<<31)!=0,a&(1<<30)!=0){ + (false,false)=>( 1i64<<32, 0i64 ),//TR + (false,true )=>( 0i64 , 1i64<<32),//TL + (true ,false)=>(-1i64<<32, 0i64 ),//BL + (true ,true )=>( 0i64 ,-1i64<<32),//BR + }; + println!("x={} y={}",Planar64::raw(x),Planar64::raw(y)); + for i in 0..30{ + if a&(1<<(29-i))!=0{ + (x,y)=(x-(y>>i),y+(x>>i)); + } + println!("i={i} t={} x={} y={}",(a&(1<<(29-i))!=0) as u8,Planar64::raw(x),Planar64::raw(y)); + } + //don't forget the gain + (Planar64::raw(x),Planar64::raw(y)) + */ + let (s, c) = (self.0 as f64 * Self::ANGLE32_TO_FLOAT64_RADIANS).sin_cos(); + ( + Planar64::raw((c * ((1u64 << 32) as f64)) as i64), + Planar64::raw((s * ((1u64 << 32) as f64)) as i64), + ) + } } -impl Into for Angle32{ - #[inline] - fn into(self)->f32{ - (self.0 as f64*Self::ANGLE32_TO_FLOAT64_RADIANS) as f32 - } +impl Into for Angle32 { + #[inline] + fn into(self) -> f32 { + (self.0 as f64 * Self::ANGLE32_TO_FLOAT64_RADIANS) as f32 + } } -impl std::ops::Neg for Angle32{ - type Output=Angle32; - #[inline] - fn neg(self)->Self::Output{ - Angle32(self.0.wrapping_neg()) - } +impl std::ops::Neg for Angle32 { + type Output = Angle32; + #[inline] + fn neg(self) -> Self::Output { + Angle32(self.0.wrapping_neg()) + } } -impl std::ops::Add for Angle32{ - type Output=Angle32; - #[inline] - fn add(self,rhs:Self)->Self::Output { - Angle32(self.0.wrapping_add(rhs.0)) - } +impl std::ops::Add for Angle32 { + type Output = Angle32; + #[inline] + fn add(self, rhs: Self) -> Self::Output { + Angle32(self.0.wrapping_add(rhs.0)) + } } -impl std::ops::Sub for Angle32{ - type Output=Angle32; - #[inline] - fn sub(self,rhs:Self)->Self::Output { - Angle32(self.0.wrapping_sub(rhs.0)) - } +impl std::ops::Sub for Angle32 { + type Output = Angle32; + #[inline] + fn sub(self, rhs: Self) -> Self::Output { + Angle32(self.0.wrapping_sub(rhs.0)) + } } -impl std::ops::Mul for Angle32{ - type Output=Angle32; - #[inline] - fn mul(self,rhs:i32)->Self::Output { - Angle32(self.0.wrapping_mul(rhs)) - } +impl std::ops::Mul for Angle32 { + type Output = Angle32; + #[inline] + fn mul(self, rhs: i32) -> Self::Output { + Angle32(self.0.wrapping_mul(rhs)) + } } -impl std::ops::Mul for Angle32{ - type Output=Angle32; - #[inline] - fn mul(self,rhs:Self)->Self::Output { - Angle32(self.0.wrapping_mul(rhs.0)) - } +impl std::ops::Mul for Angle32 { + type Output = Angle32; + #[inline] + fn mul(self, rhs: Self) -> Self::Output { + Angle32(self.0.wrapping_mul(rhs.0)) + } } #[test] -fn angle_sin_cos(){ - fn close_enough(lhs:Planar64,rhs:Planar64)->bool{ - (lhs-rhs).abs() bool { + (lhs - rhs).abs() < Planar64::EPSILON * 4 + } + fn test_angle(f: f64) { + let a = Angle32((f / Angle32::ANGLE32_TO_FLOAT64_RADIANS) as i32); + println!("a={:#034b}", a.0); + let (c, s) = a.cos_sin(); + let h = (s * s + c * c).sqrt(); + println!("cordic s={} c={}", (s / h).divide(), (c / h).divide()); + let (fs, fc) = f.sin_cos(); + println!("float s={} c={}", fs, fc); + assert!(close_enough( + (c / h).divide().fix_1(), + Planar64::raw((fc * ((1u64 << 32) as f64)) as i64) + )); + assert!(close_enough( + (s / h).divide().fix_1(), + Planar64::raw((fs * ((1u64 << 32) as f64)) as i64) + )); + } + test_angle(1.0); + test_angle(std::f64::consts::PI / 4.0); + test_angle(std::f64::consts::PI / 8.0); } /* Unit type unused for now, may revive it for map files ///[-1.0,1.0] = [-2^30,2^30] pub struct Unit32(i32); impl Unit32{ - #[inline] - pub fn as_planar64(&self) -> Planar64{ - Planar64(4*(self.0 as i64)) - } + #[inline] + pub fn as_planar64(&self) -> Planar64{ + Planar64(4*(self.0 as i64)) + } } const UNIT32_ONE_FLOAT64=((1<<30) as f64); ///[-1.0,1.0] = [-2^30,2^30] pub struct Unit32Vec3(glam::IVec3); impl TryFrom<[f32;3]> for Unit32Vec3{ - type Error=Unit32TryFromFloatError; - fn try_from(value:[f32;3])->Result{ - Ok(Self(glam::ivec3( - Unit32::try_from(Planar64::try_from(value[0])?)?.0, - Unit32::try_from(Planar64::try_from(value[1])?)?.0, - Unit32::try_from(Planar64::try_from(value[2])?)?.0, - ))) - } + type Error=Unit32TryFromFloatError; + fn try_from(value:[f32;3])->Result{ + Ok(Self(glam::ivec3( + Unit32::try_from(Planar64::try_from(value[0])?)?.0, + Unit32::try_from(Planar64::try_from(value[1])?)?.0, + Unit32::try_from(Planar64::try_from(value[2])?)?.0, + ))) + } } */ -pub type Planar64TryFromFloatError=fixed_wide::fixed::FixedFromFloatError; -pub type Planar64=fixed_wide::types::I32F32; -pub type Planar64Vec3=linear_ops::types::Vector3; -pub type Planar64Mat3=linear_ops::types::Matrix3; -pub mod vec3{ - use super::*; - pub use linear_ops::types::Vector3; - pub const MIN:Planar64Vec3=Planar64Vec3::new([Planar64::MIN;3]); - pub const MAX:Planar64Vec3=Planar64Vec3::new([Planar64::MAX;3]); - pub const ZERO:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO;3]); - pub const ZERO_2:linear_ops::types::Vector3>=linear_ops::types::Vector3::new([Fixed::<2,64>::ZERO;3]); - pub const X:Planar64Vec3=Planar64Vec3::new([Planar64::ONE,Planar64::ZERO,Planar64::ZERO]); - pub const Y:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO,Planar64::ONE,Planar64::ZERO]); - pub const Z:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO,Planar64::ZERO,Planar64::ONE]); - pub const ONE:Planar64Vec3=Planar64Vec3::new([Planar64::ONE,Planar64::ONE,Planar64::ONE]); - pub const NEG_X:Planar64Vec3=Planar64Vec3::new([Planar64::NEG_ONE,Planar64::ZERO,Planar64::ZERO]); - pub const NEG_Y:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO,Planar64::NEG_ONE,Planar64::ZERO]); - pub const NEG_Z:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO,Planar64::ZERO,Planar64::NEG_ONE]); - pub const NEG_ONE:Planar64Vec3=Planar64Vec3::new([Planar64::NEG_ONE,Planar64::NEG_ONE,Planar64::NEG_ONE]); - #[inline] - pub const fn int(x:i32,y:i32,z:i32)->Planar64Vec3{ - Planar64Vec3::new([Planar64::raw((x as i64)<<32),Planar64::raw((y as i64)<<32),Planar64::raw((z as i64)<<32)]) - } - #[inline] - pub fn raw_array(array:[i64;3])->Planar64Vec3{ - Planar64Vec3::new(array.map(Planar64::raw)) - } - #[inline] - pub fn raw_xyz(x:i64,y:i64,z:i64)->Planar64Vec3{ - Planar64Vec3::new([Planar64::raw(x),Planar64::raw(y),Planar64::raw(z)]) - } - #[inline] - pub fn try_from_f32_array([x,y,z]:[f32;3])->Result{ - Ok(Planar64Vec3::new([ - try_from_f32(x)?, - try_from_f32(y)?, - try_from_f32(z)?, - ])) - } +pub type Planar64TryFromFloatError = fixed_wide::fixed::FixedFromFloatError; +pub type Planar64 = fixed_wide::types::I32F32; +pub type Planar64Vec3 = linear_ops::types::Vector3; +pub type Planar64Mat3 = linear_ops::types::Matrix3; +pub mod vec3 { + use super::*; + pub use linear_ops::types::Vector3; + pub const MIN: Planar64Vec3 = Planar64Vec3::new([Planar64::MIN; 3]); + pub const MAX: Planar64Vec3 = Planar64Vec3::new([Planar64::MAX; 3]); + pub const ZERO: Planar64Vec3 = Planar64Vec3::new([Planar64::ZERO; 3]); + pub const ZERO_2: linear_ops::types::Vector3> = + linear_ops::types::Vector3::new([Fixed::<2, 64>::ZERO; 3]); + pub const X: Planar64Vec3 = Planar64Vec3::new([Planar64::ONE, Planar64::ZERO, Planar64::ZERO]); + pub const Y: Planar64Vec3 = Planar64Vec3::new([Planar64::ZERO, Planar64::ONE, Planar64::ZERO]); + pub const Z: Planar64Vec3 = Planar64Vec3::new([Planar64::ZERO, Planar64::ZERO, Planar64::ONE]); + pub const ONE: Planar64Vec3 = Planar64Vec3::new([Planar64::ONE, Planar64::ONE, Planar64::ONE]); + pub const NEG_X: Planar64Vec3 = + Planar64Vec3::new([Planar64::NEG_ONE, Planar64::ZERO, Planar64::ZERO]); + pub const NEG_Y: Planar64Vec3 = + Planar64Vec3::new([Planar64::ZERO, Planar64::NEG_ONE, Planar64::ZERO]); + pub const NEG_Z: Planar64Vec3 = + Planar64Vec3::new([Planar64::ZERO, Planar64::ZERO, Planar64::NEG_ONE]); + pub const NEG_ONE: Planar64Vec3 = + Planar64Vec3::new([Planar64::NEG_ONE, Planar64::NEG_ONE, Planar64::NEG_ONE]); + #[inline] + pub const fn int(x: i32, y: i32, z: i32) -> Planar64Vec3 { + Planar64Vec3::new([ + Planar64::raw((x as i64) << 32), + Planar64::raw((y as i64) << 32), + Planar64::raw((z as i64) << 32), + ]) + } + #[inline] + pub fn raw_array(array: [i64; 3]) -> Planar64Vec3 { + Planar64Vec3::new(array.map(Planar64::raw)) + } + #[inline] + pub fn raw_xyz(x: i64, y: i64, z: i64) -> Planar64Vec3 { + Planar64Vec3::new([Planar64::raw(x), Planar64::raw(y), Planar64::raw(z)]) + } + #[inline] + pub fn try_from_f32_array( + [x, y, z]: [f32; 3], + ) -> Result { + Ok(Planar64Vec3::new([ + try_from_f32(x)?, + try_from_f32(y)?, + try_from_f32(z)?, + ])) + } } #[inline] -pub fn int(value:i32)->Planar64{ - Planar64::from(value) +pub fn int(value: i32) -> Planar64 { + Planar64::from(value) } #[inline] -pub fn try_from_f32(value:f32)->Result{ - let result:Result=value.try_into(); - match result{ - Ok(ok)=>Ok(ok), - Err(e)=>e.underflow_to_zero(), - } +pub fn try_from_f32(value: f32) -> Result { + let result: Result = value.try_into(); + match result { + Ok(ok) => Ok(ok), + Err(e) => e.underflow_to_zero(), + } } -pub mod mat3{ - use super::*; - pub use linear_ops::types::Matrix3; - #[inline] - pub const fn identity()->Planar64Mat3{ - Planar64Mat3::new([ - [Planar64::ONE,Planar64::ZERO,Planar64::ZERO], - [Planar64::ZERO,Planar64::ONE,Planar64::ZERO], - [Planar64::ZERO,Planar64::ZERO,Planar64::ONE], - ]) - } - #[inline] - pub fn from_diagonal(diag:Planar64Vec3)->Planar64Mat3{ - Planar64Mat3::new([ - [diag.x,Planar64::ZERO,Planar64::ZERO], - [Planar64::ZERO,diag.y,Planar64::ZERO], - [Planar64::ZERO,Planar64::ZERO,diag.z], - ]) - } - #[inline] - pub fn from_rotation_yx(x:Angle32,y:Angle32)->Planar64Mat3{ - let (xc,xs)=x.cos_sin(); - let (yc,ys)=y.cos_sin(); - Planar64Mat3::from_cols([ - Planar64Vec3::new([xc,Planar64::ZERO,-xs]), - Planar64Vec3::new([(xs*ys).fix_1(),yc,(xc*ys).fix_1()]), - Planar64Vec3::new([(xs*yc).fix_1(),-ys,(xc*yc).fix_1()]), - ]) - } - #[inline] - pub fn from_rotation_y(y:Angle32)->Planar64Mat3{ - let (c,s)=y.cos_sin(); - Planar64Mat3::from_cols([ - Planar64Vec3::new([c,Planar64::ZERO,-s]), - vec3::Y, - Planar64Vec3::new([s,Planar64::ZERO,c]), - ]) - } - #[inline] - pub fn try_from_f32_array_2d([x_axis,y_axis,z_axis]:[[f32;3];3])->Result{ - Ok(Planar64Mat3::new([ - vec3::try_from_f32_array(x_axis)?.to_array(), - vec3::try_from_f32_array(y_axis)?.to_array(), - vec3::try_from_f32_array(z_axis)?.to_array(), - ])) - } +pub mod mat3 { + use super::*; + pub use linear_ops::types::Matrix3; + #[inline] + pub const fn identity() -> Planar64Mat3 { + Planar64Mat3::new([ + [Planar64::ONE, Planar64::ZERO, Planar64::ZERO], + [Planar64::ZERO, Planar64::ONE, Planar64::ZERO], + [Planar64::ZERO, Planar64::ZERO, Planar64::ONE], + ]) + } + #[inline] + pub fn from_diagonal(diag: Planar64Vec3) -> Planar64Mat3 { + Planar64Mat3::new([ + [diag.x, Planar64::ZERO, Planar64::ZERO], + [Planar64::ZERO, diag.y, Planar64::ZERO], + [Planar64::ZERO, Planar64::ZERO, diag.z], + ]) + } + #[inline] + pub fn from_rotation_yx(x: Angle32, y: Angle32) -> Planar64Mat3 { + let (xc, xs) = x.cos_sin(); + let (yc, ys) = y.cos_sin(); + Planar64Mat3::from_cols([ + Planar64Vec3::new([xc, Planar64::ZERO, -xs]), + Planar64Vec3::new([(xs * ys).fix_1(), yc, (xc * ys).fix_1()]), + Planar64Vec3::new([(xs * yc).fix_1(), -ys, (xc * yc).fix_1()]), + ]) + } + #[inline] + pub fn from_rotation_y(y: Angle32) -> Planar64Mat3 { + let (c, s) = y.cos_sin(); + Planar64Mat3::from_cols([ + Planar64Vec3::new([c, Planar64::ZERO, -s]), + vec3::Y, + Planar64Vec3::new([s, Planar64::ZERO, c]), + ]) + } + #[inline] + pub fn try_from_f32_array_2d( + [x_axis, y_axis, z_axis]: [[f32; 3]; 3], + ) -> Result { + Ok(Planar64Mat3::new([ + vec3::try_from_f32_array(x_axis)?.to_array(), + vec3::try_from_f32_array(y_axis)?.to_array(), + vec3::try_from_f32_array(z_axis)?.to_array(), + ])) + } } -#[derive(Clone,Copy,Default,Hash,Eq,PartialEq)] -pub struct Planar64Affine3{ - pub matrix3:Planar64Mat3,//includes scale above 1 - pub translation:Planar64Vec3, +#[derive(Clone, Copy, Default, Hash, Eq, PartialEq)] +pub struct Planar64Affine3 { + pub matrix3: Planar64Mat3, //includes scale above 1 + pub translation: Planar64Vec3, } -impl Planar64Affine3{ - #[inline] - pub const fn new(matrix3:Planar64Mat3,translation:Planar64Vec3)->Self{ - Self{matrix3,translation} - } - #[inline] - pub fn transform_point3(&self,point:Planar64Vec3)->vec3::Vector3>{ - self.translation.fix_2()+self.matrix3*point - } +impl Planar64Affine3 { + #[inline] + pub const fn new(matrix3: Planar64Mat3, translation: Planar64Vec3) -> Self { + Self { + matrix3, + translation, + } + } + #[inline] + pub fn transform_point3(&self, point: Planar64Vec3) -> vec3::Vector3> { + self.translation.fix_2() + self.matrix3 * point + } } -impl Into for Planar64Affine3{ - #[inline] - fn into(self)->glam::Mat4{ - let matrix3=self.matrix3.to_array().map(|row|row.map(Into::::into)); - let translation=self.translation.to_array().map(Into::::into); - glam::Mat4::from_cols_array(&[ - matrix3[0][0],matrix3[0][1],matrix3[0][2],0.0, - matrix3[1][0],matrix3[1][1],matrix3[1][2],0.0, - matrix3[2][0],matrix3[2][1],matrix3[2][2],0.0, - translation[0],translation[1],translation[2],1.0 - ]) - } +impl Into for Planar64Affine3 { + #[inline] + fn into(self) -> glam::Mat4 { + let matrix3 = self + .matrix3 + .to_array() + .map(|row| row.map(Into::::into)); + let translation = self.translation.to_array().map(Into::::into); + glam::Mat4::from_cols_array(&[ + matrix3[0][0], + matrix3[0][1], + matrix3[0][2], + 0.0, + matrix3[1][0], + matrix3[1][1], + matrix3[1][2], + 0.0, + matrix3[2][0], + matrix3[2][1], + matrix3[2][2], + 0.0, + translation[0], + translation[1], + translation[2], + 1.0, + ]) + } } #[test] -fn test_sqrt(){ - let r=int(400); - assert_eq!(r,Planar64::raw(1717986918400)); - let s=r.sqrt(); - assert_eq!(s,Planar64::raw(85899345920)); +fn test_sqrt() { + let r = int(400); + assert_eq!(r, Planar64::raw(1717986918400)); + let s = r.sqrt(); + assert_eq!(s, Planar64::raw(85899345920)); } diff --git a/lib/common/src/lib.rs b/lib/common/src/lib.rs index 69a3501..c1de8c0 100644 --- a/lib/common/src/lib.rs +++ b/lib/common/src/lib.rs @@ -1,16 +1,16 @@ -pub mod bvh; -pub mod map; -pub mod run; pub mod aabb; -pub mod model; -pub mod mouse; -pub mod timer; -pub mod integer; -pub mod physics; -pub mod session; -pub mod updatable; -pub mod instruction; +pub mod bvh; +pub mod controls_bitflag; pub mod gameplay_attributes; pub mod gameplay_modes; pub mod gameplay_style; -pub mod controls_bitflag; +pub mod instruction; +pub mod integer; +pub mod map; +pub mod model; +pub mod mouse; +pub mod physics; +pub mod run; +pub mod session; +pub mod timer; +pub mod updatable; diff --git a/lib/common/src/map.rs b/lib/common/src/map.rs index 3c4020c..2c29ea1 100644 --- a/lib/common/src/map.rs +++ b/lib/common/src/map.rs @@ -1,14 +1,14 @@ -use crate::model; -use crate::gameplay_modes; use crate::gameplay_attributes; +use crate::gameplay_modes; +use crate::model; //this is a temporary struct to try to get the code running again //TODO: use snf::map::Region to update the data in physics and graphics instead of this -pub struct CompleteMap{ - pub modes:gameplay_modes::Modes, - pub attributes:Vec, - pub meshes:Vec, - pub models:Vec, - //RenderPattern - pub textures:Vec>, - pub render_configs:Vec, +pub struct CompleteMap { + pub modes: gameplay_modes::Modes, + pub attributes: Vec, + pub meshes: Vec, + pub models: Vec, + //RenderPattern + pub textures: Vec>, + pub render_configs: Vec, } diff --git a/lib/common/src/model.rs b/lib/common/src/model.rs index 3a9980b..4f734ad 100644 --- a/lib/common/src/model.rs +++ b/lib/common/src/model.rs @@ -1,51 +1,56 @@ -use crate::integer::{Planar64Vec3,Planar64Affine3}; use crate::gameplay_attributes; +use crate::integer::{Planar64Affine3, Planar64Vec3}; -pub type TextureCoordinate=glam::Vec2; -pub type Color4=glam::Vec4; -#[derive(Clone,Copy,Hash,id::Id,PartialEq,Eq)] +pub type TextureCoordinate = glam::Vec2; +pub type Color4 = glam::Vec4; +#[derive(Clone, Copy, Hash, id::Id, PartialEq, Eq)] pub struct PositionId(u32); -#[derive(Clone,Copy,Hash,id::Id,PartialEq,Eq)] +#[derive(Clone, Copy, Hash, id::Id, PartialEq, Eq)] pub struct TextureCoordinateId(u32); -#[derive(Clone,Copy,Hash,id::Id,PartialEq,Eq)] +#[derive(Clone, Copy, Hash, id::Id, PartialEq, Eq)] pub struct NormalId(u32); -#[derive(Clone,Copy,Hash,id::Id,PartialEq,Eq)] +#[derive(Clone, Copy, Hash, id::Id, PartialEq, Eq)] pub struct ColorId(u32); -#[derive(Clone,Hash,PartialEq,Eq)] -pub struct IndexedVertex{ - pub pos:PositionId, - pub tex:TextureCoordinateId, - pub normal:NormalId, - pub color:ColorId, +#[derive(Clone, Hash, PartialEq, Eq)] +pub struct IndexedVertex { + pub pos: PositionId, + pub tex: TextureCoordinateId, + pub normal: NormalId, + pub color: ColorId, } -#[derive(Clone,Copy,Hash,id::Id,PartialEq,Eq)] +#[derive(Clone, Copy, Hash, id::Id, PartialEq, Eq)] pub struct VertexId(u32); -pub type IndexedVertexList=Vec; -pub trait PolygonIter{ - fn polys(&self)->impl Iterator; +pub type IndexedVertexList = Vec; +pub trait PolygonIter { + fn polys(&self) -> impl Iterator; } -pub trait MapVertexId{ - fn map_vertex_idVertexId>(self,f:F)->Self; +pub trait MapVertexId { + fn map_vertex_id VertexId>(self, f: F) -> Self; } #[derive(Clone)] pub struct PolygonList(Vec); -impl PolygonList{ - pub const fn new(list:Vec)->Self{ - Self(list) - } - pub fn extend>(&mut self,iter:T){ - self.0.extend(iter); - } +impl PolygonList { + pub const fn new(list: Vec) -> Self { + Self(list) + } + pub fn extend>(&mut self, iter: T) { + self.0.extend(iter); + } } -impl PolygonIter for PolygonList{ - fn polys(&self)->impl Iterator{ - self.0.iter().map(|poly|poly.as_slice()) - } +impl PolygonIter for PolygonList { + fn polys(&self) -> impl Iterator { + self.0.iter().map(|poly| poly.as_slice()) + } } -impl MapVertexId for PolygonList{ - fn map_vertex_idVertexId>(self,f:F)->Self{ - Self(self.0.into_iter().map(|ivl|ivl.into_iter().map(&f).collect()).collect()) - } +impl MapVertexId for PolygonList { + fn map_vertex_id VertexId>(self, f: F) -> Self { + Self( + self.0 + .into_iter() + .map(|ivl| ivl.into_iter().map(&f).collect()) + .collect(), + ) + } } // pub struct TriangleStrip(IndexedVertexList); // impl PolygonIter for TriangleStrip{ @@ -53,81 +58,81 @@ impl MapVertexId for PolygonList{ // self.0.vertices.windows(3).enumerate().map(|(i,s)|if i&0!=0{return s.iter().rev()}else{return s.iter()}) // } // } -#[derive(Clone,Copy,Hash,id::Id,PartialEq,Eq)] +#[derive(Clone, Copy, Hash, id::Id, PartialEq, Eq)] pub struct PolygonGroupId(u32); #[derive(Clone)] -pub enum PolygonGroup{ - PolygonList(PolygonList), - //TriangleStrip(TriangleStrip), +pub enum PolygonGroup { + PolygonList(PolygonList), + //TriangleStrip(TriangleStrip), } -impl PolygonIter for PolygonGroup{ - fn polys(&self)->impl Iterator{ - match self{ - PolygonGroup::PolygonList(list)=>list.polys(), - //PolygonGroup::TriangleStrip(strip)=>strip.polys(), - } - } +impl PolygonIter for PolygonGroup { + fn polys(&self) -> impl Iterator { + match self { + PolygonGroup::PolygonList(list) => list.polys(), + //PolygonGroup::TriangleStrip(strip)=>strip.polys(), + } + } } -impl MapVertexId for PolygonGroup{ - fn map_vertex_idVertexId>(self,f:F)->Self{ - match self{ - PolygonGroup::PolygonList(polys)=>Self::PolygonList(polys.map_vertex_id(f)), - } - } +impl MapVertexId for PolygonGroup { + fn map_vertex_id VertexId>(self, f: F) -> Self { + match self { + PolygonGroup::PolygonList(polys) => Self::PolygonList(polys.map_vertex_id(f)), + } + } } /// Ah yes, a group of things to render at the same time -#[derive(Clone,Copy,Debug,Hash,id::Id,Eq,PartialEq)] +#[derive(Clone, Copy, Debug, Hash, id::Id, Eq, PartialEq)] pub struct TextureId(u32); -#[derive(Clone,Copy,Hash,id::Id,Eq,PartialEq)] +#[derive(Clone, Copy, Hash, id::Id, Eq, PartialEq)] pub struct RenderConfigId(u32); -#[derive(Clone,Copy,Default)] -pub struct RenderConfig{ - pub texture:Option, +#[derive(Clone, Copy, Default)] +pub struct RenderConfig { + pub texture: Option, } -impl RenderConfig{ - pub const fn texture(texture:TextureId)->Self{ - Self{ - texture:Some(texture), - } - } +impl RenderConfig { + pub const fn texture(texture: TextureId) -> Self { + Self { + texture: Some(texture), + } + } } #[derive(Clone)] -pub struct IndexedGraphicsGroup{ - //Render pattern material/texture/shader/flat color - pub render:RenderConfigId, - pub groups:Vec, +pub struct IndexedGraphicsGroup { + //Render pattern material/texture/shader/flat color + pub render: RenderConfigId, + pub groups: Vec, } -#[derive(Clone,Default)] -pub struct IndexedPhysicsGroup{ - //the polygons in this group are guaranteed to make a closed convex shape - pub groups:Vec, +#[derive(Clone, Default)] +pub struct IndexedPhysicsGroup { + //the polygons in this group are guaranteed to make a closed convex shape + pub groups: Vec, } //This is a superset of PhysicsModel and GraphicsModel -#[derive(Clone,Copy,Debug,Hash,id::Id,Eq,PartialEq)] +#[derive(Clone, Copy, Debug, Hash, id::Id, Eq, PartialEq)] pub struct MeshId(u32); #[derive(Clone)] -pub struct Mesh{ - pub unique_pos:Vec,//Unit32Vec3 - pub unique_normal:Vec,//Unit32Vec3 - pub unique_tex:Vec, - pub unique_color:Vec, - pub unique_vertices:Vec, - //polygon groups are constant texture AND convexity slices - //note that this may need to be changed to be a list of individual faces - //for submeshes to work since face ids need to be consistent across submeshes - //so face == polygon_groups[face_id] - pub polygon_groups:Vec, - //graphics indexed (by texture) - pub graphics_groups:Vec, - //physics indexed (by convexity) - pub physics_groups:Vec, +pub struct Mesh { + pub unique_pos: Vec, //Unit32Vec3 + pub unique_normal: Vec, //Unit32Vec3 + pub unique_tex: Vec, + pub unique_color: Vec, + pub unique_vertices: Vec, + //polygon groups are constant texture AND convexity slices + //note that this may need to be changed to be a list of individual faces + //for submeshes to work since face ids need to be consistent across submeshes + //so face == polygon_groups[face_id] + pub polygon_groups: Vec, + //graphics indexed (by texture) + pub graphics_groups: Vec, + //physics indexed (by convexity) + pub physics_groups: Vec, } -#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)] +#[derive(Debug, Clone, Copy, Hash, id::Id, Eq, PartialEq)] pub struct ModelId(u32); -pub struct Model{ - pub mesh:MeshId, - pub attributes:gameplay_attributes::CollisionAttributesId, - pub color:Color4,//transparency is in here - pub transform:Planar64Affine3, +pub struct Model { + pub mesh: MeshId, + pub attributes: gameplay_attributes::CollisionAttributesId, + pub color: Color4, //transparency is in here + pub transform: Planar64Affine3, } diff --git a/lib/common/src/mouse.rs b/lib/common/src/mouse.rs index 3ab0807..487314c 100644 --- a/lib/common/src/mouse.rs +++ b/lib/common/src/mouse.rs @@ -1,28 +1,29 @@ use crate::integer::Time; -#[derive(Clone,Debug)] -pub struct MouseState{ - pub pos:glam::IVec2, - pub time:Time, +#[derive(Clone, Debug)] +pub struct MouseState { + pub pos: glam::IVec2, + pub time: Time, } -impl Default for MouseState{ - fn default()->Self{ - Self{ - time:Time::ZERO, - pos:glam::IVec2::ZERO, - } - } +impl Default for MouseState { + fn default() -> Self { + Self { + time: Time::ZERO, + pos: glam::IVec2::ZERO, + } + } } impl MouseState - where Time:Copy, +where + Time: Copy, { - pub fn lerp(&self,target:&MouseState,time:Time)->glam::IVec2{ - let m0=self.pos.as_i64vec2(); - let m1=target.pos.as_i64vec2(); - //these are deltas - let t1t=(target.time-time).nanos(); - let tt0=(time-self.time).nanos(); - let dt=(target.time-self.time).nanos(); - ((m0*t1t+m1*tt0)/dt).as_ivec2() - } + pub fn lerp(&self, target: &MouseState, time: Time) -> glam::IVec2 { + let m0 = self.pos.as_i64vec2(); + let m1 = target.pos.as_i64vec2(); + //these are deltas + let t1t = (target.time - time).nanos(); + let tt0 = (time - self.time).nanos(); + let dt = (target.time - self.time).nanos(); + ((m0 * t1t + m1 * tt0) / dt).as_ivec2() + } } diff --git a/lib/common/src/physics.rs b/lib/common/src/physics.rs index dc6be13..abb1c98 100644 --- a/lib/common/src/physics.rs +++ b/lib/common/src/physics.rs @@ -1,31 +1,37 @@ -#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)] -pub enum TimeInner{} -pub type Time=crate::integer::Time; +#[derive(Clone, Copy, Hash, Eq, PartialEq, PartialOrd, Debug)] +pub enum TimeInner {} +pub type Time = crate::integer::Time; -#[derive(Clone,Debug)] -pub enum Instruction{ - ReplaceMouse(crate::mouse::MouseState,crate::mouse::MouseState), - SetNextMouse(crate::mouse::MouseState), - SetMoveRight(bool), - SetMoveUp(bool), - SetMoveBack(bool), - SetMoveLeft(bool), - SetMoveDown(bool), - SetMoveForward(bool), - SetJump(bool), - SetZoom(bool), - /// Reset: fully replace the physics state. - /// This forgets all inputs and settings which need to be reapplied. - Reset, - /// Restart: Teleport to the start zone. - Restart, - /// Spawn: Teleport to a specific mode's spawn - /// Sets current mode & spawn - Spawn(crate::gameplay_modes::ModeId,crate::gameplay_modes::StageId), - Idle, - //Idle: there were no input events, but the simulation is safe to advance to this timestep - //for interpolation / networking / playback reasons, most playback heads will always want - //to be 1 instruction ahead to generate the next state for interpolation. - PracticeFly, - SetSensitivity(crate::integer::Ratio64Vec2), +#[derive(Clone, Debug)] +pub enum Instruction { + ReplaceMouse( + crate::mouse::MouseState, + crate::mouse::MouseState, + ), + SetNextMouse(crate::mouse::MouseState), + SetMoveRight(bool), + SetMoveUp(bool), + SetMoveBack(bool), + SetMoveLeft(bool), + SetMoveDown(bool), + SetMoveForward(bool), + SetJump(bool), + SetZoom(bool), + /// Reset: fully replace the physics state. + /// This forgets all inputs and settings which need to be reapplied. + Reset, + /// Restart: Teleport to the start zone. + Restart, + /// Spawn: Teleport to a specific mode's spawn + /// Sets current mode & spawn + Spawn( + crate::gameplay_modes::ModeId, + crate::gameplay_modes::StageId, + ), + Idle, + //Idle: there were no input events, but the simulation is safe to advance to this timestep + //for interpolation / networking / playback reasons, most playback heads will always want + //to be 1 instruction ahead to generate the next state for interpolation. + PracticeFly, + SetSensitivity(crate::integer::Ratio64Vec2), } diff --git a/lib/common/src/run.rs b/lib/common/src/run.rs index 8ab499c..dc34931 100644 --- a/lib/common/src/run.rs +++ b/lib/common/src/run.rs @@ -1,113 +1,117 @@ -use crate::timer::{TimerFixed,Realtime,Paused,Unpaused}; +use crate::timer::{Paused, Realtime, TimerFixed, Unpaused}; -use crate::physics::{TimeInner as PhysicsTimeInner,Time as PhysicsTime}; +use crate::physics::{Time as PhysicsTime, TimeInner as PhysicsTimeInner}; -#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)] -pub enum TimeInner{} -pub type Time=crate::integer::Time; +#[derive(Clone, Copy, Hash, Eq, PartialEq, PartialOrd, Debug)] +pub enum TimeInner {} +pub type Time = crate::integer::Time; -#[derive(Clone,Copy,Debug)] -pub enum FlagReason{ - Anticheat, - StyleChange, - Clock, - Pause, - Flying, - Gravity, - Timescale, - TimeTravel, - Teleport, +#[derive(Clone, Copy, Debug)] +pub enum FlagReason { + Anticheat, + StyleChange, + Clock, + Pause, + Flying, + Gravity, + Timescale, + TimeTravel, + Teleport, } -impl ToString for FlagReason{ - fn to_string(&self)->String{ - self.as_ref().to_owned() - } +impl ToString for FlagReason { + fn to_string(&self) -> String { + self.as_ref().to_owned() + } } -impl AsRef for FlagReason{ - fn as_ref(&self)->&str{ - match self{ - FlagReason::Anticheat=>"Passed through anticheat zone.", - FlagReason::StyleChange=>"Changed style.", - FlagReason::Clock=>"Incorrect clock. (This can be caused by internet hiccups)", - FlagReason::Pause=>"Pausing is not allowed in this style.", - FlagReason::Flying=>"Flying is not allowed in this style.", - FlagReason::Gravity=>"Gravity modification is not allowed in this style.", - FlagReason::Timescale=>"Timescale is not allowed in this style.", - FlagReason::TimeTravel=>"Time travel is not allowed in this style.", - FlagReason::Teleport=>"Illegal teleport.", - } - } +impl AsRef for FlagReason { + fn as_ref(&self) -> &str { + match self { + FlagReason::Anticheat => "Passed through anticheat zone.", + FlagReason::StyleChange => "Changed style.", + FlagReason::Clock => "Incorrect clock. (This can be caused by internet hiccups)", + FlagReason::Pause => "Pausing is not allowed in this style.", + FlagReason::Flying => "Flying is not allowed in this style.", + FlagReason::Gravity => "Gravity modification is not allowed in this style.", + FlagReason::Timescale => "Timescale is not allowed in this style.", + FlagReason::TimeTravel => "Time travel is not allowed in this style.", + FlagReason::Teleport => "Illegal teleport.", + } + } } #[derive(Debug)] -pub enum Error{ - NotStarted, - AlreadyStarted, - AlreadyFinished, +pub enum Error { + NotStarted, + AlreadyStarted, + AlreadyFinished, } -impl std::fmt::Display for Error{ - fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ - write!(f,"{self:?}") - } +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{} +impl std::error::Error for Error {} -#[derive(Clone,Copy,Debug)] -enum RunState{ - Created, - Started{timer:TimerFixed,Unpaused>}, - Finished{timer:TimerFixed,Paused>}, +#[derive(Clone, Copy, Debug)] +enum RunState { + Created, + Started { + timer: TimerFixed, Unpaused>, + }, + Finished { + timer: TimerFixed, Paused>, + }, } -#[derive(Clone,Copy,Debug)] -pub struct Run{ - state:RunState, - flagged:Option, +#[derive(Clone, Copy, Debug)] +pub struct Run { + state: RunState, + flagged: Option, } -impl Run{ - pub fn new()->Self{ - Self{ - state:RunState::Created, - flagged:None, - } - } - pub fn time(&self,time:PhysicsTime)->Time{ - match &self.state{ - RunState::Created=>Time::ZERO, - RunState::Started{timer}=>timer.time(time), - RunState::Finished{timer}=>timer.time(time), - } - } - pub fn start(&mut self,time:PhysicsTime)->Result<(),Error>{ - match &self.state{ - RunState::Created=>{ - self.state=RunState::Started{ - timer:TimerFixed::new(time,Time::ZERO), - }; - Ok(()) - }, - RunState::Started{..}=>Err(Error::AlreadyStarted), - RunState::Finished{..}=>Err(Error::AlreadyFinished), - } - } - pub fn finish(&mut self,time:PhysicsTime)->Result<(),Error>{ - //this uses Copy - match &self.state{ - RunState::Created=>Err(Error::NotStarted), - RunState::Started{timer}=>{ - self.state=RunState::Finished{ - timer:timer.into_paused(time), - }; - Ok(()) - }, - RunState::Finished{..}=>Err(Error::AlreadyFinished), - } - } - pub fn flag(&mut self,flag_reason:FlagReason){ - //don't replace the first reason the run was flagged - if self.flagged.is_none(){ - self.flagged=Some(flag_reason); - } - } +impl Run { + pub fn new() -> Self { + Self { + state: RunState::Created, + flagged: None, + } + } + pub fn time(&self, time: PhysicsTime) -> Time { + match &self.state { + RunState::Created => Time::ZERO, + RunState::Started { timer } => timer.time(time), + RunState::Finished { timer } => timer.time(time), + } + } + pub fn start(&mut self, time: PhysicsTime) -> Result<(), Error> { + match &self.state { + RunState::Created => { + self.state = RunState::Started { + timer: TimerFixed::new(time, Time::ZERO), + }; + Ok(()) + } + RunState::Started { .. } => Err(Error::AlreadyStarted), + RunState::Finished { .. } => Err(Error::AlreadyFinished), + } + } + pub fn finish(&mut self, time: PhysicsTime) -> Result<(), Error> { + //this uses Copy + match &self.state { + RunState::Created => Err(Error::NotStarted), + RunState::Started { timer } => { + self.state = RunState::Finished { + timer: timer.into_paused(time), + }; + Ok(()) + } + RunState::Finished { .. } => Err(Error::AlreadyFinished), + } + } + pub fn flag(&mut self, flag_reason: FlagReason) { + //don't replace the first reason the run was flagged + if self.flagged.is_none() { + self.flagged = Some(flag_reason); + } + } } diff --git a/lib/common/src/session.rs b/lib/common/src/session.rs index 7f2c01d..3e094e2 100644 --- a/lib/common/src/session.rs +++ b/lib/common/src/session.rs @@ -1,3 +1,3 @@ -#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)] -pub enum TimeInner{} -pub type Time=crate::integer::Time; +#[derive(Clone, Copy, Hash, Eq, PartialEq, PartialOrd, Debug)] +pub enum TimeInner {} +pub type Time = crate::integer::Time; diff --git a/lib/common/src/timer.rs b/lib/common/src/timer.rs index b75c68b..56666a1 100644 --- a/lib/common/src/timer.rs +++ b/lib/common/src/timer.rs @@ -1,363 +1,372 @@ -use crate::integer::{Time,Ratio64}; +use crate::integer::{Ratio64, Time}; -#[derive(Clone,Copy,Debug)] +#[derive(Clone, Copy, Debug)] pub struct Paused; -#[derive(Clone,Copy,Debug)] +#[derive(Clone, Copy, Debug)] pub struct Unpaused; -pub trait PauseState:Copy+std::fmt::Debug{ - const IS_PAUSED:bool; - fn new()->Self; +pub trait PauseState: Copy + std::fmt::Debug { + const IS_PAUSED: bool; + fn new() -> Self; } -impl PauseState for Paused{ - const IS_PAUSED:bool=true; - fn new()->Self{ - Self - } +impl PauseState for Paused { + const IS_PAUSED: bool = true; + fn new() -> Self { + Self + } } -impl PauseState for Unpaused{ - const IS_PAUSED:bool=false; - fn new()->Self{ - Self - } +impl PauseState for Unpaused { + const IS_PAUSED: bool = false; + fn new() -> Self { + Self + } } -#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)] -enum Inner{} -type InnerTime=Time; +#[derive(Clone, Copy, Hash, Eq, PartialEq, PartialOrd, Debug)] +enum Inner {} +type InnerTime = Time; -#[derive(Clone,Copy,Debug)] -pub struct Realtime{ - offset:InnerTime, - _in:core::marker::PhantomData, - _out:core::marker::PhantomData, +#[derive(Clone, Copy, Debug)] +pub struct Realtime { + offset: InnerTime, + _in: core::marker::PhantomData, + _out: core::marker::PhantomData, } -impl Realtime{ - pub const fn new(offset:InnerTime)->Self{ - Self{ - offset, - _in:core::marker::PhantomData, - _out:core::marker::PhantomData, - } - } +impl Realtime { + pub const fn new(offset: InnerTime) -> Self { + Self { + offset, + _in: core::marker::PhantomData, + _out: core::marker::PhantomData, + } + } } -#[derive(Clone,Copy,Debug)] -pub struct Scaled{ - scale:Ratio64, - offset:InnerTime, - _in:core::marker::PhantomData, - _out:core::marker::PhantomData, +#[derive(Clone, Copy, Debug)] +pub struct Scaled { + scale: Ratio64, + offset: InnerTime, + _in: core::marker::PhantomData, + _out: core::marker::PhantomData, } -impl Scaled - where Time:Copy, +impl Scaled +where + Time: Copy, { - pub const fn new(scale:Ratio64,offset:InnerTime)->Self{ - Self{ - scale, - offset, - _in:core::marker::PhantomData, - _out:core::marker::PhantomData, - } - } - const fn with_scale(scale:Ratio64)->Self{ - Self::new(scale,InnerTime::ZERO) - } - const fn scale(&self,time:Time)->InnerTime{ - InnerTime::raw(self.scale.mul_int(time.get())) - } - const fn get_scale(&self)->Ratio64{ - self.scale - } - fn set_scale(&mut self,time:Time,new_scale:Ratio64){ - let new_time=self.get_time(time); - self.scale=new_scale; - self.set_time(time,new_time); - } + pub const fn new(scale: Ratio64, offset: InnerTime) -> Self { + Self { + scale, + offset, + _in: core::marker::PhantomData, + _out: core::marker::PhantomData, + } + } + const fn with_scale(scale: Ratio64) -> Self { + Self::new(scale, InnerTime::ZERO) + } + const fn scale(&self, time: Time) -> InnerTime { + InnerTime::raw(self.scale.mul_int(time.get())) + } + const fn get_scale(&self) -> Ratio64 { + self.scale + } + fn set_scale(&mut self, time: Time, new_scale: Ratio64) { + let new_time = self.get_time(time); + self.scale = new_scale; + self.set_time(time, new_time); + } } -pub trait TimerState{ - type In; - type Out; - fn identity()->Self; - fn get_time(&self,time:Time)->Time; - fn set_time(&mut self,time:Time,new_time:Time); - fn get_offset(&self)->InnerTime; - fn set_offset(&mut self,offset:InnerTime); +pub trait TimerState { + type In; + type Out; + fn identity() -> Self; + fn get_time(&self, time: Time) -> Time; + fn set_time(&mut self, time: Time, new_time: Time); + fn get_offset(&self) -> InnerTime; + fn set_offset(&mut self, offset: InnerTime); } -impl TimerState for Realtime{ - type In=In; - type Out=Out; - fn identity()->Self{ - Self::new(InnerTime::ZERO) - } - fn get_time(&self,time:Time)->Time{ - time.coerce()+self.offset.coerce() - } - fn set_time(&mut self,time:Time,new_time:Time){ - self.offset=new_time.coerce()-time.coerce(); - } - fn get_offset(&self)->InnerTime{ - self.offset - } - fn set_offset(&mut self,offset:InnerTime){ - self.offset=offset; - } +impl TimerState for Realtime { + type In = In; + type Out = Out; + fn identity() -> Self { + Self::new(InnerTime::ZERO) + } + fn get_time(&self, time: Time) -> Time { + time.coerce() + self.offset.coerce() + } + fn set_time(&mut self, time: Time, new_time: Time) { + self.offset = new_time.coerce() - time.coerce(); + } + fn get_offset(&self) -> InnerTime { + self.offset + } + fn set_offset(&mut self, offset: InnerTime) { + self.offset = offset; + } } -impl TimerState for Scaled - where Time:Copy, +impl TimerState for Scaled +where + Time: Copy, { - type In=In; - type Out=Out; - fn identity()->Self{ - Self::new(Ratio64::ONE,InnerTime::ZERO) - } - fn get_time(&self,time:Time)->Time{ - (self.scale(time)+self.offset).coerce() - } - fn set_time(&mut self,time:Time,new_time:Time){ - self.offset=new_time.coerce()-self.scale(time); - } - fn get_offset(&self)->InnerTime{ - self.offset - } - fn set_offset(&mut self,offset:InnerTime){ - self.offset=offset; - } + type In = In; + type Out = Out; + fn identity() -> Self { + Self::new(Ratio64::ONE, InnerTime::ZERO) + } + fn get_time(&self, time: Time) -> Time { + (self.scale(time) + self.offset).coerce() + } + fn set_time(&mut self, time: Time, new_time: Time) { + self.offset = new_time.coerce() - self.scale(time); + } + fn get_offset(&self) -> InnerTime { + self.offset + } + fn set_offset(&mut self, offset: InnerTime) { + self.offset = offset; + } } -#[derive(Clone,Copy,Debug)] -pub struct TimerFixed{ - state:T, - _paused:P, +#[derive(Clone, Copy, Debug)] +pub struct TimerFixed { + state: T, + _paused: P, } //scaled timer methods are generic across PauseState -impl TimerFixed,P> - where Time:Copy, +impl TimerFixed, P> +where + Time: Copy, { - pub fn scaled(time:Time,new_time:Time,scale:Ratio64)->Self{ - let mut timer=Self{ - state:Scaled::with_scale(scale), - _paused:P::new(), - }; - timer.set_time(time,new_time); - timer - } - pub const fn get_scale(&self)->Ratio64{ - self.state.get_scale() - } - pub fn set_scale(&mut self,time:Time,new_scale:Ratio64){ - self.state.set_scale(time,new_scale) - } + pub fn scaled(time: Time, new_time: Time, scale: Ratio64) -> Self { + let mut timer = Self { + state: Scaled::with_scale(scale), + _paused: P::new(), + }; + timer.set_time(time, new_time); + timer + } + pub const fn get_scale(&self) -> Ratio64 { + self.state.get_scale() + } + pub fn set_scale(&mut self, time: Time, new_scale: Ratio64) { + self.state.set_scale(time, new_scale) + } } //pause and unpause is generic across TimerState -impl TimerFixed - where Time:Copy, +impl TimerFixed +where + Time: Copy, { - pub fn into_unpaused(self,time:Time)->TimerFixed{ - let new_time=self.time(time); - let mut timer=TimerFixed{ - state:self.state, - _paused:Unpaused, - }; - timer.set_time(time,new_time); - timer - } + pub fn into_unpaused(self, time: Time) -> TimerFixed { + let new_time = self.time(time); + let mut timer = TimerFixed { + state: self.state, + _paused: Unpaused, + }; + timer.set_time(time, new_time); + timer + } } -impl TimerFixed - where Time:Copy, +impl TimerFixed +where + Time: Copy, { - pub fn into_paused(self,time:Time)->TimerFixed{ - let new_time=self.time(time); - let mut timer=TimerFixed{ - state:self.state, - _paused:Paused, - }; - timer.set_time(time,new_time); - timer - } + pub fn into_paused(self, time: Time) -> TimerFixed { + let new_time = self.time(time); + let mut timer = TimerFixed { + state: self.state, + _paused: Paused, + }; + timer.set_time(time, new_time); + timer + } } //the new constructor and time queries are generic across both -impl TimerFixed{ - pub fn new(time:Time,new_time:Time)->Self{ - let mut timer=Self{ - state:T::identity(), - _paused:P::new(), - }; - timer.set_time(time,new_time); - timer - } - pub fn from_state(state:T)->Self{ - Self{ - state, - _paused:P::new(), - } - } - pub fn into_state(self)->T{ - self.state - } - pub fn time(&self,time:Time)->Time{ - match P::IS_PAUSED{ - true=>self.state.get_offset().coerce(), - false=>self.state.get_time(time), - } - } - pub fn set_time(&mut self,time:Time,new_time:Time){ - match P::IS_PAUSED{ - true=>self.state.set_offset(new_time.coerce()), - false=>self.state.set_time(time,new_time), - } - } +impl TimerFixed { + pub fn new(time: Time, new_time: Time) -> Self { + let mut timer = Self { + state: T::identity(), + _paused: P::new(), + }; + timer.set_time(time, new_time); + timer + } + pub fn from_state(state: T) -> Self { + Self { + state, + _paused: P::new(), + } + } + pub fn into_state(self) -> T { + self.state + } + pub fn time(&self, time: Time) -> Time { + match P::IS_PAUSED { + true => self.state.get_offset().coerce(), + false => self.state.get_time(time), + } + } + pub fn set_time(&mut self, time: Time, new_time: Time) { + match P::IS_PAUSED { + true => self.state.set_offset(new_time.coerce()), + false => self.state.set_time(time, new_time), + } + } } #[derive(Debug)] -pub enum Error{ - AlreadyPaused, - AlreadyUnpaused, +pub enum Error { + AlreadyPaused, + AlreadyUnpaused, } -impl std::fmt::Display for Error{ - fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ - write!(f,"{self:?}") - } +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{} +impl std::error::Error for Error {} //wrapper type which holds type state internally -#[derive(Clone,Debug)] -pub enum Timer{ - Paused(TimerFixed), - Unpaused(TimerFixed), +#[derive(Clone, Debug)] +pub enum Timer { + Paused(TimerFixed), + Unpaused(TimerFixed), } -impl Timer - where - T:Copy, - Time:Copy, +impl Timer +where + T: Copy, + Time: Copy, { - pub fn from_state(state:T,paused:bool)->Self{ - match paused{ - true=>Self::Paused(TimerFixed::from_state(state)), - false=>Self::Unpaused(TimerFixed::from_state(state)), - } - } - pub fn into_state(self)->(T,bool){ - match self{ - Self::Paused(timer)=>(timer.into_state(),true), - Self::Unpaused(timer)=>(timer.into_state(),false), - } - } - pub fn paused(time:Time,new_time:Time)->Self{ - Self::Paused(TimerFixed::new(time,new_time)) - } - pub fn unpaused(time:Time,new_time:Time)->Self{ - Self::Unpaused(TimerFixed::new(time,new_time)) - } - pub fn time(&self,time:Time)->Time{ - match self{ - Self::Paused(timer)=>timer.time(time), - Self::Unpaused(timer)=>timer.time(time), - } - } - pub fn set_time(&mut self,time:Time,new_time:Time){ - match self{ - Self::Paused(timer)=>timer.set_time(time,new_time), - Self::Unpaused(timer)=>timer.set_time(time,new_time), - } - } - pub fn pause(&mut self,time:Time)->Result<(),Error>{ - *self=match *self{ - Self::Paused(_)=>return Err(Error::AlreadyPaused), - Self::Unpaused(timer)=>Self::Paused(timer.into_paused(time)), - }; - Ok(()) - } - pub fn unpause(&mut self,time:Time)->Result<(),Error>{ - *self=match *self{ - Self::Paused(timer)=>Self::Unpaused(timer.into_unpaused(time)), - Self::Unpaused(_)=>return Err(Error::AlreadyUnpaused), - }; - Ok(()) - } - pub fn is_paused(&self)->bool{ - match self{ - Self::Paused(_)=>true, - Self::Unpaused(_)=>false, - } - } - pub fn set_paused(&mut self,time:Time,paused:bool)->Result<(),Error>{ - match paused{ - true=>self.pause(time), - false=>self.unpause(time), - } - } + pub fn from_state(state: T, paused: bool) -> Self { + match paused { + true => Self::Paused(TimerFixed::from_state(state)), + false => Self::Unpaused(TimerFixed::from_state(state)), + } + } + pub fn into_state(self) -> (T, bool) { + match self { + Self::Paused(timer) => (timer.into_state(), true), + Self::Unpaused(timer) => (timer.into_state(), false), + } + } + pub fn paused(time: Time, new_time: Time) -> Self { + Self::Paused(TimerFixed::new(time, new_time)) + } + pub fn unpaused(time: Time, new_time: Time) -> Self { + Self::Unpaused(TimerFixed::new(time, new_time)) + } + pub fn time(&self, time: Time) -> Time { + match self { + Self::Paused(timer) => timer.time(time), + Self::Unpaused(timer) => timer.time(time), + } + } + pub fn set_time(&mut self, time: Time, new_time: Time) { + match self { + Self::Paused(timer) => timer.set_time(time, new_time), + Self::Unpaused(timer) => timer.set_time(time, new_time), + } + } + pub fn pause(&mut self, time: Time) -> Result<(), Error> { + *self = match *self { + Self::Paused(_) => return Err(Error::AlreadyPaused), + Self::Unpaused(timer) => Self::Paused(timer.into_paused(time)), + }; + Ok(()) + } + pub fn unpause(&mut self, time: Time) -> Result<(), Error> { + *self = match *self { + Self::Paused(timer) => Self::Unpaused(timer.into_unpaused(time)), + Self::Unpaused(_) => return Err(Error::AlreadyUnpaused), + }; + Ok(()) + } + pub fn is_paused(&self) -> bool { + match self { + Self::Paused(_) => true, + Self::Unpaused(_) => false, + } + } + pub fn set_paused(&mut self, time: Time, paused: bool) -> Result<(), Error> { + match paused { + true => self.pause(time), + false => self.unpause(time), + } + } } //scaled timer methods are generic across PauseState -impl Timer> - where Time:Copy, +impl Timer> +where + Time: Copy, { - pub const fn get_scale(&self)->Ratio64{ - match self{ - Self::Paused(timer)=>timer.get_scale(), - Self::Unpaused(timer)=>timer.get_scale(), - } - } - pub fn set_scale(&mut self,time:Time,new_scale:Ratio64){ - match self{ - Self::Paused(timer)=>timer.set_scale(time,new_scale), - Self::Unpaused(timer)=>timer.set_scale(time,new_scale), - } - } + pub const fn get_scale(&self) -> Ratio64 { + match self { + Self::Paused(timer) => timer.get_scale(), + Self::Unpaused(timer) => timer.get_scale(), + } + } + pub fn set_scale(&mut self, time: Time, new_scale: Ratio64) { + match self { + Self::Paused(timer) => timer.set_scale(time, new_scale), + Self::Unpaused(timer) => timer.set_scale(time, new_scale), + } + } } #[cfg(test)] -mod test{ - use super::*; - macro_rules! sec { - ($s: expr) => { - Time::from_secs($s) - }; - } +mod test { + use super::*; + macro_rules! sec { + ($s: expr) => { + Time::from_secs($s) + }; + } - #[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)] - enum Parent{} - #[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)] - enum Calculated{} - #[test] - fn test_timerfixed_scaled(){ - //create a paused timer that reads 0s - let timer=TimerFixed::,Paused>::from_state(Scaled::new(0.5f32.try_into().unwrap(),sec!(0))); - //the paused timer at 1 second should read 0s - assert_eq!(timer.time(sec!(1)),sec!(0)); + #[derive(Clone, Copy, Hash, Eq, PartialEq, PartialOrd, Debug)] + enum Parent {} + #[derive(Clone, Copy, Hash, Eq, PartialEq, PartialOrd, Debug)] + enum Calculated {} + #[test] + fn test_timerfixed_scaled() { + //create a paused timer that reads 0s + let timer = TimerFixed::, Paused>::from_state(Scaled::new( + 0.5f32.try_into().unwrap(), + sec!(0), + )); + //the paused timer at 1 second should read 0s + assert_eq!(timer.time(sec!(1)), sec!(0)); - //unpause it after one second - let timer=timer.into_unpaused(sec!(1)); - //the timer at 6 seconds should read 2.5s - assert_eq!(timer.time(sec!(6)),Time::from_millis(2500)); + //unpause it after one second + let timer = timer.into_unpaused(sec!(1)); + //the timer at 6 seconds should read 2.5s + assert_eq!(timer.time(sec!(6)), Time::from_millis(2500)); - //pause the timer after 11 seconds - let timer=timer.into_paused(sec!(11)); - //the paused timer at 20 seconds should read 5s - assert_eq!(timer.time(sec!(20)),sec!(5)); - } - #[test] - fn test_timer()->Result<(),Error>{ - //create a paused timer that reads 0s - let mut timer=Timer::>::paused(sec!(0),sec!(0)); - //the paused timer at 1 second should read 0s - assert_eq!(timer.time(sec!(1)),sec!(0)); + //pause the timer after 11 seconds + let timer = timer.into_paused(sec!(11)); + //the paused timer at 20 seconds should read 5s + assert_eq!(timer.time(sec!(20)), sec!(5)); + } + #[test] + fn test_timer() -> Result<(), Error> { + //create a paused timer that reads 0s + let mut timer = Timer::>::paused(sec!(0), sec!(0)); + //the paused timer at 1 second should read 0s + assert_eq!(timer.time(sec!(1)), sec!(0)); - //unpause it after one second - timer.unpause(sec!(1))?; - //the timer at 6 seconds should read 5s - assert_eq!(timer.time(sec!(6)),sec!(5)); + //unpause it after one second + timer.unpause(sec!(1))?; + //the timer at 6 seconds should read 5s + assert_eq!(timer.time(sec!(6)), sec!(5)); - //pause the timer after 11 seconds - timer.pause(sec!(11))?; - //the paused timer at 20 seconds should read 10s - assert_eq!(timer.time(sec!(20)),sec!(10)); + //pause the timer after 11 seconds + timer.pause(sec!(11))?; + //the paused timer at 20 seconds should read 10s + assert_eq!(timer.time(sec!(20)), sec!(10)); - Ok(()) - } + Ok(()) + } } diff --git a/lib/common/src/updatable.rs b/lib/common/src/updatable.rs index 13b6dd2..e8d7e3e 100644 --- a/lib/common/src/updatable.rs +++ b/lib/common/src/updatable.rs @@ -1,56 +1,56 @@ -pub trait Updatable{ - fn update(&mut self,update:Updater); +pub trait Updatable { + fn update(&mut self, update: Updater); } -#[derive(Clone,Copy,Hash,Eq,PartialEq)] +#[derive(Clone, Copy, Hash, Eq, PartialEq)] struct InnerId(u32); #[derive(Clone)] -struct Inner{ - id:InnerId, - enabled:bool, +struct Inner { + id: InnerId, + enabled: bool, } -#[derive(Clone,Copy,Hash,Eq,PartialEq)] +#[derive(Clone, Copy, Hash, Eq, PartialEq)] struct OuterId(u32); -struct Outer{ - id:OuterId, - inners:std::collections::HashMap, +struct Outer { + id: OuterId, + inners: std::collections::HashMap, } -enum Update{ - Insert(I), - Update(U), - Remove +enum Update { + Insert(I), + Update(U), + Remove, } -struct InnerUpdate{ - //#[updatable(Update)] - enabled:Option, +struct InnerUpdate { + //#[updatable(Update)] + enabled: Option, } -struct OuterUpdate{ - //#[updatable(Insert,Update,Remove)] - inners:std::collections::HashMap>, - //#[updatable(Update)] - //inners:std::collections::HashMap, +struct OuterUpdate { + //#[updatable(Insert,Update,Remove)] + inners: std::collections::HashMap>, + //#[updatable(Update)] + //inners:std::collections::HashMap, } -impl Updatable for Inner{ - fn update(&mut self,update:InnerUpdate){ - if let Some(enabled)=update.enabled{ - self.enabled=enabled; - } - } +impl Updatable for Inner { + fn update(&mut self, update: InnerUpdate) { + if let Some(enabled) = update.enabled { + self.enabled = enabled; + } + } } -impl Updatable for Outer{ - fn update(&mut self,update:OuterUpdate){ - for (id,up) in update.inners{ - match up{ - Update::Insert(new_inner)=>self.inners.insert(id,new_inner), - Update::Update(inner_update)=>self.inners.get_mut(&id).map(|inner|{ - let old=inner.clone(); - inner.update(inner_update); - old - }), - Update::Remove=>self.inners.remove(&id), - }; - } - } +impl Updatable for Outer { + fn update(&mut self, update: OuterUpdate) { + for (id, up) in update.inners { + match up { + Update::Insert(new_inner) => self.inners.insert(id, new_inner), + Update::Update(inner_update) => self.inners.get_mut(&id).map(|inner| { + let old = inner.clone(); + inner.update(inner_update); + old + }), + Update::Remove => self.inners.remove(&id), + }; + } + } } -//*/ \ No newline at end of file +//*/ diff --git a/lib/fixed_wide/src/fixed.rs b/lib/fixed_wide/src/fixed.rs index 58aae16..028af70 100644 --- a/lib/fixed_wide/src/fixed.rs +++ b/lib/fixed_wide/src/fixed.rs @@ -1,73 +1,71 @@ -use bnum::{BInt,cast::As}; +use bnum::{cast::As, BInt}; -#[derive(Clone,Copy,Debug,Default,Hash)] +#[derive(Clone, Copy, Debug, Default, Hash)] /// A Fixed point number for which multiply operations widen the bits in the output. (when the wide-mul feature is enabled) /// N is the number of u64s to use /// F is the number of fractional bits (always N*32 lol) -pub struct Fixed{ - pub(crate)bits:BInt<{N}>, +pub struct Fixed { + pub(crate) bits: BInt<{ N }>, } -impl Fixed{ - pub const MAX:Self=Self::from_bits(BInt::::MAX); - pub const MIN:Self=Self::from_bits(BInt::::MIN); - pub const ZERO:Self=Self::from_bits(BInt::::ZERO); - pub const EPSILON:Self=Self::from_bits(BInt::::ONE); - pub const NEG_EPSILON:Self=Self::from_bits(BInt::::NEG_ONE); - pub const ONE:Self=Self::from_bits(BInt::::ONE.shl(F as u32)); - pub const TWO:Self=Self::from_bits(BInt::::TWO.shl(F as u32)); - pub const HALF:Self=Self::from_bits(BInt::::ONE.shl(F as u32-1)); - pub const NEG_ONE:Self=Self::from_bits(BInt::::NEG_ONE.shl(F as u32)); - pub const NEG_TWO:Self=Self::from_bits(BInt::::NEG_TWO.shl(F as u32)); - pub const NEG_HALF:Self=Self::from_bits(BInt::::NEG_ONE.shl(F as u32-1)); +impl Fixed { + pub const MAX: Self = Self::from_bits(BInt::::MAX); + pub const MIN: Self = Self::from_bits(BInt::::MIN); + pub const ZERO: Self = Self::from_bits(BInt::::ZERO); + pub const EPSILON: Self = Self::from_bits(BInt::::ONE); + pub const NEG_EPSILON: Self = Self::from_bits(BInt::::NEG_ONE); + pub const ONE: Self = Self::from_bits(BInt::::ONE.shl(F as u32)); + pub const TWO: Self = Self::from_bits(BInt::::TWO.shl(F as u32)); + pub const HALF: Self = Self::from_bits(BInt::::ONE.shl(F as u32 - 1)); + pub const NEG_ONE: Self = Self::from_bits(BInt::::NEG_ONE.shl(F as u32)); + pub const NEG_TWO: Self = Self::from_bits(BInt::::NEG_TWO.shl(F as u32)); + pub const NEG_HALF: Self = Self::from_bits(BInt::::NEG_ONE.shl(F as u32 - 1)); } -impl Fixed{ - #[inline] - pub const fn from_bits(bits:BInt::)->Self{ - Self{ - bits, - } - } - #[inline] - pub const fn to_bits(self)->BInt{ - self.bits - } - #[inline] - pub const fn raw_digit(value:i64)->Self{ - let mut digits=[0u64;N]; - digits[0]=value.abs() as u64; - //sign bit - digits[N-1]|=(value&i64::MIN) as u64; - Self::from_bits(BInt::from_bits(bnum::BUint::from_digits(digits))) - } - #[inline] - pub const fn is_zero(self)->bool{ - self.bits.is_zero() - } - #[inline] - pub const fn is_negative(self)->bool{ - self.bits.is_negative() - } - #[inline] - pub const fn is_positive(self)->bool{ - self.bits.is_positive() - } - #[inline] - pub const fn abs(self)->Self{ - Self::from_bits(self.bits.abs()) - } +impl Fixed { + #[inline] + pub const fn from_bits(bits: BInt) -> Self { + Self { bits } + } + #[inline] + pub const fn to_bits(self) -> BInt { + self.bits + } + #[inline] + pub const fn raw_digit(value: i64) -> Self { + let mut digits = [0u64; N]; + digits[0] = value.abs() as u64; + //sign bit + digits[N - 1] |= (value & i64::MIN) as u64; + Self::from_bits(BInt::from_bits(bnum::BUint::from_digits(digits))) + } + #[inline] + pub const fn is_zero(self) -> bool { + self.bits.is_zero() + } + #[inline] + pub const fn is_negative(self) -> bool { + self.bits.is_negative() + } + #[inline] + pub const fn is_positive(self) -> bool { + self.bits.is_positive() + } + #[inline] + pub const fn abs(self) -> Self { + Self::from_bits(self.bits.abs()) + } } -impl Fixed<1,F>{ - /// My old code called this function everywhere so let's provide it - #[inline] - pub const fn raw(value:i64)->Self{ - Self::from_bits(BInt::from_bits(bnum::BUint::from_digit(value as u64))) - } - #[inline] - pub const fn to_raw(self)->i64{ - let &[digit]=self.to_bits().to_bits().digits(); - digit as i64 - } +impl Fixed<1, F> { + /// My old code called this function everywhere so let's provide it + #[inline] + pub const fn raw(value: i64) -> Self { + Self::from_bits(BInt::from_bits(bnum::BUint::from_digit(value as u64))) + } + #[inline] + pub const fn to_raw(self) -> i64 { + let &[digit] = self.to_bits().to_bits().digits(); + digit as i64 + } } macro_rules! impl_from { @@ -82,395 +80,404 @@ macro_rules! impl_from { )* }; } -impl_from!( - u8,u16,u32,u64,u128,usize, - i8,i16,i32,i64,i128,isize -); +impl_from!(u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize); -impl PartialEq for Fixed{ - #[inline] - fn eq(&self,other:&Self)->bool{ - self.bits.eq(&other.bits) - } +impl PartialEq for Fixed { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.bits.eq(&other.bits) + } } -impl PartialEq for Fixed +impl PartialEq for Fixed where - T:Copy, - BInt:::From, + T: Copy, + BInt: From, { - #[inline] - fn eq(&self,&other:&T)->bool{ - self.bits.eq(&other.into()) - } + #[inline] + fn eq(&self, &other: &T) -> bool { + self.bits.eq(&other.into()) + } } -impl Eq for Fixed{} +impl Eq for Fixed {} -impl PartialOrd for Fixed{ - #[inline] - fn partial_cmp(&self,other:&Self)->Option{ - self.bits.partial_cmp(&other.bits) - } +impl PartialOrd for Fixed { + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + self.bits.partial_cmp(&other.bits) + } } -impl PartialOrd for Fixed - where - T:Copy, - BInt:::From, +impl PartialOrd for Fixed +where + T: Copy, + BInt: From, { - #[inline] - fn partial_cmp(&self,&other:&T)->Option{ - self.bits.partial_cmp(&other.into()) - } + #[inline] + fn partial_cmp(&self, &other: &T) -> Option { + self.bits.partial_cmp(&other.into()) + } } -impl Ord for Fixed{ - #[inline] - fn cmp(&self,other:&Self)->std::cmp::Ordering{ - self.bits.cmp(&other.bits) - } +impl Ord for Fixed { + #[inline] + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.bits.cmp(&other.bits) + } } -impl std::ops::Neg for Fixed{ - type Output=Self; - #[inline] - fn neg(self)->Self{ - Self::from_bits(self.bits.neg()) - } +impl std::ops::Neg for Fixed { + type Output = Self; + #[inline] + fn neg(self) -> Self { + Self::from_bits(self.bits.neg()) + } } -impl std::iter::Sum for Fixed{ - #[inline] - fn sum>(iter:I)->Self{ - let mut sum=Self::ZERO; - for elem in iter{ - sum+=elem; - } - sum - } +impl std::iter::Sum for Fixed { + #[inline] + fn sum>(iter: I) -> Self { + let mut sum = Self::ZERO; + for elem in iter { + sum += elem; + } + sum + } } -const fn signed_shift(lhs:u64,rhs:i32)->u64{ - if rhs.is_negative(){ - lhs>>-rhs - }else{ - lhs< u64 { + if rhs.is_negative() { + lhs >> -rhs + } else { + lhs << rhs + } } macro_rules! impl_into_float { - ( $output: ty, $unsigned:ty, $exponent_bits:expr, $mantissa_bits:expr ) => { - impl Into<$output> for Fixed{ - #[inline] - fn into(self)->$output{ - const DIGIT_SHIFT:u32=6;//Log2[64] - // SBBB BBBB - // 1001 1110 0000 0000 - let sign=if self.bits.is_negative(){(1 as $unsigned)<<(<$unsigned>::BITS-1)}else{0}; - let unsigned=self.bits.unsigned_abs(); - let most_significant_bit=unsigned.bits(); - let exp=if unsigned.is_zero(){ - 0 - }else{ - let msb=most_significant_bit as $unsigned; - let _127=((1 as $unsigned)<<($exponent_bits-1))-1; - let msb_offset=msb+_127-1-F as $unsigned; - msb_offset<<($mantissa_bits-1) - }; - let digits=unsigned.digits(); - let digit_index=most_significant_bit.saturating_sub(1)>>DIGIT_SHIFT; - let digit=digits[digit_index as usize]; - //How many bits does the mantissa take from this digit - let take_bits=most_significant_bit-(digit_index<::from_bits(bits) - } - } - } + ( $output: ty, $unsigned:ty, $exponent_bits:expr, $mantissa_bits:expr ) => { + impl Into<$output> for Fixed { + #[inline] + fn into(self) -> $output { + const DIGIT_SHIFT: u32 = 6; //Log2[64] + // SBBB BBBB + // 1001 1110 0000 0000 + let sign = if self.bits.is_negative() { + (1 as $unsigned) << (<$unsigned>::BITS - 1) + } else { + 0 + }; + let unsigned = self.bits.unsigned_abs(); + let most_significant_bit = unsigned.bits(); + let exp = if unsigned.is_zero() { + 0 + } else { + let msb = most_significant_bit as $unsigned; + let _127 = ((1 as $unsigned) << ($exponent_bits - 1)) - 1; + let msb_offset = msb + _127 - 1 - F as $unsigned; + msb_offset << ($mantissa_bits - 1) + }; + let digits = unsigned.digits(); + let digit_index = most_significant_bit.saturating_sub(1) >> DIGIT_SHIFT; + let digit = digits[digit_index as usize]; + //How many bits does the mantissa take from this digit + let take_bits = most_significant_bit - (digit_index << DIGIT_SHIFT); + let rest_of_mantissa = $mantissa_bits as i32 - (take_bits as i32); + let mut unmasked_mant = signed_shift(digit, rest_of_mantissa) as $unsigned; + if 0 < rest_of_mantissa && digit_index != 0 { + //take the next digit down and shove some of its bits onto the bottom of the mantissa + let digit = digits[digit_index as usize - 1]; + let take_bits = most_significant_bit - ((digit_index - 1) << DIGIT_SHIFT); + let rest_of_mantissa = $mantissa_bits as i32 - (take_bits as i32); + let unmasked_mant2 = signed_shift(digit, rest_of_mantissa) as $unsigned; + unmasked_mant |= unmasked_mant2; + } + let mant = unmasked_mant & ((1 as $unsigned) << ($mantissa_bits - 1)) - 1; + let bits = sign | exp | mant; + <$output>::from_bits(bits) + } + } + }; } -impl_into_float!(f32,u32,8,24); -impl_into_float!(f64,u64,11,53); +impl_into_float!(f32, u32, 8, 24); +impl_into_float!(f64, u64, 11, 53); #[inline] fn integer_decode_f32(f: f32) -> (u64, i16, bool) { - let bits: u32 = f.to_bits(); - let sign: bool = bits & (1<<31) != 0; - let mut exponent: i16 = ((bits >> 23) & 0xff) as i16; - let mantissa = if exponent == 0 { - (bits & 0x7fffff) << 1 - } else { - (bits & 0x7fffff) | 0x800000 - }; - // Exponent bias + mantissa shift - exponent -= 127 + 23; - (mantissa as u64, exponent, sign) + let bits: u32 = f.to_bits(); + let sign: bool = bits & (1 << 31) != 0; + let mut exponent: i16 = ((bits >> 23) & 0xff) as i16; + let mantissa = if exponent == 0 { + (bits & 0x7fffff) << 1 + } else { + (bits & 0x7fffff) | 0x800000 + }; + // Exponent bias + mantissa shift + exponent -= 127 + 23; + (mantissa as u64, exponent, sign) } #[inline] fn integer_decode_f64(f: f64) -> (u64, i16, bool) { - let bits: u64 = f.to_bits(); - let sign: bool = bits & (1u64<<63) != 0; - let mut exponent: i16 = ((bits >> 52) & 0x7ff) as i16; - let mantissa = if exponent == 0 { - (bits & 0xfffffffffffff) << 1 - } else { - (bits & 0xfffffffffffff) | 0x10000000000000 - }; - // Exponent bias + mantissa shift - exponent -= 1023 + 52; - (mantissa, exponent, sign) + let bits: u64 = f.to_bits(); + let sign: bool = bits & (1u64 << 63) != 0; + let mut exponent: i16 = ((bits >> 52) & 0x7ff) as i16; + let mantissa = if exponent == 0 { + (bits & 0xfffffffffffff) << 1 + } else { + (bits & 0xfffffffffffff) | 0x10000000000000 + }; + // Exponent bias + mantissa shift + exponent -= 1023 + 52; + (mantissa, exponent, sign) } -#[derive(Debug,Eq,PartialEq)] -pub enum FixedFromFloatError{ - Nan, - Infinite, - Overflow, - Underflow, +#[derive(Debug, Eq, PartialEq)] +pub enum FixedFromFloatError { + Nan, + Infinite, + Overflow, + Underflow, } -impl FixedFromFloatError{ - pub fn underflow_to_zero(self)->Result,Self>{ - match self{ - FixedFromFloatError::Underflow=>Ok(Fixed::ZERO), - _=>Err(self), - } - } +impl FixedFromFloatError { + pub fn underflow_to_zero(self) -> Result, Self> { + match self { + FixedFromFloatError::Underflow => Ok(Fixed::ZERO), + _ => Err(self), + } + } } macro_rules! impl_from_float { - ( $decode:ident, $input: ty, $mantissa_bits:expr ) => { - impl TryFrom<$input> for Fixed{ - type Error=FixedFromFloatError; - #[inline] - fn try_from(value:$input)->Result{ - const DIGIT_SHIFT:u32=6; - match value.classify(){ - std::num::FpCategory::Nan=>Err(FixedFromFloatError::Nan), - std::num::FpCategory::Infinite=>Err(FixedFromFloatError::Infinite), - std::num::FpCategory::Zero=>Ok(Self::ZERO), - std::num::FpCategory::Subnormal - |std::num::FpCategory::Normal - =>{ - let (m,e,s)=$decode(value); - let mut digits=[0u64;N]; - let most_significant_bit=e as i32+$mantissa_bits as i32+F as i32; - if most_significant_bit<0{ - return Err(FixedFromFloatError::Underflow); - } - let digit_index=most_significant_bit>>DIGIT_SHIFT; - let digit=digits.get_mut(digit_index as usize).ok_or(FixedFromFloatError::Overflow)?; - let take_bits=most_significant_bit-(digit_index< { + impl TryFrom<$input> for Fixed { + type Error = FixedFromFloatError; + #[inline] + fn try_from(value: $input) -> Result { + const DIGIT_SHIFT: u32 = 6; + match value.classify() { + std::num::FpCategory::Nan => Err(FixedFromFloatError::Nan), + std::num::FpCategory::Infinite => Err(FixedFromFloatError::Infinite), + std::num::FpCategory::Zero => Ok(Self::ZERO), + std::num::FpCategory::Subnormal | std::num::FpCategory::Normal => { + let (m, e, s) = $decode(value); + let mut digits = [0u64; N]; + let most_significant_bit = e as i32 + $mantissa_bits as i32 + F as i32; + if most_significant_bit < 0 { + return Err(FixedFromFloatError::Underflow); + } + let digit_index = most_significant_bit >> DIGIT_SHIFT; + let digit = digits + .get_mut(digit_index as usize) + .ok_or(FixedFromFloatError::Overflow)?; + let take_bits = most_significant_bit - (digit_index << DIGIT_SHIFT); + let rest_of_mantissa = -($mantissa_bits as i32 - (take_bits as i32)); + *digit = signed_shift(m, rest_of_mantissa); + if rest_of_mantissa < 0 && digit_index != 0 { + //we don't care if some float bits are partially truncated + if let Some(digit) = digits.get_mut((digit_index - 1) as usize) { + let take_bits = + most_significant_bit - ((digit_index - 1) << DIGIT_SHIFT); + let rest_of_mantissa = + -($mantissa_bits as i32 - (take_bits as i32)); + *digit = signed_shift(m, rest_of_mantissa); + } + } + let bits = BInt::from_bits(bnum::BUint::from_digits(digits)); + Ok(if s { + Self::from_bits(bits.overflowing_neg().0) + } else { + Self::from_bits(bits) + }) + } + } + } + } + }; } -impl_from_float!(integer_decode_f32,f32,24); -impl_from_float!(integer_decode_f64,f64,53); +impl_from_float!(integer_decode_f32, f32, 24); +impl_from_float!(integer_decode_f64, f64, 53); -impl core::fmt::Display for Fixed{ - #[inline] - fn fmt(&self,f:&mut core::fmt::Formatter)->Result<(),core::fmt::Error>{ - let float:f32=(*self).into(); - core::write!(f,"{:.3}",float) - } +impl core::fmt::Display for Fixed { + #[inline] + fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> { + let float: f32 = (*self).into(); + core::write!(f, "{:.3}", float) + } } macro_rules! impl_additive_operator { - ( $struct: ident, $trait: ident, $method: ident, $output: ty ) => { - impl $struct{ - #[inline] - pub const fn $method(self, other: Self) -> Self { - Self::from_bits(self.bits.$method(other.bits)) - } - } - impl core::ops::$trait for $struct{ - type Output = $output; - #[inline] - fn $method(self, other: Self) -> Self::Output { - self.$method(other) - } - } - impl core::ops::$trait for $struct - where - BInt:::From, - { - type Output = $output; - #[inline] - fn $method(self, other: U) -> Self::Output { - Self::from_bits(self.bits.$method(BInt::::from(other).shl(F as u32))) - } - } - }; + ( $struct: ident, $trait: ident, $method: ident, $output: ty ) => { + impl $struct { + #[inline] + pub const fn $method(self, other: Self) -> Self { + Self::from_bits(self.bits.$method(other.bits)) + } + } + impl core::ops::$trait for $struct { + type Output = $output; + #[inline] + fn $method(self, other: Self) -> Self::Output { + self.$method(other) + } + } + impl core::ops::$trait for $struct + where + BInt: From, + { + type Output = $output; + #[inline] + fn $method(self, other: U) -> Self::Output { + Self::from_bits(self.bits.$method(BInt::::from(other).shl(F as u32))) + } + } + }; } macro_rules! impl_additive_assign_operator { - ( $struct: ident, $trait: ident, $method: ident ) => { - impl core::ops::$trait for $struct{ - #[inline] - fn $method(&mut self, other: Self) { - self.bits.$method(other.bits); - } - } - impl core::ops::$trait for $struct - where - BInt:::From, - { - #[inline] - fn $method(&mut self, other: U) { - self.bits.$method(BInt::::from(other).shl(F as u32)); - } - } - }; + ( $struct: ident, $trait: ident, $method: ident ) => { + impl core::ops::$trait for $struct { + #[inline] + fn $method(&mut self, other: Self) { + self.bits.$method(other.bits); + } + } + impl core::ops::$trait for $struct + where + BInt: From, + { + #[inline] + fn $method(&mut self, other: U) { + self.bits.$method(BInt::::from(other).shl(F as u32)); + } + } + }; } // Impl arithmetic pperators -impl_additive_assign_operator!( Fixed, AddAssign, add_assign ); -impl_additive_operator!( Fixed, Add, add, Self ); -impl_additive_assign_operator!( Fixed, SubAssign, sub_assign ); -impl_additive_operator!( Fixed, Sub, sub, Self ); -impl_additive_assign_operator!( Fixed, RemAssign, rem_assign ); -impl_additive_operator!( Fixed, Rem, rem, Self ); +impl_additive_assign_operator!(Fixed, AddAssign, add_assign); +impl_additive_operator!(Fixed, Add, add, Self); +impl_additive_assign_operator!(Fixed, SubAssign, sub_assign); +impl_additive_operator!(Fixed, Sub, sub, Self); +impl_additive_assign_operator!(Fixed, RemAssign, rem_assign); +impl_additive_operator!(Fixed, Rem, rem, Self); // Impl bitwise operators -impl_additive_assign_operator!( Fixed, BitAndAssign, bitand_assign ); -impl_additive_operator!( Fixed, BitAnd, bitand, Self ); -impl_additive_assign_operator!( Fixed, BitOrAssign, bitor_assign ); -impl_additive_operator!( Fixed, BitOr, bitor, Self ); -impl_additive_assign_operator!( Fixed, BitXorAssign, bitxor_assign ); -impl_additive_operator!( Fixed, BitXor, bitxor, Self ); +impl_additive_assign_operator!(Fixed, BitAndAssign, bitand_assign); +impl_additive_operator!(Fixed, BitAnd, bitand, Self); +impl_additive_assign_operator!(Fixed, BitOrAssign, bitor_assign); +impl_additive_operator!(Fixed, BitOr, bitor, Self); +impl_additive_assign_operator!(Fixed, BitXorAssign, bitxor_assign); +impl_additive_operator!(Fixed, BitXor, bitxor, Self); // non-wide operators. The result is the same width as the inputs. // This macro is not used in the default configuration. #[allow(unused_macros)] macro_rules! impl_multiplicative_operator_not_const_generic { - ( ($struct: ident, $trait: ident, $method: ident, $output: ty ), $width:expr ) => { - impl core::ops::$trait for $struct<$width,F>{ - type Output = $output; - #[inline] - fn $method(self, other: Self) -> Self::Output { - paste::item!{ - self.[](other) - } - } - } - }; + ( ($struct: ident, $trait: ident, $method: ident, $output: ty ), $width:expr ) => { + impl core::ops::$trait for $struct<$width, F> { + type Output = $output; + #[inline] + fn $method(self, other: Self) -> Self::Output { + paste::item! { + self.[](other) + } + } + } + }; } macro_rules! impl_multiplicative_assign_operator_not_const_generic { - ( ($struct: ident, $trait: ident, $method: ident, $non_assign_method: ident ), $width:expr ) => { - impl core::ops::$trait for $struct<$width,F>{ - #[inline] - fn $method(&mut self, other: Self) { - paste::item!{ - *self=self.[](other); - } - } - } - }; + ( ($struct: ident, $trait: ident, $method: ident, $non_assign_method: ident ), $width:expr ) => { + impl core::ops::$trait for $struct<$width, F> { + #[inline] + fn $method(&mut self, other: Self) { + paste::item! { + *self=self.[](other); + } + } + } + }; } macro_rules! impl_multiply_operator_not_const_generic { - ( ($struct: ident, $trait: ident, $method: ident, $output: ty ), $width:expr ) => { - impl $struct<$width,F>{ - paste::item!{ - #[inline] - pub fn [](self, rhs: Self) -> Self { - let (low,high)=self.bits.unsigned_abs().widening_mul(rhs.bits.unsigned_abs()); - let out:BInt::<{$width*2}>=unsafe{core::mem::transmute([low,high])}; - if self.is_negative()==rhs.is_negative(){ - Self::from_bits(out.shr(F as u32).as_()) - }else{ - -Self::from_bits(out.shr(F as u32).as_()) - } - } - } - } - #[cfg(not(feature="wide-mul"))] - impl_multiplicative_operator_not_const_generic!(($struct, $trait, $method, $output ), $width); - #[cfg(feature="deferred-division")] - impl ratio_ops::ratio::Divide for Fixed<$width,{$width*32}>{ - type Output=Self; - #[inline] - fn divide(self, other: i64)->Self::Output{ - Self::from_bits(self.bits.div_euclid(BInt::from(other))) - } - } - } + ( ($struct: ident, $trait: ident, $method: ident, $output: ty ), $width:expr ) => { + impl $struct<$width, F> { + paste::item! { + #[inline] + pub fn [](self, rhs: Self) -> Self { + let (low,high)=self.bits.unsigned_abs().widening_mul(rhs.bits.unsigned_abs()); + let out:BInt::<{$width*2}>=unsafe{core::mem::transmute([low,high])}; + if self.is_negative()==rhs.is_negative(){ + Self::from_bits(out.shr(F as u32).as_()) + }else{ + -Self::from_bits(out.shr(F as u32).as_()) + } + } + } + } + #[cfg(not(feature = "wide-mul"))] + impl_multiplicative_operator_not_const_generic!( + ($struct, $trait, $method, $output), + $width + ); + #[cfg(feature = "deferred-division")] + impl ratio_ops::ratio::Divide for Fixed<$width, { $width * 32 }> { + type Output = Self; + #[inline] + fn divide(self, other: i64) -> Self::Output { + Self::from_bits(self.bits.div_euclid(BInt::from(other))) + } + } + }; } macro_rules! impl_divide_operator_not_const_generic { - ( ($struct: ident, $trait: ident, $method: ident, $output: ty ), $width:expr ) => { - impl $struct<$width,F>{ - paste::item!{ - #[inline] - pub fn [](self,other:Self)->Self{ - //this only needs to be $width+F as u32/64+1 but MUH CONST GENERICS!!!!! - let lhs=self.bits.as_::>().shl(F as u32); - let rhs=other.bits.as_::>(); - Self::from_bits(lhs.div_euclid(rhs).as_()) - } - } - } - #[cfg(all(not(feature="wide-mul"),not(feature="deferred-division")))] - impl_multiplicative_operator_not_const_generic!(($struct, $trait, $method, $output ), $width); - #[cfg(all(not(feature="wide-mul"),feature="deferred-division"))] - impl ratio_ops::ratio::Divide for $struct<$width,F>{ - type Output = $output; - #[inline] - fn divide(self, other: Self) -> Self::Output { - paste::item!{ - self.[](other) - } - } - } - }; + ( ($struct: ident, $trait: ident, $method: ident, $output: ty ), $width:expr ) => { + impl $struct<$width, F> { + paste::item! { + #[inline] + pub fn [](self,other:Self)->Self{ + //this only needs to be $width+F as u32/64+1 but MUH CONST GENERICS!!!!! + let lhs=self.bits.as_::>().shl(F as u32); + let rhs=other.bits.as_::>(); + Self::from_bits(lhs.div_euclid(rhs).as_()) + } + } + } + #[cfg(all(not(feature = "wide-mul"), not(feature = "deferred-division")))] + impl_multiplicative_operator_not_const_generic!( + ($struct, $trait, $method, $output), + $width + ); + #[cfg(all(not(feature = "wide-mul"), feature = "deferred-division"))] + impl ratio_ops::ratio::Divide for $struct<$width, F> { + type Output = $output; + #[inline] + fn divide(self, other: Self) -> Self::Output { + paste::item! { + self.[](other) + } + } + } + }; } macro_rules! impl_multiplicative_operator { - ( $struct: ident, $trait: ident, $method: ident, $inner_method: ident, $output: ty ) => { - impl core::ops::$trait for $struct - where - BInt:::From+core::ops::$trait, - { - type Output = $output; - #[inline] - fn $method(self,other:U)->Self::Output{ - Self::from_bits(self.bits.$inner_method(BInt::::from(other))) - } - } - }; + ( $struct: ident, $trait: ident, $method: ident, $inner_method: ident, $output: ty ) => { + impl core::ops::$trait for $struct + where + BInt: From + core::ops::$trait, + { + type Output = $output; + #[inline] + fn $method(self, other: U) -> Self::Output { + Self::from_bits(self.bits.$inner_method(BInt::::from(other))) + } + } + }; } macro_rules! impl_multiplicative_assign_operator { - ( $struct: ident, $trait: ident, $method: ident, $not_assign_method: ident ) => { - impl core::ops::$trait for $struct - where - BInt:::From+core::ops::$trait, - { - #[inline] - fn $method(&mut self,other:U){ - self.bits=self.bits.$not_assign_method(BInt::::from(other)); - } - } - }; + ( $struct: ident, $trait: ident, $method: ident, $not_assign_method: ident ) => { + impl core::ops::$trait for $struct + where + BInt: From + core::ops::$trait, + { + #[inline] + fn $method(&mut self, other: U) { + self.bits = self.bits.$not_assign_method(BInt::::from(other)); + } + } + }; } macro_rules! macro_repeated{ @@ -486,94 +493,108 @@ macro_rules! macro_repeated{ } macro_rules! macro_16 { - ( $macro: ident, $any:tt ) => { - macro_repeated!($macro,$any,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16); - } + ( $macro: ident, $any:tt ) => { + macro_repeated!($macro, $any, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16); + }; } -macro_16!( impl_multiplicative_assign_operator_not_const_generic, (Fixed, MulAssign, mul_assign, mul) ); -macro_16!( impl_multiply_operator_not_const_generic, (Fixed, Mul, mul, Self) ); -macro_16!( impl_multiplicative_assign_operator_not_const_generic, (Fixed, DivAssign, div_assign, div) ); -macro_16!( impl_divide_operator_not_const_generic, (Fixed, Div, div, Self) ); -impl_multiplicative_assign_operator!( Fixed, MulAssign, mul_assign, mul ); -impl_multiplicative_operator!( Fixed, Mul, mul, mul, Self ); -impl_multiplicative_assign_operator!( Fixed, DivAssign, div_assign, div_euclid ); -impl_multiplicative_operator!( Fixed, Div, div, div_euclid, Self ); -#[cfg(feature="deferred-division")] -impl core::ops::Div> for Fixed{ - type Output=ratio_ops::ratio::Ratio,Fixed>; - #[inline] - fn div(self, other: Fixed)->Self::Output{ - ratio_ops::ratio::Ratio::new(self,other) - } +macro_16!( + impl_multiplicative_assign_operator_not_const_generic, + (Fixed, MulAssign, mul_assign, mul) +); +macro_16!( + impl_multiply_operator_not_const_generic, + (Fixed, Mul, mul, Self) +); +macro_16!( + impl_multiplicative_assign_operator_not_const_generic, + (Fixed, DivAssign, div_assign, div) +); +macro_16!( + impl_divide_operator_not_const_generic, + (Fixed, Div, div, Self) +); +impl_multiplicative_assign_operator!(Fixed, MulAssign, mul_assign, mul); +impl_multiplicative_operator!(Fixed, Mul, mul, mul, Self); +impl_multiplicative_assign_operator!(Fixed, DivAssign, div_assign, div_euclid); +impl_multiplicative_operator!(Fixed, Div, div, div_euclid, Self); +#[cfg(feature = "deferred-division")] +impl + core::ops::Div> for Fixed +{ + type Output = ratio_ops::ratio::Ratio, Fixed>; + #[inline] + fn div(self, other: Fixed) -> Self::Output { + ratio_ops::ratio::Ratio::new(self, other) + } } -#[cfg(feature="deferred-division")] -impl ratio_ops::ratio::Parity for Fixed{ - fn parity(&self)->bool{ - self.is_negative() - } +#[cfg(feature = "deferred-division")] +impl ratio_ops::ratio::Parity for Fixed { + fn parity(&self) -> bool { + self.is_negative() + } } macro_rules! impl_shift_operator { - ( $struct: ident, $trait: ident, $method: ident, $output: ty ) => { - impl core::ops::$trait for $struct{ - type Output = $output; - #[inline] - fn $method(self, other: u32) -> Self::Output { - Self::from_bits(self.bits.$method(other)) - } - } - }; + ( $struct: ident, $trait: ident, $method: ident, $output: ty ) => { + impl core::ops::$trait for $struct { + type Output = $output; + #[inline] + fn $method(self, other: u32) -> Self::Output { + Self::from_bits(self.bits.$method(other)) + } + } + }; } macro_rules! impl_shift_assign_operator { - ( $struct: ident, $trait: ident, $method: ident ) => { - impl core::ops::$trait for $struct{ - #[inline] - fn $method(&mut self, other: u32) { - self.bits.$method(other); - } - } - }; + ( $struct: ident, $trait: ident, $method: ident ) => { + impl core::ops::$trait for $struct { + #[inline] + fn $method(&mut self, other: u32) { + self.bits.$method(other); + } + } + }; } -impl_shift_assign_operator!( Fixed, ShlAssign, shl_assign ); -impl_shift_operator!( Fixed, Shl, shl, Self ); -impl_shift_assign_operator!( Fixed, ShrAssign, shr_assign ); -impl_shift_operator!( Fixed, Shr, shr, Self ); +impl_shift_assign_operator!(Fixed, ShlAssign, shl_assign); +impl_shift_operator!(Fixed, Shl, shl, Self); +impl_shift_assign_operator!(Fixed, ShrAssign, shr_assign); +impl_shift_operator!(Fixed, Shr, shr, Self); // wide operators. The result width is the sum of the input widths, i.e. none of the multiplication #[allow(unused_macros)] -macro_rules! impl_wide_operators{ - ($lhs:expr,$rhs:expr)=>{ - impl core::ops::Mul> for Fixed<$lhs,{$lhs*32}>{ - type Output=Fixed<{$lhs+$rhs},{($lhs+$rhs)*32}>; - #[inline] - fn mul(self, other: Fixed<$rhs,{$rhs*32}>)->Self::Output{ - paste::item!{ - self.[](other) - } - } - } - #[cfg(not(feature="deferred-division"))] - impl core::ops::Div> for Fixed<$lhs,{$lhs*32}>{ - type Output=Fixed<{$lhs+$rhs},{($lhs+$rhs)*32}>; - #[inline] - fn div(self, other: Fixed<$rhs,{$rhs*32}>)->Self::Output{ - paste::item!{ - self.[](other) - } - } - } - #[cfg(feature="deferred-division")] - impl ratio_ops::ratio::Divide> for Fixed<$lhs,{$lhs*32}>{ - type Output=Fixed<{$lhs+$rhs},{($lhs+$rhs)*32}>; - #[inline] - fn divide(self, other: Fixed<$rhs,{$rhs*32}>)->Self::Output{ - paste::item!{ - self.[](other) - } - } - } - } +macro_rules! impl_wide_operators { + ($lhs:expr,$rhs:expr) => { + impl core::ops::Mul> for Fixed<$lhs, { $lhs * 32 }> { + type Output = Fixed<{ $lhs + $rhs }, { ($lhs + $rhs) * 32 }>; + #[inline] + fn mul(self, other: Fixed<$rhs, { $rhs * 32 }>) -> Self::Output { + paste::item! { + self.[](other) + } + } + } + #[cfg(not(feature = "deferred-division"))] + impl core::ops::Div> for Fixed<$lhs, { $lhs * 32 }> { + type Output = Fixed<{ $lhs + $rhs }, { ($lhs + $rhs) * 32 }>; + #[inline] + fn div(self, other: Fixed<$rhs, { $rhs * 32 }>) -> Self::Output { + paste::item! { + self.[](other) + } + } + } + #[cfg(feature = "deferred-division")] + impl ratio_ops::ratio::Divide> for Fixed<$lhs, { $lhs * 32 }> { + type Output = Fixed<{ $lhs + $rhs }, { ($lhs + $rhs) * 32 }>; + #[inline] + fn divide(self, other: Fixed<$rhs, { $rhs * 32 }>) -> Self::Output { + paste::item! { + self.[](other) + } + } + } + }; } // WIDE MUL: multiply into a wider type @@ -648,30 +669,137 @@ macro_rules! impl_wide_same_size_not_const_generic{ //const generics sidestepped wahoo macro_repeated!( - impl_wide_not_const_generic,(), - (2,1),(3,1),(4,1),(5,1),(6,1),(7,1),(8,1),(9,1),(10,1),(11,1),(12,1),(13,1),(14,1),(15,1), - (1,2), (3,2),(4,2),(5,2),(6,2),(7,2),(8,2),(9,2),(10,2),(11,2),(12,2),(13,2),(14,2), - (1,3),(2,3), (4,3),(5,3),(6,3),(7,3),(8,3),(9,3),(10,3),(11,3),(12,3),(13,3), - (1,4),(2,4),(3,4), (5,4),(6,4),(7,4),(8,4),(9,4),(10,4),(11,4),(12,4), - (1,5),(2,5),(3,5),(4,5), (6,5),(7,5),(8,5),(9,5),(10,5),(11,5), - (1,6),(2,6),(3,6),(4,6),(5,6), (7,6),(8,6),(9,6),(10,6), - (1,7),(2,7),(3,7),(4,7),(5,7),(6,7), (8,7),(9,7), - (1,8),(2,8),(3,8),(4,8),(5,8),(6,8),(7,8), (9,8), - (1,9),(2,9),(3,9),(4,9),(5,9),(6,9),(7,9), - (1,10),(2,10),(3,10),(4,10),(5,10),(6,10), - (1,11),(2,11),(3,11),(4,11),(5,11), - (1,12),(2,12),(3,12),(4,12), - (1,13),(2,13),(3,13), - (1,14),(2,14), - (1,15) + impl_wide_not_const_generic, + (), + (2, 1), + (3, 1), + (4, 1), + (5, 1), + (6, 1), + (7, 1), + (8, 1), + (9, 1), + (10, 1), + (11, 1), + (12, 1), + (13, 1), + (14, 1), + (15, 1), + (1, 2), + (3, 2), + (4, 2), + (5, 2), + (6, 2), + (7, 2), + (8, 2), + (9, 2), + (10, 2), + (11, 2), + (12, 2), + (13, 2), + (14, 2), + (1, 3), + (2, 3), + (4, 3), + (5, 3), + (6, 3), + (7, 3), + (8, 3), + (9, 3), + (10, 3), + (11, 3), + (12, 3), + (13, 3), + (1, 4), + (2, 4), + (3, 4), + (5, 4), + (6, 4), + (7, 4), + (8, 4), + (9, 4), + (10, 4), + (11, 4), + (12, 4), + (1, 5), + (2, 5), + (3, 5), + (4, 5), + (6, 5), + (7, 5), + (8, 5), + (9, 5), + (10, 5), + (11, 5), + (1, 6), + (2, 6), + (3, 6), + (4, 6), + (5, 6), + (7, 6), + (8, 6), + (9, 6), + (10, 6), + (1, 7), + (2, 7), + (3, 7), + (4, 7), + (5, 7), + (6, 7), + (8, 7), + (9, 7), + (1, 8), + (2, 8), + (3, 8), + (4, 8), + (5, 8), + (6, 8), + (7, 8), + (9, 8), + (1, 9), + (2, 9), + (3, 9), + (4, 9), + (5, 9), + (6, 9), + (7, 9), + (1, 10), + (2, 10), + (3, 10), + (4, 10), + (5, 10), + (6, 10), + (1, 11), + (2, 11), + (3, 11), + (4, 11), + (5, 11), + (1, 12), + (2, 12), + (3, 12), + (4, 12), + (1, 13), + (2, 13), + (3, 13), + (1, 14), + (2, 14), + (1, 15) ); macro_repeated!( - impl_wide_same_size_not_const_generic,(), - 1,2,3,4,5,6,7,8 + impl_wide_same_size_not_const_generic, + (), + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8 ); -pub trait Fix{ - fn fix(self)->Out; +pub trait Fix { + fn fix(self) -> Out; } macro_rules! impl_fix_rhs_lt_lhs_not_const_generic{ @@ -720,129 +848,357 @@ macro_rules! impl_fix_lhs_lt_rhs_not_const_generic{ } } } -macro_rules! impl_fix_lhs_eq_rhs_not_const_generic{ - ( +macro_rules! impl_fix_lhs_eq_rhs_not_const_generic { + ( (), ($lhs:expr,$rhs:expr) - )=>{ - impl Fixed<$lhs,{$lhs*32}> - { - paste::item!{ - #[inline] - pub fn [](self)->Fixed<$rhs,{$rhs*32}>{ - self - } - } - } - impl Fix> for Fixed<$lhs,{$lhs*32}>{ - fn fix(self)->Fixed<$rhs,{$rhs*32}>{ - paste::item!{ - self.[]() - } - } - } - } + ) => { + impl Fixed<$lhs, { $lhs * 32 }> { + paste::item! { + #[inline] + pub fn [](self)->Fixed<$rhs,{$rhs*32}>{ + self + } + } + } + impl Fix> for Fixed<$lhs, { $lhs * 32 }> { + fn fix(self) -> Fixed<$rhs, { $rhs * 32 }> { + paste::item! { + self.[]() + } + } + } + }; } // I LOVE NOT BEING ABLE TO USE CONST GENERICS macro_repeated!( - impl_fix_rhs_lt_lhs_not_const_generic,(), - (2,1),(3,1),(4,1),(5,1),(6,1),(7,1),(8,1),(9,1),(10,1),(11,1),(12,1),(13,1),(14,1),(15,1),(16,1),(17,1), - (3,2),(4,2),(5,2),(6,2),(7,2),(8,2),(9,2),(10,2),(11,2),(12,2),(13,2),(14,2),(15,2),(16,2), - (4,3),(5,3),(6,3),(7,3),(8,3),(9,3),(10,3),(11,3),(12,3),(13,3),(14,3),(15,3),(16,3), - (5,4),(6,4),(7,4),(8,4),(9,4),(10,4),(11,4),(12,4),(13,4),(14,4),(15,4),(16,4), - (6,5),(7,5),(8,5),(9,5),(10,5),(11,5),(12,5),(13,5),(14,5),(15,5),(16,5), - (7,6),(8,6),(9,6),(10,6),(11,6),(12,6),(13,6),(14,6),(15,6),(16,6), - (8,7),(9,7),(10,7),(11,7),(12,7),(13,7),(14,7),(15,7),(16,7), - (9,8),(10,8),(11,8),(12,8),(13,8),(14,8),(15,8),(16,8), - (10,9),(11,9),(12,9),(13,9),(14,9),(15,9),(16,9), - (11,10),(12,10),(13,10),(14,10),(15,10),(16,10), - (12,11),(13,11),(14,11),(15,11),(16,11), - (13,12),(14,12),(15,12),(16,12), - (14,13),(15,13),(16,13), - (15,14),(16,14), - (16,15) + impl_fix_rhs_lt_lhs_not_const_generic, + (), + (2, 1), + (3, 1), + (4, 1), + (5, 1), + (6, 1), + (7, 1), + (8, 1), + (9, 1), + (10, 1), + (11, 1), + (12, 1), + (13, 1), + (14, 1), + (15, 1), + (16, 1), + (17, 1), + (3, 2), + (4, 2), + (5, 2), + (6, 2), + (7, 2), + (8, 2), + (9, 2), + (10, 2), + (11, 2), + (12, 2), + (13, 2), + (14, 2), + (15, 2), + (16, 2), + (4, 3), + (5, 3), + (6, 3), + (7, 3), + (8, 3), + (9, 3), + (10, 3), + (11, 3), + (12, 3), + (13, 3), + (14, 3), + (15, 3), + (16, 3), + (5, 4), + (6, 4), + (7, 4), + (8, 4), + (9, 4), + (10, 4), + (11, 4), + (12, 4), + (13, 4), + (14, 4), + (15, 4), + (16, 4), + (6, 5), + (7, 5), + (8, 5), + (9, 5), + (10, 5), + (11, 5), + (12, 5), + (13, 5), + (14, 5), + (15, 5), + (16, 5), + (7, 6), + (8, 6), + (9, 6), + (10, 6), + (11, 6), + (12, 6), + (13, 6), + (14, 6), + (15, 6), + (16, 6), + (8, 7), + (9, 7), + (10, 7), + (11, 7), + (12, 7), + (13, 7), + (14, 7), + (15, 7), + (16, 7), + (9, 8), + (10, 8), + (11, 8), + (12, 8), + (13, 8), + (14, 8), + (15, 8), + (16, 8), + (10, 9), + (11, 9), + (12, 9), + (13, 9), + (14, 9), + (15, 9), + (16, 9), + (11, 10), + (12, 10), + (13, 10), + (14, 10), + (15, 10), + (16, 10), + (12, 11), + (13, 11), + (14, 11), + (15, 11), + (16, 11), + (13, 12), + (14, 12), + (15, 12), + (16, 12), + (14, 13), + (15, 13), + (16, 13), + (15, 14), + (16, 14), + (16, 15) ); macro_repeated!( - impl_fix_lhs_lt_rhs_not_const_generic,(), - (1,2), - (1,3),(2,3), - (1,4),(2,4),(3,4), - (1,5),(2,5),(3,5),(4,5), - (1,6),(2,6),(3,6),(4,6),(5,6), - (1,7),(2,7),(3,7),(4,7),(5,7),(6,7), - (1,8),(2,8),(3,8),(4,8),(5,8),(6,8),(7,8), - (1,9),(2,9),(3,9),(4,9),(5,9),(6,9),(7,9),(8,9), - (1,10),(2,10),(3,10),(4,10),(5,10),(6,10),(7,10),(8,10),(9,10), - (1,11),(2,11),(3,11),(4,11),(5,11),(6,11),(7,11),(8,11),(9,11),(10,11), - (1,12),(2,12),(3,12),(4,12),(5,12),(6,12),(7,12),(8,12),(9,12),(10,12),(11,12), - (1,13),(2,13),(3,13),(4,13),(5,13),(6,13),(7,13),(8,13),(9,13),(10,13),(11,13),(12,13), - (1,14),(2,14),(3,14),(4,14),(5,14),(6,14),(7,14),(8,14),(9,14),(10,14),(11,14),(12,14),(13,14), - (1,15),(2,15),(3,15),(4,15),(5,15),(6,15),(7,15),(8,15),(9,15),(10,15),(11,15),(12,15),(13,15),(14,15), - (1,16),(2,16),(3,16),(4,16),(5,16),(6,16),(7,16),(8,16),(9,16),(10,16),(11,16),(12,16),(13,16),(14,16),(15,16) + impl_fix_lhs_lt_rhs_not_const_generic, + (), + (1, 2), + (1, 3), + (2, 3), + (1, 4), + (2, 4), + (3, 4), + (1, 5), + (2, 5), + (3, 5), + (4, 5), + (1, 6), + (2, 6), + (3, 6), + (4, 6), + (5, 6), + (1, 7), + (2, 7), + (3, 7), + (4, 7), + (5, 7), + (6, 7), + (1, 8), + (2, 8), + (3, 8), + (4, 8), + (5, 8), + (6, 8), + (7, 8), + (1, 9), + (2, 9), + (3, 9), + (4, 9), + (5, 9), + (6, 9), + (7, 9), + (8, 9), + (1, 10), + (2, 10), + (3, 10), + (4, 10), + (5, 10), + (6, 10), + (7, 10), + (8, 10), + (9, 10), + (1, 11), + (2, 11), + (3, 11), + (4, 11), + (5, 11), + (6, 11), + (7, 11), + (8, 11), + (9, 11), + (10, 11), + (1, 12), + (2, 12), + (3, 12), + (4, 12), + (5, 12), + (6, 12), + (7, 12), + (8, 12), + (9, 12), + (10, 12), + (11, 12), + (1, 13), + (2, 13), + (3, 13), + (4, 13), + (5, 13), + (6, 13), + (7, 13), + (8, 13), + (9, 13), + (10, 13), + (11, 13), + (12, 13), + (1, 14), + (2, 14), + (3, 14), + (4, 14), + (5, 14), + (6, 14), + (7, 14), + (8, 14), + (9, 14), + (10, 14), + (11, 14), + (12, 14), + (13, 14), + (1, 15), + (2, 15), + (3, 15), + (4, 15), + (5, 15), + (6, 15), + (7, 15), + (8, 15), + (9, 15), + (10, 15), + (11, 15), + (12, 15), + (13, 15), + (14, 15), + (1, 16), + (2, 16), + (3, 16), + (4, 16), + (5, 16), + (6, 16), + (7, 16), + (8, 16), + (9, 16), + (10, 16), + (11, 16), + (12, 16), + (13, 16), + (14, 16), + (15, 16) ); macro_repeated!( - impl_fix_lhs_eq_rhs_not_const_generic,(), - (1,1),(2,2),(3,3),(4,4),(5,5),(6,6),(7,7),(8,8),(9,9),(10,10),(11,11),(12,12),(13,13),(14,14),(15,15),(16,16) + impl_fix_lhs_eq_rhs_not_const_generic, + (), + (1, 1), + (2, 2), + (3, 3), + (4, 4), + (5, 5), + (6, 6), + (7, 7), + (8, 8), + (9, 9), + (10, 10), + (11, 11), + (12, 12), + (13, 13), + (14, 14), + (15, 15), + (16, 16) ); -macro_rules! impl_not_const_generic{ - ($n:expr,$_2n:expr)=>{ - impl Fixed<$n,{$n*32}>{ - paste::item!{ - #[inline] - pub fn sqrt_unchecked(self)->Self{ - //1<>1 (sqrt-ish) - //3. add on fractional offset - //Voila - let used_bits=self.bits.bits() as i32-1-($n*32) as i32; - let max_shift=((used_bits>>1)+($n*32) as i32) as u32; - let mut result=Self::ZERO; +macro_rules! impl_not_const_generic { + ($n:expr,$_2n:expr) => { + impl Fixed<$n, { $n * 32 }> { + paste::item! { + #[inline] + pub fn sqrt_unchecked(self)->Self{ + //1<>1 (sqrt-ish) + //3. add on fractional offset + //Voila + let used_bits=self.bits.bits() as i32-1-($n*32) as i32; + let max_shift=((used_bits>>1)+($n*32) as i32) as u32; + let mut result=Self::ZERO; - //resize self to match the wide mul output - let wide_self=self.[](); - //descend down the bits and check if flipping each bit would push the square over the input value - for shift in (0..=max_shift).rev(){ - let new_result={ - let mut bits=result.to_bits().to_bits(); - bits.set_bit(shift,true); - Self::from_bits(BInt::from_bits(bits)) - }; - if new_result.[](new_result)<=wide_self{ - result=new_result; - } - } - result - } - } - #[inline] - pub fn sqrt(self)->Self{ - if selfOption{ - if self](); + //descend down the bits and check if flipping each bit would push the square over the input value + for shift in (0..=max_shift).rev(){ + let new_result={ + let mut bits=result.to_bits().to_bits(); + bits.set_bit(shift,true); + Self::from_bits(BInt::from_bits(bits)) + }; + if new_result.[](new_result)<=wide_self{ + result=new_result; + } + } + result + } + } + #[inline] + pub fn sqrt(self) -> Self { + if self < Self::ZERO { + panic!("Square root less than zero") + } else { + self.sqrt_unchecked() + } + } + #[inline] + pub fn sqrt_checked(self) -> Option { + if self < Self::ZERO { + None + } else { + Some(self.sqrt_unchecked()) + } + } + } + }; } -impl_not_const_generic!(1,2); -impl_not_const_generic!(2,4); -impl_not_const_generic!(3,6); -impl_not_const_generic!(4,8); -impl_not_const_generic!(5,10); -impl_not_const_generic!(6,12); -impl_not_const_generic!(7,14); -impl_not_const_generic!(8,16); +impl_not_const_generic!(1, 2); +impl_not_const_generic!(2, 4); +impl_not_const_generic!(3, 6); +impl_not_const_generic!(4, 8); +impl_not_const_generic!(5, 10); +impl_not_const_generic!(6, 12); +impl_not_const_generic!(7, 14); +impl_not_const_generic!(8, 16); diff --git a/lib/fixed_wide/src/lib.rs b/lib/fixed_wide/src/lib.rs index 8767a20..1814ece 100644 --- a/lib/fixed_wide/src/lib.rs +++ b/lib/fixed_wide/src/lib.rs @@ -1,7 +1,7 @@ pub mod fixed; pub mod types; -#[cfg(feature="zeroes")] +#[cfg(feature = "zeroes")] pub mod zeroes; #[cfg(test)] diff --git a/lib/fixed_wide/src/tests.rs b/lib/fixed_wide/src/tests.rs index c171c6b..6fed559 100644 --- a/lib/fixed_wide/src/tests.rs +++ b/lib/fixed_wide/src/tests.rs @@ -1,218 +1,230 @@ -use crate::types::I32F32; use crate::types::I256F256; +use crate::types::I32F32; #[test] -fn you_can_add_numbers(){ - let a=I256F256::from((3i128*2).pow(4)); - assert_eq!(a+a,I256F256::from((3i128*2).pow(4)*2)); +fn you_can_add_numbers() { + let a = I256F256::from((3i128 * 2).pow(4)); + assert_eq!(a + a, I256F256::from((3i128 * 2).pow(4) * 2)); } #[test] -fn to_f32(){ - let a=I256F256::from(1)>>2; - let f:f32=a.into(); - assert_eq!(f,0.25f32); - let f:f32=(-a).into(); - assert_eq!(f,-0.25f32); - let a=I256F256::from(0); - let f:f32=(-a).into(); - assert_eq!(f,0f32); - let a=I256F256::from(237946589723468975i64)<<16; - let f:f32=a.into(); - assert_eq!(f,237946589723468975f32*2.0f32.powi(16)); +fn to_f32() { + let a = I256F256::from(1) >> 2; + let f: f32 = a.into(); + assert_eq!(f, 0.25f32); + let f: f32 = (-a).into(); + assert_eq!(f, -0.25f32); + let a = I256F256::from(0); + let f: f32 = (-a).into(); + assert_eq!(f, 0f32); + let a = I256F256::from(237946589723468975i64) << 16; + let f: f32 = a.into(); + assert_eq!(f, 237946589723468975f32 * 2.0f32.powi(16)); } #[test] -fn to_f64(){ - let a=I256F256::from(1)>>2; - let f:f64=a.into(); - assert_eq!(f,0.25f64); - let f:f64=(-a).into(); - assert_eq!(f,-0.25f64); - let a=I256F256::from(0); - let f:f64=(-a).into(); - assert_eq!(f,0f64); - let a=I256F256::from(237946589723468975i64)<<16; - let f:f64=a.into(); - assert_eq!(f,237946589723468975f64*2.0f64.powi(16)); +fn to_f64() { + let a = I256F256::from(1) >> 2; + let f: f64 = a.into(); + assert_eq!(f, 0.25f64); + let f: f64 = (-a).into(); + assert_eq!(f, -0.25f64); + let a = I256F256::from(0); + let f: f64 = (-a).into(); + assert_eq!(f, 0f64); + let a = I256F256::from(237946589723468975i64) << 16; + let f: f64 = a.into(); + assert_eq!(f, 237946589723468975f64 * 2.0f64.powi(16)); } #[test] -fn from_f32(){ - let a=I256F256::from(1)>>2; - let b:Result=0.25f32.try_into(); - assert_eq!(b,Ok(a)); - let a=I256F256::from(-1)>>2; - let b:Result=(-0.25f32).try_into(); - assert_eq!(b,Ok(a)); - let a=I256F256::from(0); - let b:Result=0.try_into(); - assert_eq!(b,Ok(a)); - let a=I256F256::from(0b101011110101001010101010000000000000000000000000000i64)<<16; - let b:Result=(0b101011110101001010101010000000000000000000000000000u64 as f32*2.0f32.powi(16)).try_into(); - assert_eq!(b,Ok(a)); - //I32F32::MAX into f32 is truncated into this value - let a=I32F32::raw(0b111111111111111111111111000000000000000000000000000000000000000i64); - let b:Result=Into::::into(I32F32::MAX).try_into(); - assert_eq!(b,Ok(a)); - //I32F32::MIN hits a special case since it's not representable as a positive signed integer - //TODO: don't return an overflow because this is technically possible - let a=I32F32::MIN; - let b:Result=Into::::into(I32F32::MIN).try_into(); - assert_eq!(b,Err(crate::fixed::FixedFromFloatError::Overflow)); - //16 is within the 24 bits of float precision - let b:Result=Into::::into(-I32F32::MIN.fix_2()).try_into(); - assert_eq!(b,Err(crate::fixed::FixedFromFloatError::Overflow)); - let b:Result=f32::MIN_POSITIVE.try_into(); - assert_eq!(b,Err(crate::fixed::FixedFromFloatError::Underflow)); - //test many cases - for i in 0..64{ - let a=crate::fixed::Fixed::<2,64>::raw_digit(0b111111111111111111111111000000000000000000000000000000000000000i64)<,_>=f.try_into(); - assert_eq!(b,Ok(a)); - } +fn from_f32() { + let a = I256F256::from(1) >> 2; + let b: Result = 0.25f32.try_into(); + assert_eq!(b, Ok(a)); + let a = I256F256::from(-1) >> 2; + let b: Result = (-0.25f32).try_into(); + assert_eq!(b, Ok(a)); + let a = I256F256::from(0); + let b: Result = 0.try_into(); + assert_eq!(b, Ok(a)); + let a = I256F256::from(0b101011110101001010101010000000000000000000000000000i64) << 16; + let b: Result = (0b101011110101001010101010000000000000000000000000000u64 as f32 + * 2.0f32.powi(16)) + .try_into(); + assert_eq!(b, Ok(a)); + //I32F32::MAX into f32 is truncated into this value + let a = I32F32::raw(0b111111111111111111111111000000000000000000000000000000000000000i64); + let b: Result = Into::::into(I32F32::MAX).try_into(); + assert_eq!(b, Ok(a)); + //I32F32::MIN hits a special case since it's not representable as a positive signed integer + //TODO: don't return an overflow because this is technically possible + let a = I32F32::MIN; + let b: Result = Into::::into(I32F32::MIN).try_into(); + assert_eq!(b, Err(crate::fixed::FixedFromFloatError::Overflow)); + //16 is within the 24 bits of float precision + let b: Result = Into::::into(-I32F32::MIN.fix_2()).try_into(); + assert_eq!(b, Err(crate::fixed::FixedFromFloatError::Overflow)); + let b: Result = f32::MIN_POSITIVE.try_into(); + assert_eq!(b, Err(crate::fixed::FixedFromFloatError::Underflow)); + //test many cases + for i in 0..64 { + let a = crate::fixed::Fixed::<2, 64>::raw_digit( + 0b111111111111111111111111000000000000000000000000000000000000000i64, + ) << i; + let f: f32 = a.into(); + let b: Result, _> = f.try_into(); + assert_eq!(b, Ok(a)); + } } #[test] -fn from_f64(){ - let a=I256F256::from(1)>>2; - let b:Result=0.25f64.try_into(); - assert_eq!(b,Ok(a)); - let a=I256F256::from(-1)>>2; - let b:Result=(-0.25f64).try_into(); - assert_eq!(b,Ok(a)); - let a=I256F256::from(0); - let b:Result=0.try_into(); - assert_eq!(b,Ok(a)); - let a=I256F256::from(0b101011110101001010101010000000000000000000000000000i64)<<16; - let b:Result=(0b101011110101001010101010000000000000000000000000000u64 as f64*2.0f64.powi(16)).try_into(); - assert_eq!(b,Ok(a)); +fn from_f64() { + let a = I256F256::from(1) >> 2; + let b: Result = 0.25f64.try_into(); + assert_eq!(b, Ok(a)); + let a = I256F256::from(-1) >> 2; + let b: Result = (-0.25f64).try_into(); + assert_eq!(b, Ok(a)); + let a = I256F256::from(0); + let b: Result = 0.try_into(); + assert_eq!(b, Ok(a)); + let a = I256F256::from(0b101011110101001010101010000000000000000000000000000i64) << 16; + let b: Result = (0b101011110101001010101010000000000000000000000000000u64 as f64 + * 2.0f64.powi(16)) + .try_into(); + assert_eq!(b, Ok(a)); } #[test] -fn you_can_shr_numbers(){ - let a=I32F32::from(4); - assert_eq!(a>>1,I32F32::from(2)); +fn you_can_shr_numbers() { + let a = I32F32::from(4); + assert_eq!(a >> 1, I32F32::from(2)); } #[test] -fn test_wide_mul(){ - let a=I32F32::ONE; - let aa=a.wide_mul_1_1(a); - assert_eq!(aa,crate::types::I64F64::ONE); +fn test_wide_mul() { + let a = I32F32::ONE; + let aa = a.wide_mul_1_1(a); + assert_eq!(aa, crate::types::I64F64::ONE); } #[test] -fn test_wide_div(){ - let a=I32F32::ONE*4; - let b=I32F32::ONE*2; - let wide_a=a.wide_mul_1_1(I32F32::ONE); - let wide_b=b.wide_mul_1_1(I32F32::ONE); - let ab=a.wide_div_1_1(b); - assert_eq!(ab,crate::types::I64F64::ONE*2); - let wab=wide_a.wide_div_2_1(b); - assert_eq!(wab,crate::fixed::Fixed::<3,96>::ONE*2); - let awb=a.wide_div_1_2(wide_b); - assert_eq!(awb,crate::fixed::Fixed::<3,96>::ONE*2); +fn test_wide_div() { + let a = I32F32::ONE * 4; + let b = I32F32::ONE * 2; + let wide_a = a.wide_mul_1_1(I32F32::ONE); + let wide_b = b.wide_mul_1_1(I32F32::ONE); + let ab = a.wide_div_1_1(b); + assert_eq!(ab, crate::types::I64F64::ONE * 2); + let wab = wide_a.wide_div_2_1(b); + assert_eq!(wab, crate::fixed::Fixed::<3, 96>::ONE * 2); + let awb = a.wide_div_1_2(wide_b); + assert_eq!(awb, crate::fixed::Fixed::<3, 96>::ONE * 2); } #[test] fn test_wide_mul_repeated() { - let a=I32F32::from(2); - let b=I32F32::from(3); + let a = I32F32::from(2); + let b = I32F32::from(3); - let w1=a.wide_mul_1_1(b); - let w2=w1.wide_mul_2_2(w1); - let w3=w2.wide_mul_4_4(w2); + let w1 = a.wide_mul_1_1(b); + let w2 = w1.wide_mul_2_2(w1); + let w3 = w2.wide_mul_4_4(w2); - assert_eq!(w3,I256F256::from((3i128*2).pow(4))); + assert_eq!(w3, I256F256::from((3i128 * 2).pow(4))); } #[test] -fn test_bint(){ - let a=I32F32::ONE; - assert_eq!(a*2,I32F32::from(2)); +fn test_bint() { + let a = I32F32::ONE; + assert_eq!(a * 2, I32F32::from(2)); } #[test] -fn test_fix(){ - assert_eq!(I32F32::ONE.fix_8(),I256F256::ONE); - assert_eq!(I32F32::ONE,I256F256::ONE.fix_1()); - assert_eq!(I32F32::NEG_ONE.fix_8(),I256F256::NEG_ONE); - assert_eq!(I32F32::NEG_ONE,I256F256::NEG_ONE.fix_1()); +fn test_fix() { + assert_eq!(I32F32::ONE.fix_8(), I256F256::ONE); + assert_eq!(I32F32::ONE, I256F256::ONE.fix_1()); + assert_eq!(I32F32::NEG_ONE.fix_8(), I256F256::NEG_ONE); + assert_eq!(I32F32::NEG_ONE, I256F256::NEG_ONE.fix_1()); } #[test] -fn test_sqrt(){ - let a=I32F32::ONE*4; - assert_eq!(a.sqrt(),I32F32::from(2)); +fn test_sqrt() { + let a = I32F32::ONE * 4; + assert_eq!(a.sqrt(), I32F32::from(2)); } #[test] -fn test_sqrt_zero(){ - let a=I32F32::ZERO; - assert_eq!(a.sqrt(),I32F32::ZERO); +fn test_sqrt_zero() { + let a = I32F32::ZERO; + assert_eq!(a.sqrt(), I32F32::ZERO); } #[test] -fn test_sqrt_low(){ - let a=I32F32::HALF; - let b=a.fixed_mul(a); - assert_eq!(b.sqrt(),a); +fn test_sqrt_low() { + let a = I32F32::HALF; + let b = a.fixed_mul(a); + assert_eq!(b.sqrt(), a); } -fn find_equiv_sqrt_via_f64(n:I32F32)->I32F32{ - //GIMME THEM BITS BOY - let &[bits]=n.to_bits().to_bits().digits(); - let ibits=bits as i64; - let f=(ibits as f64)/((1u64<<32) as f64); - let f_ans=f.sqrt(); - let i=(f_ans*((1u64<<32) as f64)) as i64; - let r=I32F32::from_bits(bnum::BInt::<1>::from(i)); - //mimic the behaviour of the algorithm, - //return the result if it truncates to the exact answer - if (r+I32F32::EPSILON).wide_mul_1_1(r+I32F32::EPSILON)==n.wide_mul_1_1(I32F32::ONE){ - return r+I32F32::EPSILON; - } - if (r-I32F32::EPSILON).wide_mul_1_1(r-I32F32::EPSILON)==n.wide_mul_1_1(I32F32::ONE){ - return r-I32F32::EPSILON; - } - return r; +fn find_equiv_sqrt_via_f64(n: I32F32) -> I32F32 { + //GIMME THEM BITS BOY + let &[bits] = n.to_bits().to_bits().digits(); + let ibits = bits as i64; + let f = (ibits as f64) / ((1u64 << 32) as f64); + let f_ans = f.sqrt(); + let i = (f_ans * ((1u64 << 32) as f64)) as i64; + let r = I32F32::from_bits(bnum::BInt::<1>::from(i)); + //mimic the behaviour of the algorithm, + //return the result if it truncates to the exact answer + if (r + I32F32::EPSILON).wide_mul_1_1(r + I32F32::EPSILON) == n.wide_mul_1_1(I32F32::ONE) { + return r + I32F32::EPSILON; + } + if (r - I32F32::EPSILON).wide_mul_1_1(r - I32F32::EPSILON) == n.wide_mul_1_1(I32F32::ONE) { + return r - I32F32::EPSILON; + } + return r; } -fn test_exact(n:I32F32){ - assert_eq!(n.sqrt(),find_equiv_sqrt_via_f64(n)); +fn test_exact(n: I32F32) { + assert_eq!(n.sqrt(), find_equiv_sqrt_via_f64(n)); } #[test] -fn test_sqrt_exact(){ - //43 - for i in 0..((i64::MAX as f32).ln() as u32){ - let n=I32F32::from_bits(bnum::BInt::<1>::from((i as f32).exp() as i64)); - test_exact(n); - } +fn test_sqrt_exact() { + //43 + for i in 0..((i64::MAX as f32).ln() as u32) { + let n = I32F32::from_bits(bnum::BInt::<1>::from((i as f32).exp() as i64)); + test_exact(n); + } } #[test] -fn test_sqrt_max(){ - let a=I32F32::MAX; - test_exact(a); +fn test_sqrt_max() { + let a = I32F32::MAX; + test_exact(a); } #[test] -#[cfg(all(feature="zeroes",not(feature="deferred-division")))] -fn test_zeroes_normal(){ - // (x-1)*(x+1) - // x^2-1 - let zeroes=I32F32::zeroes2(I32F32::NEG_ONE,I32F32::ZERO,I32F32::ONE); - assert_eq!(zeroes,arrayvec::ArrayVec::from_iter([I32F32::NEG_ONE,I32F32::ONE])); - let zeroes=I32F32::zeroes2(I32F32::NEG_ONE*3,I32F32::ONE*2,I32F32::ONE); - assert_eq!(zeroes,arrayvec::ArrayVec::from_iter([I32F32::NEG_ONE*3,I32F32::ONE])); +#[cfg(all(feature = "zeroes", not(feature = "deferred-division")))] +fn test_zeroes_normal() { + // (x-1)*(x+1) + // x^2-1 + let zeroes = I32F32::zeroes2(I32F32::NEG_ONE, I32F32::ZERO, I32F32::ONE); + assert_eq!( + zeroes, + arrayvec::ArrayVec::from_iter([I32F32::NEG_ONE, I32F32::ONE]) + ); + let zeroes = I32F32::zeroes2(I32F32::NEG_ONE * 3, I32F32::ONE * 2, I32F32::ONE); + assert_eq!( + zeroes, + arrayvec::ArrayVec::from_iter([I32F32::NEG_ONE * 3, I32F32::ONE]) + ); } #[test] -#[cfg(all(feature="zeroes",feature="deferred-division"))] -fn test_zeroes_deferred_division(){ - // (x-1)*(x+1) - // x^2-1 - let zeroes=I32F32::zeroes2(I32F32::NEG_ONE,I32F32::ZERO,I32F32::ONE); - assert_eq!( - zeroes, - arrayvec::ArrayVec::from_iter([ - ratio_ops::ratio::Ratio::new(I32F32::ONE*2,I32F32::NEG_ONE*2), - ratio_ops::ratio::Ratio::new(I32F32::ONE*2,I32F32::ONE*2), - ]) - ); +#[cfg(all(feature = "zeroes", feature = "deferred-division"))] +fn test_zeroes_deferred_division() { + // (x-1)*(x+1) + // x^2-1 + let zeroes = I32F32::zeroes2(I32F32::NEG_ONE, I32F32::ZERO, I32F32::ONE); + assert_eq!( + zeroes, + arrayvec::ArrayVec::from_iter([ + ratio_ops::ratio::Ratio::new(I32F32::ONE * 2, I32F32::NEG_ONE * 2), + ratio_ops::ratio::Ratio::new(I32F32::ONE * 2, I32F32::ONE * 2), + ]) + ); } diff --git a/lib/fixed_wide/src/types.rs b/lib/fixed_wide/src/types.rs index 8694435..6bb8f45 100644 --- a/lib/fixed_wide/src/types.rs +++ b/lib/fixed_wide/src/types.rs @@ -1,4 +1,4 @@ -pub type I32F32=crate::fixed::Fixed<1,32>; -pub type I64F64=crate::fixed::Fixed<2,64>; -pub type I128F128=crate::fixed::Fixed<4,128>; -pub type I256F256=crate::fixed::Fixed<8,256>; +pub type I32F32 = crate::fixed::Fixed<1, 32>; +pub type I64F64 = crate::fixed::Fixed<2, 64>; +pub type I128F128 = crate::fixed::Fixed<4, 128>; +pub type I256F256 = crate::fixed::Fixed<8, 256>; diff --git a/lib/fixed_wide/src/zeroes.rs b/lib/fixed_wide/src/zeroes.rs index 7f1dbc9..95ac991 100644 --- a/lib/fixed_wide/src/zeroes.rs +++ b/lib/fixed_wide/src/zeroes.rs @@ -2,45 +2,63 @@ use crate::fixed::Fixed; use arrayvec::ArrayVec; use std::cmp::Ordering; -macro_rules! impl_zeroes{ - ($n:expr)=>{ - impl Fixed<$n,{$n*32}>{ - #[inline] - pub fn zeroes2(a0:Self,a1:Self,a2:Self)->ArrayVec<::Output,2>{ - let a2pos=match a2.cmp(&Self::ZERO){ - Ordering::Greater=>true, - Ordering::Equal=>return ArrayVec::from_iter(Self::zeroes1(a0,a1).into_iter()), - Ordering::Less=>false, - }; - let radicand=a1*a1-a2*a0*4; - match radicand.cmp(&::Output::ZERO){ - Ordering::Greater=>{ - paste::item!{ - let planar_radicand=radicand.sqrt().[](); - } - //sort roots ascending and avoid taking the difference of large numbers - let zeroes=match (a2pos,Self::ZERO[(-a1-planar_radicand)/(a2*2),(a0*2)/(-a1-planar_radicand)], - (true, false)=>[(a0*2)/(-a1+planar_radicand),(-a1+planar_radicand)/(a2*2)], - (false,true )=>[(a0*2)/(-a1-planar_radicand),(-a1-planar_radicand)/(a2*2)], - (false,false)=>[(-a1+planar_radicand)/(a2*2),(a0*2)/(-a1+planar_radicand)], - }; - ArrayVec::from_iter(zeroes) - }, - Ordering::Equal=>ArrayVec::from_iter([(a1)/(a2*-2)]), - Ordering::Less=>ArrayVec::new_const(), - } - } - #[inline] - pub fn zeroes1(a0:Self,a1:Self)->ArrayVec<::Output,1>{ - if a1==Self::ZERO{ - ArrayVec::new_const() - }else{ - ArrayVec::from_iter([(-a0)/(a1)]) - } - } - } - }; +macro_rules! impl_zeroes { + ($n:expr) => { + impl Fixed<$n, { $n * 32 }> { + #[inline] + pub fn zeroes2( + a0: Self, + a1: Self, + a2: Self, + ) -> ArrayVec<::Output, 2> { + let a2pos = match a2.cmp(&Self::ZERO) { + Ordering::Greater => true, + Ordering::Equal => { + return ArrayVec::from_iter(Self::zeroes1(a0, a1).into_iter()) + } + Ordering::Less => false, + }; + let radicand = a1 * a1 - a2 * a0 * 4; + match radicand.cmp(&::Output::ZERO) { + Ordering::Greater => { + paste::item! { + let planar_radicand=radicand.sqrt().[](); + } + //sort roots ascending and avoid taking the difference of large numbers + let zeroes = match (a2pos, Self::ZERO < a1) { + (true, true) => [ + (-a1 - planar_radicand) / (a2 * 2), + (a0 * 2) / (-a1 - planar_radicand), + ], + (true, false) => [ + (a0 * 2) / (-a1 + planar_radicand), + (-a1 + planar_radicand) / (a2 * 2), + ], + (false, true) => [ + (a0 * 2) / (-a1 - planar_radicand), + (-a1 - planar_radicand) / (a2 * 2), + ], + (false, false) => [ + (-a1 + planar_radicand) / (a2 * 2), + (a0 * 2) / (-a1 + planar_radicand), + ], + }; + ArrayVec::from_iter(zeroes) + } + Ordering::Equal => ArrayVec::from_iter([(a1) / (a2 * -2)]), + Ordering::Less => ArrayVec::new_const(), + } + } + #[inline] + pub fn zeroes1(a0: Self, a1: Self) -> ArrayVec<::Output, 1> { + if a1 == Self::ZERO { + ArrayVec::new_const() + } else { + ArrayVec::from_iter([(-a0) / (a1)]) + } + } + } + }; } impl_zeroes!(1); impl_zeroes!(2); diff --git a/lib/linear_ops/src/lib.rs b/lib/linear_ops/src/lib.rs index 628ca4e..8a84184 100644 --- a/lib/linear_ops/src/lib.rs +++ b/lib/linear_ops/src/lib.rs @@ -1,9 +1,9 @@ mod macros; +pub mod matrix; pub mod types; pub mod vector; -pub mod matrix; -#[cfg(feature="named-fields")] +#[cfg(feature = "named-fields")] mod named; #[cfg(test)] diff --git a/lib/linear_ops/src/macros/fixed_wide.rs b/lib/linear_ops/src/macros/fixed_wide.rs index 199e7fb..d93873b 100644 --- a/lib/linear_ops/src/macros/fixed_wide.rs +++ b/lib/linear_ops/src/macros/fixed_wide.rs @@ -1,79 +1,328 @@ #[doc(hidden)] #[macro_export(local_inner_macros)] macro_rules! impl_fixed_wide_vector_not_const_generic { - ( + ( (), $n:expr ) => { - impl Vector>{ - #[inline] - pub fn length(self)-> as core::ops::Mul>::Output{ - self.length_squared().sqrt_unchecked() - } - #[inline] - pub fn with_length(self,length:U)-> as core::ops::Div< as core::ops::Mul>::Output>>::Output - where - fixed_wide::fixed::Fixed<$n,{$n*32}>:core::ops::Mul, - U:Copy, - V:core::ops::Div< as core::ops::Mul>::Output>, - { - self*length/self.length() - } - } - }; + impl Vector> { + #[inline] + pub fn length( + self, + ) -> as core::ops::Mul>::Output { + self.length_squared().sqrt_unchecked() + } + #[inline] + pub fn with_length( + self, + length: U, + ) -> as core::ops::Div< + as core::ops::Mul>::Output, + >>::Output + where + fixed_wide::fixed::Fixed<$n, { $n * 32 }>: core::ops::Mul, + U: Copy, + V: core::ops::Div< + as core::ops::Mul>::Output, + >, + { + self * length / self.length() + } + } + }; } #[doc(hidden)] #[macro_export(local_inner_macros)] macro_rules! macro_4 { - ( $macro: ident, $any:tt ) => { - $crate::macro_repeated!($macro,$any,1,2,3,4); - } + ( $macro: ident, $any:tt ) => { + $crate::macro_repeated!($macro, $any, 1, 2, 3, 4); + }; } #[doc(hidden)] #[macro_export(local_inner_macros)] macro_rules! impl_fixed_wide_vector { - () => { - $crate::macro_4!(impl_fixed_wide_vector_not_const_generic,()); - // I LOVE NOT BEING ABLE TO USE CONST GENERICS - $crate::macro_repeated!( - impl_fix_not_const_generic,(), - (1,1),(2,1),(3,1),(4,1),(5,1),(6,1),(7,1),(8,1),(9,1),(10,1),(11,1),(12,1),(13,1),(14,1),(15,1),(16,1), - (1,2),(2,2),(3,2),(4,2),(5,2),(6,2),(7,2),(8,2),(9,2),(10,2),(11,2),(12,2),(13,2),(14,2),(15,2),(16,2), - (1,3),(2,3),(3,3),(4,3),(5,3),(6,3),(7,3),(8,3),(9,3),(10,3),(11,3),(12,3),(13,3),(14,3),(15,3),(16,3), - (1,4),(2,4),(3,4),(4,4),(5,4),(6,4),(7,4),(8,4),(9,4),(10,4),(11,4),(12,4),(13,4),(14,4),(15,4),(16,4), - (1,5),(2,5),(3,5),(4,5),(5,5),(6,5),(7,5),(8,5),(9,5),(10,5),(11,5),(12,5),(13,5),(14,5),(15,5),(16,5), - (1,6),(2,6),(3,6),(4,6),(5,6),(6,6),(7,6),(8,6),(9,6),(10,6),(11,6),(12,6),(13,6),(14,6),(15,6),(16,6), - (1,7),(2,7),(3,7),(4,7),(5,7),(6,7),(7,7),(8,7),(9,7),(10,7),(11,7),(12,7),(13,7),(14,7),(15,7),(16,7), - (1,8),(2,8),(3,8),(4,8),(5,8),(6,8),(7,8),(8,8),(9,8),(10,8),(11,8),(12,8),(13,8),(14,8),(15,8),(16,8), - (1,9),(2,9),(3,9),(4,9),(5,9),(6,9),(7,9),(8,9),(9,9),(10,9),(11,9),(12,9),(13,9),(14,9),(15,9),(16,9), - (1,10),(2,10),(3,10),(4,10),(5,10),(6,10),(7,10),(8,10),(9,10),(10,10),(11,10),(12,10),(13,10),(14,10),(15,10),(16,10), - (1,11),(2,11),(3,11),(4,11),(5,11),(6,11),(7,11),(8,11),(9,11),(10,11),(11,11),(12,11),(13,11),(14,11),(15,11),(16,11), - (1,12),(2,12),(3,12),(4,12),(5,12),(6,12),(7,12),(8,12),(9,12),(10,12),(11,12),(12,12),(13,12),(14,12),(15,12),(16,12), - (1,13),(2,13),(3,13),(4,13),(5,13),(6,13),(7,13),(8,13),(9,13),(10,13),(11,13),(12,13),(13,13),(14,13),(15,13),(16,13), - (1,14),(2,14),(3,14),(4,14),(5,14),(6,14),(7,14),(8,14),(9,14),(10,14),(11,14),(12,14),(13,14),(14,14),(15,14),(16,14), - (1,15),(2,15),(3,15),(4,15),(5,15),(6,15),(7,15),(8,15),(9,15),(10,15),(11,15),(12,15),(13,15),(14,15),(15,15),(16,15), - (1,16),(2,16),(3,16),(4,16),(5,16),(6,16),(7,16),(8,16),(9,16),(10,16),(11,16),(12,16),(13,16),(14,16),(15,16),(16,16) - ); - }; + () => { + $crate::macro_4!(impl_fixed_wide_vector_not_const_generic, ()); + // I LOVE NOT BEING ABLE TO USE CONST GENERICS + $crate::macro_repeated!( + impl_fix_not_const_generic, + (), + (1, 1), + (2, 1), + (3, 1), + (4, 1), + (5, 1), + (6, 1), + (7, 1), + (8, 1), + (9, 1), + (10, 1), + (11, 1), + (12, 1), + (13, 1), + (14, 1), + (15, 1), + (16, 1), + (1, 2), + (2, 2), + (3, 2), + (4, 2), + (5, 2), + (6, 2), + (7, 2), + (8, 2), + (9, 2), + (10, 2), + (11, 2), + (12, 2), + (13, 2), + (14, 2), + (15, 2), + (16, 2), + (1, 3), + (2, 3), + (3, 3), + (4, 3), + (5, 3), + (6, 3), + (7, 3), + (8, 3), + (9, 3), + (10, 3), + (11, 3), + (12, 3), + (13, 3), + (14, 3), + (15, 3), + (16, 3), + (1, 4), + (2, 4), + (3, 4), + (4, 4), + (5, 4), + (6, 4), + (7, 4), + (8, 4), + (9, 4), + (10, 4), + (11, 4), + (12, 4), + (13, 4), + (14, 4), + (15, 4), + (16, 4), + (1, 5), + (2, 5), + (3, 5), + (4, 5), + (5, 5), + (6, 5), + (7, 5), + (8, 5), + (9, 5), + (10, 5), + (11, 5), + (12, 5), + (13, 5), + (14, 5), + (15, 5), + (16, 5), + (1, 6), + (2, 6), + (3, 6), + (4, 6), + (5, 6), + (6, 6), + (7, 6), + (8, 6), + (9, 6), + (10, 6), + (11, 6), + (12, 6), + (13, 6), + (14, 6), + (15, 6), + (16, 6), + (1, 7), + (2, 7), + (3, 7), + (4, 7), + (5, 7), + (6, 7), + (7, 7), + (8, 7), + (9, 7), + (10, 7), + (11, 7), + (12, 7), + (13, 7), + (14, 7), + (15, 7), + (16, 7), + (1, 8), + (2, 8), + (3, 8), + (4, 8), + (5, 8), + (6, 8), + (7, 8), + (8, 8), + (9, 8), + (10, 8), + (11, 8), + (12, 8), + (13, 8), + (14, 8), + (15, 8), + (16, 8), + (1, 9), + (2, 9), + (3, 9), + (4, 9), + (5, 9), + (6, 9), + (7, 9), + (8, 9), + (9, 9), + (10, 9), + (11, 9), + (12, 9), + (13, 9), + (14, 9), + (15, 9), + (16, 9), + (1, 10), + (2, 10), + (3, 10), + (4, 10), + (5, 10), + (6, 10), + (7, 10), + (8, 10), + (9, 10), + (10, 10), + (11, 10), + (12, 10), + (13, 10), + (14, 10), + (15, 10), + (16, 10), + (1, 11), + (2, 11), + (3, 11), + (4, 11), + (5, 11), + (6, 11), + (7, 11), + (8, 11), + (9, 11), + (10, 11), + (11, 11), + (12, 11), + (13, 11), + (14, 11), + (15, 11), + (16, 11), + (1, 12), + (2, 12), + (3, 12), + (4, 12), + (5, 12), + (6, 12), + (7, 12), + (8, 12), + (9, 12), + (10, 12), + (11, 12), + (12, 12), + (13, 12), + (14, 12), + (15, 12), + (16, 12), + (1, 13), + (2, 13), + (3, 13), + (4, 13), + (5, 13), + (6, 13), + (7, 13), + (8, 13), + (9, 13), + (10, 13), + (11, 13), + (12, 13), + (13, 13), + (14, 13), + (15, 13), + (16, 13), + (1, 14), + (2, 14), + (3, 14), + (4, 14), + (5, 14), + (6, 14), + (7, 14), + (8, 14), + (9, 14), + (10, 14), + (11, 14), + (12, 14), + (13, 14), + (14, 14), + (15, 14), + (16, 14), + (1, 15), + (2, 15), + (3, 15), + (4, 15), + (5, 15), + (6, 15), + (7, 15), + (8, 15), + (9, 15), + (10, 15), + (11, 15), + (12, 15), + (13, 15), + (14, 15), + (15, 15), + (16, 15), + (1, 16), + (2, 16), + (3, 16), + (4, 16), + (5, 16), + (6, 16), + (7, 16), + (8, 16), + (9, 16), + (10, 16), + (11, 16), + (12, 16), + (13, 16), + (14, 16), + (15, 16), + (16, 16) + ); + }; } #[doc(hidden)] #[macro_export(local_inner_macros)] -macro_rules! impl_fix_not_const_generic{ - ( +macro_rules! impl_fix_not_const_generic { + ( (), ($lhs:expr,$rhs:expr) - )=>{ - impl Vector> - { - paste::item!{ - #[inline] - pub fn [](self)->Vector>{ - self.map(|t|t.[]()) - } - } - } - } + ) => { + impl Vector> { + paste::item! { + #[inline] + pub fn [](self)->Vector>{ + self.map(|t|t.[]()) + } + } + } + }; } diff --git a/lib/linear_ops/src/macros/matrix.rs b/lib/linear_ops/src/macros/matrix.rs index 69db874..5f9f6c3 100644 --- a/lib/linear_ops/src/macros/matrix.rs +++ b/lib/linear_ops/src/macros/matrix.rs @@ -1,219 +1,217 @@ #[doc(hidden)] #[macro_export(local_inner_macros)] macro_rules! impl_matrix { - () => { - impl Matrix{ - #[inline(always)] - pub const fn new(array:[[T;Y];X])->Self{ - Self{array} - } - #[inline(always)] - pub fn to_array(self)->[[T;Y];X]{ - self.array - } - #[inline] - pub fn from_cols(cols:[Vector;X])->Self - { - Matrix::new( - cols.map(|col|col.array), - ) - } - #[inline] - pub fn map(self,f:F)->Matrix - where - F:Fn(T)->U - { - Matrix::new( - self.array.map(|inner|inner.map(&f)), - ) - } - #[inline] - pub fn transpose(self)->Matrix{ - //how did I think of this - let mut array_of_iterators=self.array.map(|axis|axis.into_iter()); - Matrix::new( - core::array::from_fn(|_| - array_of_iterators.each_mut().map(|iter| - iter.next().unwrap() - ) - ) - ) - } - #[inline] - // old (list of rows) MatY.MatX = MatY - // new (list of columns) MatX.MatZ = MatZ - pub fn dot(self,rhs:Matrix)->Matrix - where - T:core::ops::Mul+Copy, - V:core::iter::Sum, - U:Copy, - { - let mut array_of_iterators=self.array.map(|axis|axis.into_iter().cycle()); - Matrix{ - array:rhs.array.map(|rhs_axis| - core::array::from_fn(|_| - array_of_iterators - .iter_mut() - .zip(rhs_axis.iter()) - .map(|(lhs_iter,&rhs_value)| - lhs_iter.next().unwrap()*rhs_value - ).sum() - ) - ) - } - } - #[inline] - // MatX.VecY = VecX - pub fn transform_vector(self,rhs:Vector)->Vector - where - T:core::ops::Mul, - V:core::iter::Sum, - U:Copy, - { - let mut array_of_iterators=self.array.map(|axis|axis.into_iter()); - Vector::new( - core::array::from_fn(|_| - array_of_iterators - .iter_mut() - .zip(rhs.array.iter()) - .map(|(lhs_iter,&rhs_value)| - lhs_iter.next().unwrap()*rhs_value - ).sum() - ) - ) - } - } - impl Matrix - where - T:Copy - { - #[inline(always)] - pub const fn from_value(value:T)->Self{ - Self::new([[value;Y];X]) - } - } + () => { + impl Matrix { + #[inline(always)] + pub const fn new(array: [[T; Y]; X]) -> Self { + Self { array } + } + #[inline(always)] + pub fn to_array(self) -> [[T; Y]; X] { + self.array + } + #[inline] + pub fn from_cols(cols: [Vector; X]) -> Self { + Matrix::new(cols.map(|col| col.array)) + } + #[inline] + pub fn map(self, f: F) -> Matrix + where + F: Fn(T) -> U, + { + Matrix::new(self.array.map(|inner| inner.map(&f))) + } + #[inline] + pub fn transpose(self) -> Matrix { + //how did I think of this + let mut array_of_iterators = self.array.map(|axis| axis.into_iter()); + Matrix::new(core::array::from_fn(|_| { + array_of_iterators + .each_mut() + .map(|iter| iter.next().unwrap()) + })) + } + #[inline] + // old (list of rows) MatY.MatX = MatY + // new (list of columns) MatX.MatZ = MatZ + pub fn dot(self, rhs: Matrix) -> Matrix + where + T: core::ops::Mul + Copy, + V: core::iter::Sum, + U: Copy, + { + let mut array_of_iterators = self.array.map(|axis| axis.into_iter().cycle()); + Matrix { + array: rhs.array.map(|rhs_axis| { + core::array::from_fn(|_| { + array_of_iterators + .iter_mut() + .zip(rhs_axis.iter()) + .map(|(lhs_iter, &rhs_value)| lhs_iter.next().unwrap() * rhs_value) + .sum() + }) + }), + } + } + #[inline] + // MatX.VecY = VecX + pub fn transform_vector(self, rhs: Vector) -> Vector + where + T: core::ops::Mul, + V: core::iter::Sum, + U: Copy, + { + let mut array_of_iterators = self.array.map(|axis| axis.into_iter()); + Vector::new(core::array::from_fn(|_| { + array_of_iterators + .iter_mut() + .zip(rhs.array.iter()) + .map(|(lhs_iter, &rhs_value)| lhs_iter.next().unwrap() * rhs_value) + .sum() + })) + } + } + impl Matrix + where + T: Copy, + { + #[inline(always)] + pub const fn from_value(value: T) -> Self { + Self::new([[value; Y]; X]) + } + } - impl Default for Matrix{ - #[inline] - fn default()->Self{ - Self::new( - core::array::from_fn(|_|core::array::from_fn(|_|Default::default())) - ) - } - } + impl Default for Matrix { + #[inline] + fn default() -> Self { + Self::new(core::array::from_fn(|_| { + core::array::from_fn(|_| Default::default()) + })) + } + } - impl core::fmt::Display for Matrix{ - #[inline] - fn fmt(&self,f:&mut core::fmt::Formatter)->Result<(),core::fmt::Error>{ - for col in &self.array[0..X]{ - core::write!(f,"\n")?; - for elem in &col[0..Y-1]{ - core::write!(f,"{}, ",elem)?; - } - // assume we will be using matrices of size 1x1 or greater - core::write!(f,"{}",col.last().unwrap())?; - } - Ok(()) - } - } + impl core::fmt::Display + for Matrix + { + #[inline] + fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> { + for col in &self.array[0..X] { + core::write!(f, "\n")?; + for elem in &col[0..Y - 1] { + core::write!(f, "{}, ", elem)?; + } + // assume we will be using matrices of size 1x1 or greater + core::write!(f, "{}", col.last().unwrap())?; + } + Ok(()) + } + } - impl core::ops::Mul> for Matrix - where - T:core::ops::Mul+Copy, - V:core::iter::Sum, - U:Copy, - { - type Output=Matrix; - #[inline] - fn mul(self,rhs:Matrix)->Self::Output{ - self.dot(rhs) - } - } - impl core::ops::Mul> for Matrix - where - T:core::ops::Mul, - V:core::iter::Sum, - U:Copy, - { - type Output=Vector; - #[inline] - fn mul(self,rhs:Vector)->Self::Output{ - self.transform_vector(rhs) - } - } - #[cfg(feature="deferred-division")] - $crate::impl_matrix_deferred_division!(); - } + impl + core::ops::Mul> for Matrix + where + T: core::ops::Mul + Copy, + V: core::iter::Sum, + U: Copy, + { + type Output = Matrix; + #[inline] + fn mul(self, rhs: Matrix) -> Self::Output { + self.dot(rhs) + } + } + impl core::ops::Mul> + for Matrix + where + T: core::ops::Mul, + V: core::iter::Sum, + U: Copy, + { + type Output = Vector; + #[inline] + fn mul(self, rhs: Vector) -> Self::Output { + self.transform_vector(rhs) + } + } + #[cfg(feature = "deferred-division")] + $crate::impl_matrix_deferred_division!(); + }; } #[doc(hidden)] #[macro_export(local_inner_macros)] macro_rules! impl_matrix_deferred_division { - () => { - impl,U:Copy,V> ratio_ops::ratio::Divide for Matrix{ - type Output=Matrix; - #[inline] - fn divide(self,rhs:U)->Self::Output{ - self.map(|t|t.divide(rhs)) - } - } - impl core::ops::Div for Matrix{ - type Output=ratio_ops::ratio::Ratio,U>; - #[inline] - fn div(self,rhs:U)->Self::Output{ - ratio_ops::ratio::Ratio::new(self,rhs) - } - } - } + () => { + impl< + const X: usize, + const Y: usize, + T: ratio_ops::ratio::Divide, + U: Copy, + V, + > ratio_ops::ratio::Divide for Matrix + { + type Output = Matrix; + #[inline] + fn divide(self, rhs: U) -> Self::Output { + self.map(|t| t.divide(rhs)) + } + } + impl core::ops::Div for Matrix { + type Output = ratio_ops::ratio::Ratio, U>; + #[inline] + fn div(self, rhs: U) -> Self::Output { + ratio_ops::ratio::Ratio::new(self, rhs) + } + } + }; } #[doc(hidden)] #[macro_export(local_inner_macros)] macro_rules! impl_matrix_extend { - ( $x: expr, $y: expr ) => { - impl Matrix<$x,$y,T>{ - #[inline] - pub fn extend_column(self,value:Vector<$y,T>)->Matrix<{$x+1},$y,T>{ - let mut iter=self.array.into_iter().chain(core::iter::once(value.array)); - Matrix::new( - core::array::from_fn(|_|iter.next().unwrap()), - ) - } - #[inline] - pub fn extend_row(self,value:Vector<$x,T>)->Matrix<$x,{$y+1},T>{ - let mut iter_rows=value.array.into_iter(); - Matrix::new( - self.array.map(|axis|{ - let mut elements_iter=axis.into_iter().chain(core::iter::once(iter_rows.next().unwrap())); - core::array::from_fn(|_|elements_iter.next().unwrap()) - }) - ) - } - } - } + ( $x: expr, $y: expr ) => { + impl Matrix<$x, $y, T> { + #[inline] + pub fn extend_column(self, value: Vector<$y, T>) -> Matrix<{ $x + 1 }, $y, T> { + let mut iter = self.array.into_iter().chain(core::iter::once(value.array)); + Matrix::new(core::array::from_fn(|_| iter.next().unwrap())) + } + #[inline] + pub fn extend_row(self, value: Vector<$x, T>) -> Matrix<$x, { $y + 1 }, T> { + let mut iter_rows = value.array.into_iter(); + Matrix::new(self.array.map(|axis| { + let mut elements_iter = axis + .into_iter() + .chain(core::iter::once(iter_rows.next().unwrap())); + core::array::from_fn(|_| elements_iter.next().unwrap()) + })) + } + } + }; } #[doc(hidden)] #[macro_export(local_inner_macros)] macro_rules! impl_matrix_named_fields_shape { - ( + ( ($struct_outer:ident, $size_outer: expr), ($size_inner: expr) ) => { - impl core::ops::Deref for Matrix<$size_outer,$size_inner,T>{ - type Target=$struct_outer>; - #[inline] - fn deref(&self)->&Self::Target{ - unsafe{core::mem::transmute(&self.array)} - } - } - impl core::ops::DerefMut for Matrix<$size_outer,$size_inner,T>{ - #[inline] - fn deref_mut(&mut self)->&mut Self::Target{ - unsafe{core::mem::transmute(&mut self.array)} - } - } - } + impl core::ops::Deref for Matrix<$size_outer, $size_inner, T> { + type Target = $struct_outer>; + #[inline] + fn deref(&self) -> &Self::Target { + unsafe { core::mem::transmute(&self.array) } + } + } + impl core::ops::DerefMut for Matrix<$size_outer, $size_inner, T> { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { core::mem::transmute(&mut self.array) } + } + } + }; } #[doc(hidden)] @@ -241,32 +239,44 @@ macro_rules! impl_matrix_named_fields { #[doc(hidden)] #[macro_export(local_inner_macros)] macro_rules! impl_matrix_3x3 { - ()=>{ - impl Matrix<3,3,T> - where - //cross - T:core::ops::Mul+Copy, - T2:core::ops::Sub, - //dot - T:core::ops::Mul<::Output,Output=T3>, - T3:core::iter::Sum, - { - pub fn det(self)->T3{ - self.x_axis.dot(self.y_axis.cross(self.z_axis)) - } - } - impl Matrix<3,3,T> - where - T:core::ops::Mul+Copy, - T2:core::ops::Sub, - { - pub fn adjugate(self)->Matrix<3,3,::Output>{ - Matrix::new([ - [self.y_axis.y*self.z_axis.z-self.y_axis.z*self.z_axis.y,self.x_axis.z*self.z_axis.y-self.x_axis.y*self.z_axis.z,self.x_axis.y*self.y_axis.z-self.x_axis.z*self.y_axis.y], - [self.y_axis.z*self.z_axis.x-self.y_axis.x*self.z_axis.z,self.x_axis.x*self.z_axis.z-self.x_axis.z*self.z_axis.x,self.x_axis.z*self.y_axis.x-self.x_axis.x*self.y_axis.z], - [self.y_axis.x*self.z_axis.y-self.y_axis.y*self.z_axis.x,self.x_axis.y*self.z_axis.x-self.x_axis.x*self.z_axis.y,self.x_axis.x*self.y_axis.y-self.x_axis.y*self.y_axis.x], - ]) - } - } - } + () => { + impl Matrix<3, 3, T> + where + //cross + T: core::ops::Mul + Copy, + T2: core::ops::Sub, + //dot + T: core::ops::Mul<::Output, Output = T3>, + T3: core::iter::Sum, + { + pub fn det(self) -> T3 { + self.x_axis.dot(self.y_axis.cross(self.z_axis)) + } + } + impl Matrix<3, 3, T> + where + T: core::ops::Mul + Copy, + T2: core::ops::Sub, + { + pub fn adjugate(self) -> Matrix<3, 3, ::Output> { + Matrix::new([ + [ + self.y_axis.y * self.z_axis.z - self.y_axis.z * self.z_axis.y, + self.x_axis.z * self.z_axis.y - self.x_axis.y * self.z_axis.z, + self.x_axis.y * self.y_axis.z - self.x_axis.z * self.y_axis.y, + ], + [ + self.y_axis.z * self.z_axis.x - self.y_axis.x * self.z_axis.z, + self.x_axis.x * self.z_axis.z - self.x_axis.z * self.z_axis.x, + self.x_axis.z * self.y_axis.x - self.x_axis.x * self.y_axis.z, + ], + [ + self.y_axis.x * self.z_axis.y - self.y_axis.y * self.z_axis.x, + self.x_axis.y * self.z_axis.x - self.x_axis.x * self.z_axis.y, + self.x_axis.x * self.y_axis.y - self.x_axis.y * self.y_axis.x, + ], + ]) + } + } + }; } diff --git a/lib/linear_ops/src/macros/mod.rs b/lib/linear_ops/src/macros/mod.rs index e0a9d02..e789de7 100644 --- a/lib/linear_ops/src/macros/mod.rs +++ b/lib/linear_ops/src/macros/mod.rs @@ -1,8 +1,8 @@ pub mod common; -pub mod vector; pub mod matrix; +pub mod vector; -#[cfg(feature="fixed-wide")] +#[cfg(feature = "fixed-wide")] pub mod fixed_wide; #[doc(hidden)] diff --git a/lib/linear_ops/src/macros/vector.rs b/lib/linear_ops/src/macros/vector.rs index d3beecf..48e2dc2 100644 --- a/lib/linear_ops/src/macros/vector.rs +++ b/lib/linear_ops/src/macros/vector.rs @@ -1,357 +1,368 @@ #[doc(hidden)] #[macro_export(local_inner_macros)] macro_rules! impl_vector { - () => { - impl Vector{ - #[inline(always)] - pub const fn new(array:[T;N])->Self{ - Self{array} - } - #[inline(always)] - pub fn to_array(self)->[T;N]{ - self.array - } - #[inline] - pub fn map(self,f:F)->Vector - where - F:Fn(T)->U - { - Vector::new( - self.array.map(f) - ) - } - #[inline] - pub fn map_zip(self,other:Vector,f:F)->Vector - where - F:Fn((T,U))->V, - { - let mut iter=self.array.into_iter().zip(other.array); - Vector::new( - core::array::from_fn(|_|f(iter.next().unwrap())), - ) - } - } - impl Vector{ - #[inline(always)] - pub const fn from_value(value:T)->Self{ - Self::new([value;N]) - } - } + () => { + impl Vector { + #[inline(always)] + pub const fn new(array: [T; N]) -> Self { + Self { array } + } + #[inline(always)] + pub fn to_array(self) -> [T; N] { + self.array + } + #[inline] + pub fn map(self, f: F) -> Vector + where + F: Fn(T) -> U, + { + Vector::new(self.array.map(f)) + } + #[inline] + pub fn map_zip(self, other: Vector, f: F) -> Vector + where + F: Fn((T, U)) -> V, + { + let mut iter = self.array.into_iter().zip(other.array); + Vector::new(core::array::from_fn(|_| f(iter.next().unwrap()))) + } + } + impl Vector { + #[inline(always)] + pub const fn from_value(value: T) -> Self { + Self::new([value; N]) + } + } - impl Default for Vector{ - #[inline] - fn default()->Self{ - Self::new( - core::array::from_fn(|_|Default::default()) - ) - } - } + impl Default for Vector { + #[inline] + fn default() -> Self { + Self::new(core::array::from_fn(|_| Default::default())) + } + } - impl core::fmt::Display for Vector{ - #[inline] - fn fmt(&self,f:&mut core::fmt::Formatter)->Result<(),core::fmt::Error>{ - for elem in &self.array[0..N-1]{ - core::write!(f,"{}, ",elem)?; - } - // assume we will be using vectors of length 1 or greater - core::write!(f,"{}",self.array.last().unwrap()) - } - } + impl core::fmt::Display for Vector { + #[inline] + fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> { + for elem in &self.array[0..N - 1] { + core::write!(f, "{}, ", elem)?; + } + // assume we will be using vectors of length 1 or greater + core::write!(f, "{}", self.array.last().unwrap()) + } + } - impl Vector{ - #[inline] - pub fn min(self,rhs:Self)->Self{ - self.map_zip(rhs,|(a,b)|a.min(b)) - } - #[inline] - pub fn max(self,rhs:Self)->Self{ - self.map_zip(rhs,|(a,b)|a.max(b)) - } - #[inline] - pub fn cmp(self,rhs:Self)->Vector{ - self.map_zip(rhs,|(a,b)|a.cmp(&b)) - } - #[inline] - pub fn lt(self,rhs:Self)->Vector{ - self.map_zip(rhs,|(a,b)|a.lt(&b)) - } - #[inline] - pub fn gt(self,rhs:Self)->Vector{ - self.map_zip(rhs,|(a,b)|a.gt(&b)) - } - #[inline] - pub fn ge(self,rhs:Self)->Vector{ - self.map_zip(rhs,|(a,b)|a.ge(&b)) - } - #[inline] - pub fn le(self,rhs:Self)->Vector{ - self.map_zip(rhs,|(a,b)|a.le(&b)) - } - } + impl Vector { + #[inline] + pub fn min(self, rhs: Self) -> Self { + self.map_zip(rhs, |(a, b)| a.min(b)) + } + #[inline] + pub fn max(self, rhs: Self) -> Self { + self.map_zip(rhs, |(a, b)| a.max(b)) + } + #[inline] + pub fn cmp(self, rhs: Self) -> Vector { + self.map_zip(rhs, |(a, b)| a.cmp(&b)) + } + #[inline] + pub fn lt(self, rhs: Self) -> Vector { + self.map_zip(rhs, |(a, b)| a.lt(&b)) + } + #[inline] + pub fn gt(self, rhs: Self) -> Vector { + self.map_zip(rhs, |(a, b)| a.gt(&b)) + } + #[inline] + pub fn ge(self, rhs: Self) -> Vector { + self.map_zip(rhs, |(a, b)| a.ge(&b)) + } + #[inline] + pub fn le(self, rhs: Self) -> Vector { + self.map_zip(rhs, |(a, b)| a.le(&b)) + } + } - impl Vector{ - #[inline] - pub fn all(&self)->bool{ - self.array==[true;N] - } - #[inline] - pub fn any(&self)->bool{ - self.array!=[false;N] - } - } + impl Vector { + #[inline] + pub fn all(&self) -> bool { + self.array == [true; N] + } + #[inline] + pub fn any(&self) -> bool { + self.array != [false; N] + } + } - impl,V> core::ops::Neg for Vector{ - type Output=Vector; - #[inline] - fn neg(self)->Self::Output{ - Vector::new( - self.array.map(|t|-t) - ) - } - } + impl, V> core::ops::Neg for Vector { + type Output = Vector; + #[inline] + fn neg(self) -> Self::Output { + Vector::new(self.array.map(|t| -t)) + } + } - impl Vector - { - #[inline] - pub fn dot(self,rhs:Vector)->V - where - T:core::ops::Mul, - V:core::iter::Sum, - { - self.array.into_iter().zip(rhs.array).map(|(a,b)|a*b).sum() - } - } + impl Vector { + #[inline] + pub fn dot(self, rhs: Vector) -> V + where + T: core::ops::Mul, + V: core::iter::Sum, + { + self.array + .into_iter() + .zip(rhs.array) + .map(|(a, b)| a * b) + .sum() + } + } - impl Vector - where - T:core::ops::Mul+Copy, - V:core::iter::Sum, - { - #[inline] - pub fn length_squared(self)->V{ - self.array.into_iter().map(|t|t*t).sum() - } - } + impl Vector + where + T: core::ops::Mul + Copy, + V: core::iter::Sum, + { + #[inline] + pub fn length_squared(self) -> V { + self.array.into_iter().map(|t| t * t).sum() + } + } - // Impl arithmetic operators - $crate::impl_vector_assign_operator!(AddAssign, add_assign ); - $crate::impl_vector_operator!(Add, add ); - $crate::impl_vector_assign_operator!(SubAssign, sub_assign ); - $crate::impl_vector_operator!(Sub, sub ); - $crate::impl_vector_assign_operator!(RemAssign, rem_assign ); - $crate::impl_vector_operator!(Rem, rem ); + // Impl arithmetic operators + $crate::impl_vector_assign_operator!(AddAssign, add_assign); + $crate::impl_vector_operator!(Add, add); + $crate::impl_vector_assign_operator!(SubAssign, sub_assign); + $crate::impl_vector_operator!(Sub, sub); + $crate::impl_vector_assign_operator!(RemAssign, rem_assign); + $crate::impl_vector_operator!(Rem, rem); - // mul and div are special, usually you multiply by a scalar - // and implementing both vec*vec and vec*scalar is conflicting implementations Q_Q - $crate::impl_vector_assign_operator_scalar!(MulAssign, mul_assign ); - $crate::impl_vector_operator_scalar!(Mul, mul ); - $crate::impl_vector_assign_operator_scalar!(DivAssign, div_assign ); - #[cfg(not(feature="deferred-division"))] - $crate::impl_vector_operator_scalar!(Div, div ); - #[cfg(feature="deferred-division")] - $crate::impl_vector_deferred_division!(); + // mul and div are special, usually you multiply by a scalar + // and implementing both vec*vec and vec*scalar is conflicting implementations Q_Q + $crate::impl_vector_assign_operator_scalar!(MulAssign, mul_assign); + $crate::impl_vector_operator_scalar!(Mul, mul); + $crate::impl_vector_assign_operator_scalar!(DivAssign, div_assign); + #[cfg(not(feature = "deferred-division"))] + $crate::impl_vector_operator_scalar!(Div, div); + #[cfg(feature = "deferred-division")] + $crate::impl_vector_deferred_division!(); - // Impl bitwise operators - $crate::impl_vector_assign_operator!(BitAndAssign, bitand_assign ); - $crate::impl_vector_operator!(BitAnd, bitand ); - $crate::impl_vector_assign_operator!(BitOrAssign, bitor_assign ); - $crate::impl_vector_operator!(BitOr, bitor ); - $crate::impl_vector_assign_operator!(BitXorAssign, bitxor_assign ); - $crate::impl_vector_operator!(BitXor, bitxor ); + // Impl bitwise operators + $crate::impl_vector_assign_operator!(BitAndAssign, bitand_assign); + $crate::impl_vector_operator!(BitAnd, bitand); + $crate::impl_vector_assign_operator!(BitOrAssign, bitor_assign); + $crate::impl_vector_operator!(BitOr, bitor); + $crate::impl_vector_assign_operator!(BitXorAssign, bitxor_assign); + $crate::impl_vector_operator!(BitXor, bitxor); - // Impl shift operators - $crate::impl_vector_shift_assign_operator!(ShlAssign, shl_assign); - $crate::impl_vector_shift_operator!(Shl, shl); - $crate::impl_vector_shift_assign_operator!(ShrAssign, shr_assign); - $crate::impl_vector_shift_operator!(Shr, shr); + // Impl shift operators + $crate::impl_vector_shift_assign_operator!(ShlAssign, shl_assign); + $crate::impl_vector_shift_operator!(Shl, shl); + $crate::impl_vector_shift_assign_operator!(ShrAssign, shr_assign); + $crate::impl_vector_shift_operator!(Shr, shr); - // dedicated methods for this type - #[cfg(feature="fixed-wide")] - $crate::impl_fixed_wide_vector!(); - } + // dedicated methods for this type + #[cfg(feature = "fixed-wide")] + $crate::impl_fixed_wide_vector!(); + }; } #[doc(hidden)] #[macro_export(local_inner_macros)] macro_rules! impl_vector_deferred_division { - () => { - impl,U:Copy,V> ratio_ops::ratio::Divide for Vector{ - type Output=Vector; - #[inline] - fn divide(self,rhs:U)->Self::Output{ - self.map(|t|t.divide(rhs)) - } - } - impl core::ops::Div for Vector{ - type Output=ratio_ops::ratio::Ratio,U>; - #[inline] - fn div(self,rhs:U)->Self::Output{ - ratio_ops::ratio::Ratio::new(self,rhs) - } - } - } + () => { + impl, U: Copy, V> + ratio_ops::ratio::Divide for Vector + { + type Output = Vector; + #[inline] + fn divide(self, rhs: U) -> Self::Output { + self.map(|t| t.divide(rhs)) + } + } + impl core::ops::Div for Vector { + type Output = ratio_ops::ratio::Ratio, U>; + #[inline] + fn div(self, rhs: U) -> Self::Output { + ratio_ops::ratio::Ratio::new(self, rhs) + } + } + }; } #[doc(hidden)] #[macro_export(local_inner_macros)] macro_rules! impl_vector_operator_scalar { - ($trait: ident, $method: ident ) => { - impl,U:Copy,V> core::ops::$trait for Vector{ - type Output=Vector; - #[inline] - fn $method(self,rhs:U)->Self::Output{ - self.map(|t|t.$method(rhs)) - } - } - } + ($trait: ident, $method: ident ) => { + impl, U: Copy, V> core::ops::$trait + for Vector + { + type Output = Vector; + #[inline] + fn $method(self, rhs: U) -> Self::Output { + self.map(|t| t.$method(rhs)) + } + } + }; } #[doc(hidden)] #[macro_export(local_inner_macros)] macro_rules! impl_vector_operator { - ($trait: ident, $method: ident ) => { - impl,U,V> core::ops::$trait> for Vector{ - type Output=Vector; - #[inline] - fn $method(self,rhs:Vector)->Self::Output{ - self.map_zip(rhs,|(a,b)|a.$method(b)) - } - } - impl> core::ops::$trait for Vector{ - type Output=Self; - #[inline] - fn $method(self,rhs:i64)->Self::Output{ - self.map(|t|t.$method(rhs)) - } - } - } + ($trait: ident, $method: ident ) => { + impl, U, V> + core::ops::$trait> for Vector + { + type Output = Vector; + #[inline] + fn $method(self, rhs: Vector) -> Self::Output { + self.map_zip(rhs, |(a, b)| a.$method(b)) + } + } + impl> core::ops::$trait + for Vector + { + type Output = Self; + #[inline] + fn $method(self, rhs: i64) -> Self::Output { + self.map(|t| t.$method(rhs)) + } + } + }; } #[doc(hidden)] #[macro_export(local_inner_macros)] macro_rules! impl_vector_assign_operator_scalar { - ($trait: ident, $method: ident ) => { - impl,U:Copy> core::ops::$trait for Vector{ - #[inline] - fn $method(&mut self,rhs:U){ - self.array.iter_mut() - .for_each(|t|t.$method(rhs)) - } - } - } + ($trait: ident, $method: ident ) => { + impl, U: Copy> core::ops::$trait + for Vector + { + #[inline] + fn $method(&mut self, rhs: U) { + self.array.iter_mut().for_each(|t| t.$method(rhs)) + } + } + }; } #[doc(hidden)] #[macro_export(local_inner_macros)] macro_rules! impl_vector_assign_operator { - ($trait: ident, $method: ident ) => { - impl,U> core::ops::$trait> for Vector{ - #[inline] - fn $method(&mut self,rhs:Vector){ - self.array.iter_mut().zip(rhs.array) - .for_each(|(a,b)|a.$method(b)) - } - } - impl> core::ops::$trait for Vector{ - #[inline] - fn $method(&mut self,rhs:i64){ - self.array.iter_mut() - .for_each(|t|t.$method(rhs)) - } - } - } + ($trait: ident, $method: ident ) => { + impl, U> core::ops::$trait> + for Vector + { + #[inline] + fn $method(&mut self, rhs: Vector) { + self.array + .iter_mut() + .zip(rhs.array) + .for_each(|(a, b)| a.$method(b)) + } + } + impl> core::ops::$trait for Vector { + #[inline] + fn $method(&mut self, rhs: i64) { + self.array.iter_mut().for_each(|t| t.$method(rhs)) + } + } + }; } #[doc(hidden)] #[macro_export(local_inner_macros)] macro_rules! impl_vector_shift_operator { - ($trait: ident, $method: ident ) => { - impl,U,V> core::ops::$trait> for Vector{ - type Output=Vector; - #[inline] - fn $method(self,rhs:Vector)->Self::Output{ - self.map_zip(rhs,|(a,b)|a.$method(b)) - } - } - impl,V> core::ops::$trait for Vector{ - type Output=Vector; - #[inline] - fn $method(self,rhs:u32)->Self::Output{ - self.map(|t|t.$method(rhs)) - } - } - } + ($trait: ident, $method: ident ) => { + impl, U, V> + core::ops::$trait> for Vector + { + type Output = Vector; + #[inline] + fn $method(self, rhs: Vector) -> Self::Output { + self.map_zip(rhs, |(a, b)| a.$method(b)) + } + } + impl, V> core::ops::$trait + for Vector + { + type Output = Vector; + #[inline] + fn $method(self, rhs: u32) -> Self::Output { + self.map(|t| t.$method(rhs)) + } + } + }; } #[doc(hidden)] #[macro_export(local_inner_macros)] macro_rules! impl_vector_shift_assign_operator { - ($trait: ident, $method: ident ) => { - impl,U> core::ops::$trait> for Vector{ - #[inline] - fn $method(&mut self,rhs:Vector){ - self.array.iter_mut().zip(rhs.array) - .for_each(|(a,b)|a.$method(b)) - } - } - impl> core::ops::$trait for Vector{ - #[inline] - fn $method(&mut self,rhs:u32){ - self.array.iter_mut() - .for_each(|t|t.$method(rhs)) - } - } - } + ($trait: ident, $method: ident ) => { + impl, U> core::ops::$trait> + for Vector + { + #[inline] + fn $method(&mut self, rhs: Vector) { + self.array + .iter_mut() + .zip(rhs.array) + .for_each(|(a, b)| a.$method(b)) + } + } + impl> core::ops::$trait for Vector { + #[inline] + fn $method(&mut self, rhs: u32) { + self.array.iter_mut().for_each(|t| t.$method(rhs)) + } + } + }; } #[doc(hidden)] #[macro_export(local_inner_macros)] macro_rules! impl_vector_extend { - ( $size: expr ) => { - impl Vector<$size,T>{ - #[inline] - pub fn extend(self,value:T)->Vector<{$size+1},T>{ - let mut iter=self.array.into_iter().chain(core::iter::once(value)); - Vector::new( - core::array::from_fn(|_|iter.next().unwrap()), - ) - } - } - } + ( $size: expr ) => { + impl Vector<$size, T> { + #[inline] + pub fn extend(self, value: T) -> Vector<{ $size + 1 }, T> { + let mut iter = self.array.into_iter().chain(core::iter::once(value)); + Vector::new(core::array::from_fn(|_| iter.next().unwrap())) + } + } + }; } #[doc(hidden)] #[macro_export(local_inner_macros)] macro_rules! impl_vector_named_fields { - ( $struct:ident, $size: expr ) => { - impl core::ops::Deref for Vector<$size,T>{ - type Target=$struct; - #[inline] - fn deref(&self)->&Self::Target{ - unsafe{core::mem::transmute(&self.array)} - } - } - impl core::ops::DerefMut for Vector<$size,T>{ - #[inline] - fn deref_mut(&mut self)->&mut Self::Target{ - unsafe{core::mem::transmute(&mut self.array)} - } - } - } + ( $struct:ident, $size: expr ) => { + impl core::ops::Deref for Vector<$size, T> { + type Target = $struct; + #[inline] + fn deref(&self) -> &Self::Target { + unsafe { core::mem::transmute(&self.array) } + } + } + impl core::ops::DerefMut for Vector<$size, T> { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { core::mem::transmute(&mut self.array) } + } + } + }; } #[doc(hidden)] #[macro_export(local_inner_macros)] macro_rules! impl_vector_3 { - ()=>{ - impl Vector<3,T> - { - #[inline] - pub fn cross(self,rhs:Vector<3,U>)->Vector<3,::Output> - where - T:core::ops::Mul+Copy, - U:Copy, - V:core::ops::Sub, - { - Vector::new([ - self.y*rhs.z-self.z*rhs.y, - self.z*rhs.x-self.x*rhs.z, - self.x*rhs.y-self.y*rhs.x, - ]) - } - } - } + () => { + impl Vector<3, T> { + #[inline] + pub fn cross(self, rhs: Vector<3, U>) -> Vector<3, ::Output> + where + T: core::ops::Mul + Copy, + U: Copy, + V: core::ops::Sub, + { + Vector::new([ + self.y * rhs.z - self.z * rhs.y, + self.z * rhs.x - self.x * rhs.z, + self.x * rhs.y - self.y * rhs.x, + ]) + } + } + }; } diff --git a/lib/linear_ops/src/matrix.rs b/lib/linear_ops/src/matrix.rs index 200d176..f396218 100644 --- a/lib/linear_ops/src/matrix.rs +++ b/lib/linear_ops/src/matrix.rs @@ -1,17 +1,17 @@ use crate::vector::Vector; -#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq)] -pub struct Matrix{ - pub(crate) array:[[T;Y];X], +#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)] +pub struct Matrix { + pub(crate) array: [[T; Y]; X], } crate::impl_matrix!(); -crate::impl_matrix_extend!(2,2); -crate::impl_matrix_extend!(2,3); -crate::impl_matrix_extend!(3,2); -crate::impl_matrix_extend!(3,3); +crate::impl_matrix_extend!(2, 2); +crate::impl_matrix_extend!(2, 3); +crate::impl_matrix_extend!(3, 2); +crate::impl_matrix_extend!(3, 3); //Special case 3x3 matrix operations because I cba to write macros for the arbitrary cases -#[cfg(feature="named-fields")] +#[cfg(feature = "named-fields")] crate::impl_matrix_3x3!(); diff --git a/lib/linear_ops/src/named.rs b/lib/linear_ops/src/named.rs index b467357..a3c5ebb 100644 --- a/lib/linear_ops/src/named.rs +++ b/lib/linear_ops/src/named.rs @@ -1,23 +1,23 @@ -use crate::vector::Vector; use crate::matrix::Matrix; +use crate::vector::Vector; #[repr(C)] pub struct Vector2 { - pub x: T, - pub y: T, + pub x: T, + pub y: T, } #[repr(C)] pub struct Vector3 { - pub x: T, - pub y: T, - pub z: T, + pub x: T, + pub y: T, + pub z: T, } #[repr(C)] pub struct Vector4 { - pub x: T, - pub y: T, - pub z: T, - pub w: T, + pub x: T, + pub y: T, + pub z: T, + pub w: T, } crate::impl_vector_named_fields!(Vector2, 2); @@ -26,34 +26,26 @@ crate::impl_vector_named_fields!(Vector4, 4); #[repr(C)] pub struct Matrix2 { - pub x_axis: T, - pub y_axis: T, + pub x_axis: T, + pub y_axis: T, } #[repr(C)] pub struct Matrix3 { - pub x_axis: T, - pub y_axis: T, - pub z_axis: T, + pub x_axis: T, + pub y_axis: T, + pub z_axis: T, } #[repr(C)] pub struct Matrix4 { - pub x_axis: T, - pub y_axis: T, - pub z_axis: T, - pub w_axis: T, + pub x_axis: T, + pub y_axis: T, + pub z_axis: T, + pub w_axis: T, } crate::impl_matrix_named_fields!( - //outer struct - ( - (Matrix2, 2), - (Matrix3, 3), - (Matrix4, 4) - ), - //inner struct - ( - (2), - (3), - (4) - ) + //outer struct + ((Matrix2, 2), (Matrix3, 3), (Matrix4, 4)), + //inner struct + ((2), (3), (4)) ); diff --git a/lib/linear_ops/src/tests/fixed_wide.rs b/lib/linear_ops/src/tests/fixed_wide.rs index 7032fd0..f2cd6b2 100644 --- a/lib/linear_ops/src/tests/fixed_wide.rs +++ b/lib/linear_ops/src/tests/fixed_wide.rs @@ -1,96 +1,131 @@ -use crate::types::{Matrix3,Matrix3x2,Matrix3x4,Matrix4x2,Vector3}; +use crate::types::{Matrix3, Matrix3x2, Matrix3x4, Matrix4x2, Vector3}; -type Planar64=fixed_wide::types::I32F32; -type Planar64Wide1=fixed_wide::types::I64F64; +type Planar64 = fixed_wide::types::I32F32; +type Planar64Wide1 = fixed_wide::types::I64F64; //type Planar64Wide2=fixed_wide::types::I128F128; -type Planar64Wide3=fixed_wide::types::I256F256; +type Planar64Wide3 = fixed_wide::types::I256F256; #[test] -fn wide_vec3(){ - let v=Vector3::from_value(Planar64::from(3)); - let v1=v*v.x; - let v2=v1*v1.y; - let v3=v2*v2.z; +fn wide_vec3() { + let v = Vector3::from_value(Planar64::from(3)); + let v1 = v * v.x; + let v2 = v1 * v1.y; + let v3 = v2 * v2.z; - assert_eq!(v3.array,Vector3::from_value(Planar64Wide3::from(3i128.pow(8))).array); + assert_eq!( + v3.array, + Vector3::from_value(Planar64Wide3::from(3i128.pow(8))).array + ); } #[test] -fn wide_vec3_dot(){ - let v=Vector3::from_value(Planar64::from(3)); - let v1=v*v.x; - let v2=v1*v1.y; - let v3=v2.dot(v2); +fn wide_vec3_dot() { + let v = Vector3::from_value(Planar64::from(3)); + let v1 = v * v.x; + let v2 = v1 * v1.y; + let v3 = v2.dot(v2); - assert_eq!(v3,Planar64Wide3::from(3i128.pow(8)*3)); + assert_eq!(v3, Planar64Wide3::from(3i128.pow(8) * 3)); } #[test] -fn wide_vec3_length_squared(){ - let v=Vector3::from_value(Planar64::from(3)); - let v1=v*v.x; - let v2=v1*v1.y; - let v3=v2.length_squared(); +fn wide_vec3_length_squared() { + let v = Vector3::from_value(Planar64::from(3)); + let v1 = v * v.x; + let v2 = v1 * v1.y; + let v3 = v2.length_squared(); - assert_eq!(v3,Planar64Wide3::from(3i128.pow(8)*3)); + assert_eq!(v3, Planar64Wide3::from(3i128.pow(8) * 3)); } #[test] -fn wide_matrix_dot(){ - let lhs=Matrix3x4::new([ - [Planar64::from(1),Planar64::from(2),Planar64::from(3),Planar64::from(4)], - [Planar64::from(5),Planar64::from(6),Planar64::from(7),Planar64::from(8)], - [Planar64::from(9),Planar64::from(10),Planar64::from(11),Planar64::from(12)], - ]).transpose(); - let rhs=Matrix4x2::new([ - [Planar64::from(1),Planar64::from(2)], - [Planar64::from(3),Planar64::from(4)], - [Planar64::from(5),Planar64::from(6)], - [Planar64::from(7),Planar64::from(8)], - ]).transpose(); - // Mat3.dot(Mat4) -> Mat3 - let m_dot=lhs*rhs; - //In[1]:= {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}} . {{1, 2}, {3, 4}, {5, 6}, {7, 8}} - //Out[1]= {{50, 60}, {114, 140}, {178, 220}} - assert_eq!( - m_dot.array, - Matrix3x2::new([ - [Planar64Wide1::from(50),Planar64Wide1::from(60)], - [Planar64Wide1::from(114),Planar64Wide1::from(140)], - [Planar64Wide1::from(178),Planar64Wide1::from(220)], - ]).transpose().array - ); +fn wide_matrix_dot() { + let lhs = Matrix3x4::new([ + [ + Planar64::from(1), + Planar64::from(2), + Planar64::from(3), + Planar64::from(4), + ], + [ + Planar64::from(5), + Planar64::from(6), + Planar64::from(7), + Planar64::from(8), + ], + [ + Planar64::from(9), + Planar64::from(10), + Planar64::from(11), + Planar64::from(12), + ], + ]) + .transpose(); + let rhs = Matrix4x2::new([ + [Planar64::from(1), Planar64::from(2)], + [Planar64::from(3), Planar64::from(4)], + [Planar64::from(5), Planar64::from(6)], + [Planar64::from(7), Planar64::from(8)], + ]) + .transpose(); + // Mat3.dot(Mat4) -> Mat3 + let m_dot = lhs * rhs; + //In[1]:= {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}} . {{1, 2}, {3, 4}, {5, 6}, {7, 8}} + //Out[1]= {{50, 60}, {114, 140}, {178, 220}} + assert_eq!( + m_dot.array, + Matrix3x2::new([ + [Planar64Wide1::from(50), Planar64Wide1::from(60)], + [Planar64Wide1::from(114), Planar64Wide1::from(140)], + [Planar64Wide1::from(178), Planar64Wide1::from(220)], + ]) + .transpose() + .array + ); } #[test] -#[cfg(feature="named-fields")] -fn wide_matrix_det(){ - let m=Matrix3::new([ - [Planar64::from(1),Planar64::from(2),Planar64::from(3)], - [Planar64::from(4),Planar64::from(5),Planar64::from(7)], - [Planar64::from(6),Planar64::from(8),Planar64::from(9)], - ]); - // In[2]:= Det[{{1, 2, 3}, {4, 5, 7}, {6, 8, 9}}] - // Out[2]= 7 - assert_eq!(m.det(),fixed_wide::fixed::Fixed::<3,96>::from(7)); +#[cfg(feature = "named-fields")] +fn wide_matrix_det() { + let m = Matrix3::new([ + [Planar64::from(1), Planar64::from(2), Planar64::from(3)], + [Planar64::from(4), Planar64::from(5), Planar64::from(7)], + [Planar64::from(6), Planar64::from(8), Planar64::from(9)], + ]); + // In[2]:= Det[{{1, 2, 3}, {4, 5, 7}, {6, 8, 9}}] + // Out[2]= 7 + assert_eq!(m.det(), fixed_wide::fixed::Fixed::<3, 96>::from(7)); } #[test] -#[cfg(feature="named-fields")] -fn wide_matrix_adjugate(){ - let m=Matrix3::new([ - [Planar64::from(1),Planar64::from(2),Planar64::from(3)], - [Planar64::from(4),Planar64::from(5),Planar64::from(7)], - [Planar64::from(6),Planar64::from(8),Planar64::from(9)], - ]); - // In[6]:= Adjugate[{{1, 2, 3}, {4, 5, 7}, {6, 8, 9}}] - // Out[6]= {{-11, 6, -1}, {6, -9, 5}, {2, 4, -3}} - assert_eq!( - m.adjugate().array, - Matrix3::new([ - [Planar64Wide1::from(-11),Planar64Wide1::from(6),Planar64Wide1::from(-1)], - [Planar64Wide1::from(6),Planar64Wide1::from(-9),Planar64Wide1::from(5)], - [Planar64Wide1::from(2),Planar64Wide1::from(4),Planar64Wide1::from(-3)], - ]).array - ); +#[cfg(feature = "named-fields")] +fn wide_matrix_adjugate() { + let m = Matrix3::new([ + [Planar64::from(1), Planar64::from(2), Planar64::from(3)], + [Planar64::from(4), Planar64::from(5), Planar64::from(7)], + [Planar64::from(6), Planar64::from(8), Planar64::from(9)], + ]); + // In[6]:= Adjugate[{{1, 2, 3}, {4, 5, 7}, {6, 8, 9}}] + // Out[6]= {{-11, 6, -1}, {6, -9, 5}, {2, 4, -3}} + assert_eq!( + m.adjugate().array, + Matrix3::new([ + [ + Planar64Wide1::from(-11), + Planar64Wide1::from(6), + Planar64Wide1::from(-1) + ], + [ + Planar64Wide1::from(6), + Planar64Wide1::from(-9), + Planar64Wide1::from(5) + ], + [ + Planar64Wide1::from(2), + Planar64Wide1::from(4), + Planar64Wide1::from(-3) + ], + ]) + .array + ); } diff --git a/lib/linear_ops/src/tests/mod.rs b/lib/linear_ops/src/tests/mod.rs index 5bdf283..1903097 100644 --- a/lib/linear_ops/src/tests/mod.rs +++ b/lib/linear_ops/src/tests/mod.rs @@ -1,6 +1,6 @@ mod tests; -#[cfg(feature="named-fields")] +#[cfg(feature = "named-fields")] mod named; mod fixed_wide; diff --git a/lib/linear_ops/src/tests/named.rs b/lib/linear_ops/src/tests/named.rs index 04b0a08..c26848e 100644 --- a/lib/linear_ops/src/tests/named.rs +++ b/lib/linear_ops/src/tests/named.rs @@ -1,30 +1,29 @@ -use crate::types::{Vector3,Matrix3}; +use crate::types::{Matrix3, Vector3}; #[test] -fn test_vector(){ - let mut v=Vector3::new([1,2,3]); - assert_eq!(v.x,1); - assert_eq!(v.y,2); - assert_eq!(v.z,3); +fn test_vector() { + let mut v = Vector3::new([1, 2, 3]); + assert_eq!(v.x, 1); + assert_eq!(v.y, 2); + assert_eq!(v.z, 3); - v.x=5; - assert_eq!(v.x,5); + v.x = 5; + assert_eq!(v.x, 5); - v.y*=v.x; - assert_eq!(v.y,10); + v.y *= v.x; + assert_eq!(v.y, 10); } - #[test] -fn test_matrix(){ - let mut v=Matrix3::from_value(2); - assert_eq!(v.x_axis.x,2); - assert_eq!(v.y_axis.y,2); - assert_eq!(v.z_axis.z,2); +fn test_matrix() { + let mut v = Matrix3::from_value(2); + assert_eq!(v.x_axis.x, 2); + assert_eq!(v.y_axis.y, 2); + assert_eq!(v.z_axis.z, 2); - v.x_axis.x=5; - assert_eq!(v.x_axis.x,5); + v.x_axis.x = 5; + assert_eq!(v.x_axis.x, 5); - v.y_axis.z*=v.x_axis.x; - assert_eq!(v.y_axis.z,10); + v.y_axis.z *= v.x_axis.x; + assert_eq!(v.y_axis.z, 10); } diff --git a/lib/linear_ops/src/tests/tests.rs b/lib/linear_ops/src/tests/tests.rs index 573fe9a..dfe0d76 100644 --- a/lib/linear_ops/src/tests/tests.rs +++ b/lib/linear_ops/src/tests/tests.rs @@ -1,59 +1,51 @@ -use crate::types::{Vector2,Vector3,Matrix3x4,Matrix4x2,Matrix3x2,Matrix2x3}; +use crate::types::{Matrix2x3, Matrix3x2, Matrix3x4, Matrix4x2, Vector2, Vector3}; #[test] -fn test_bool(){ - assert_eq!(Vector3::new([false,false,false]).any(),false); - assert_eq!(Vector3::new([false,false,true]).any(),true); - assert_eq!(Vector3::new([false,false,true]).all(),false); - assert_eq!(Vector3::new([true,true,true]).all(),true); +fn test_bool() { + assert_eq!(Vector3::new([false, false, false]).any(), false); + assert_eq!(Vector3::new([false, false, true]).any(), true); + assert_eq!(Vector3::new([false, false, true]).all(), false); + assert_eq!(Vector3::new([true, true, true]).all(), true); } #[test] -fn test_length_squared(){ - assert_eq!(Vector3::new([1,2,3]).length_squared(),14); +fn test_length_squared() { + assert_eq!(Vector3::new([1, 2, 3]).length_squared(), 14); } #[test] -fn test_arithmetic(){ - let a=Vector3::new([1,2,3]); - assert_eq!((a+a*2).array,Vector3::new([1*3,2*3,3*3]).array); +fn test_arithmetic() { + let a = Vector3::new([1, 2, 3]); + assert_eq!((a + a * 2).array, Vector3::new([1 * 3, 2 * 3, 3 * 3]).array); } #[test] -fn matrix_transform_vector(){ - let m=Matrix2x3::new([ - [1,2,3], - [4,5,6], - ]).transpose(); - let v=Vector3::new([1,2,3]); - let transformed=m*v; - assert_eq!(transformed.array,Vector2::new([14,32]).array); +fn matrix_transform_vector() { + let m = Matrix2x3::new([[1, 2, 3], [4, 5, 6]]).transpose(); + let v = Vector3::new([1, 2, 3]); + let transformed = m * v; + assert_eq!(transformed.array, Vector2::new([14, 32]).array); } #[test] -fn matrix_dot(){ - // All this code was written row major and I converted the lib to colum major - let rhs=Matrix4x2::new([ - [ 1.0, 2.0], - [ 3.0, 4.0], - [ 5.0, 6.0], - [ 7.0, 8.0], - ]).transpose(); // | | | - let lhs=Matrix3x4::new([ // | | | - [1.0, 2.0, 3.0, 4.0],// [ 50.0, 60.0], - [5.0, 6.0, 7.0, 8.0],// [114.0,140.0], - [9.0,10.0,11.0,12.0],// [178.0,220.0], - ]).transpose(); - // Mat3.dot(Mat4) -> Mat3 - let m_dot=lhs*rhs; - //In[1]:= {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}} . {{1, 2}, {3, 4}, {5, 6}, {7, 8}} - //Out[1]= {{50, 60}, {114, 140}, {178, 220}} - assert_eq!( - m_dot.array, - Matrix3x2::new([ - [50.0,60.0], - [114.0,140.0], - [178.0,220.0], - ]).transpose().array - ); +fn matrix_dot() { + // All this code was written row major and I converted the lib to colum major + let rhs = Matrix4x2::new([[1.0, 2.0], [3.0, 4.0], [5.0, 6.0], [7.0, 8.0]]).transpose(); // | | | + let lhs = Matrix3x4::new([ + // | | | + [1.0, 2.0, 3.0, 4.0], // [ 50.0, 60.0], + [5.0, 6.0, 7.0, 8.0], // [114.0,140.0], + [9.0, 10.0, 11.0, 12.0], // [178.0,220.0], + ]) + .transpose(); + // Mat3.dot(Mat4) -> Mat3 + let m_dot = lhs * rhs; + //In[1]:= {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}} . {{1, 2}, {3, 4}, {5, 6}, {7, 8}} + //Out[1]= {{50, 60}, {114, 140}, {178, 220}} + assert_eq!( + m_dot.array, + Matrix3x2::new([[50.0, 60.0], [114.0, 140.0], [178.0, 220.0],]) + .transpose() + .array + ); } diff --git a/lib/linear_ops/src/types.rs b/lib/linear_ops/src/types.rs index 6abf17f..af99802 100644 --- a/lib/linear_ops/src/types.rs +++ b/lib/linear_ops/src/types.rs @@ -1,18 +1,18 @@ -use crate::vector::Vector; use crate::matrix::Matrix; +use crate::vector::Vector; -pub type Vector2=Vector<2,T>; -pub type Vector3=Vector<3,T>; -pub type Vector4=Vector<4,T>; +pub type Vector2 = Vector<2, T>; +pub type Vector3 = Vector<3, T>; +pub type Vector4 = Vector<4, T>; -pub type Matrix2=Matrix<2,2,T>; -pub type Matrix2x3=Matrix<2,3,T>; -pub type Matrix2x4=Matrix<2,4,T>; +pub type Matrix2 = Matrix<2, 2, T>; +pub type Matrix2x3 = Matrix<2, 3, T>; +pub type Matrix2x4 = Matrix<2, 4, T>; -pub type Matrix3x2=Matrix<3,2,T>; -pub type Matrix3=Matrix<3,3,T>; -pub type Matrix3x4=Matrix<3,4,T>; +pub type Matrix3x2 = Matrix<3, 2, T>; +pub type Matrix3 = Matrix<3, 3, T>; +pub type Matrix3x4 = Matrix<3, 4, T>; -pub type Matrix4x2=Matrix<4,2,T>; -pub type Matrix4x3=Matrix<4,3,T>; -pub type Matrix4=Matrix<4,4,T>; +pub type Matrix4x2 = Matrix<4, 2, T>; +pub type Matrix4x3 = Matrix<4, 3, T>; +pub type Matrix4 = Matrix<4, 4, T>; diff --git a/lib/linear_ops/src/vector.rs b/lib/linear_ops/src/vector.rs index 8d223de..5b3287b 100644 --- a/lib/linear_ops/src/vector.rs +++ b/lib/linear_ops/src/vector.rs @@ -3,9 +3,9 @@ /// v.x += v.z; /// println!("v.x={}",v.x); -#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq)] -pub struct Vector{ - pub(crate) array:[T;N], +#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)] +pub struct Vector { + pub(crate) array: [T; N], } crate::impl_vector!(); @@ -15,5 +15,5 @@ crate::impl_vector_extend!(2); crate::impl_vector_extend!(3); //cross product -#[cfg(feature="named-fields")] +#[cfg(feature = "named-fields")] crate::impl_vector_3!(); diff --git a/lib/ratio_ops/src/ratio.rs b/lib/ratio_ops/src/ratio.rs index 00ad27c..aa8b67a 100644 --- a/lib/ratio_ops/src/ratio.rs +++ b/lib/ratio_ops/src/ratio.rs @@ -1,77 +1,92 @@ -#[derive(Clone,Copy,Debug,Hash)] -pub struct Ratio{ - pub num:Num, - pub den:Den, +#[derive(Clone, Copy, Debug, Hash)] +pub struct Ratio { + pub num: Num, + pub den: Den, } -impl Ratio{ - #[inline(always)] - pub const fn new(num:Num,den:Den)->Self{ - Self{num,den} - } +impl Ratio { + #[inline(always)] + pub const fn new(num: Num, den: Den) -> Self { + Self { num, den } + } } /// The actual divide implementation, Div is replaced with a Ratio constructor -pub trait Divide{ - type Output; - fn divide(self,rhs:Rhs)->Self::Output; +pub trait Divide { + type Output; + fn divide(self, rhs: Rhs) -> Self::Output; } -impl Ratio - where - Num:Divide, +impl Ratio +where + Num: Divide, { - #[inline] - pub fn divide(self)->>::Output{ - self.num.divide(self.den) - } + #[inline] + pub fn divide(self) -> >::Output { + self.num.divide(self.den) + } } //take care to use the ratio methods to avoid nested ratios -impl Ratio{ - #[inline] - pub fn mul_ratio(self,rhs:Ratio)->Ratio<>::Output,>::Output> - where - LhsNum:core::ops::Mul, - LhsDen:core::ops::Mul, - { - Ratio::new(self.num*rhs.num,self.den*rhs.den) - } - #[inline] - pub fn div_ratio(self,rhs:Ratio)->Ratio<>::Output,>::Output> - where - LhsNum:core::ops::Mul, - LhsDen:core::ops::Mul, - { - Ratio::new(self.num*rhs.den,self.den*rhs.num) - } +impl Ratio { + #[inline] + pub fn mul_ratio( + self, + rhs: Ratio, + ) -> Ratio<>::Output, >::Output> + where + LhsNum: core::ops::Mul, + LhsDen: core::ops::Mul, + { + Ratio::new(self.num * rhs.num, self.den * rhs.den) + } + #[inline] + pub fn div_ratio( + self, + rhs: Ratio, + ) -> Ratio<>::Output, >::Output> + where + LhsNum: core::ops::Mul, + LhsDen: core::ops::Mul, + { + Ratio::new(self.num * rhs.den, self.den * rhs.num) + } } macro_rules! impl_ratio_method { - ($trait:ident, $method:ident, $ratio_method:ident) => { - impl Ratio{ - #[inline] - pub fn $ratio_method(self,rhs:Ratio)->Ratio<>::Output,>::Output> - where - LhsNum:core::ops::Mul, - LhsDen:core::ops::Mul, - LhsDen:core::ops::Mul, - LhsDen:Copy, - RhsDen:Copy, - LhsCrossMul:core::ops::$trait, - { - Ratio::new((self.num*rhs.den).$method(self.den*rhs.num),self.den*rhs.den) - } - } - }; + ($trait:ident, $method:ident, $ratio_method:ident) => { + impl Ratio { + #[inline] + pub fn $ratio_method( + self, + rhs: Ratio, + ) -> Ratio< + >::Output, + >::Output, + > + where + LhsNum: core::ops::Mul, + LhsDen: core::ops::Mul, + LhsDen: core::ops::Mul, + LhsDen: Copy, + RhsDen: Copy, + LhsCrossMul: core::ops::$trait, + { + Ratio::new( + (self.num * rhs.den).$method(self.den * rhs.num), + self.den * rhs.den, + ) + } + } + }; } -impl_ratio_method!(Add,add,add_ratio); -impl_ratio_method!(Sub,sub,sub_ratio); -impl_ratio_method!(Rem,rem,rem_ratio); +impl_ratio_method!(Add, add, add_ratio); +impl_ratio_method!(Sub, sub, sub_ratio); +impl_ratio_method!(Rem, rem, rem_ratio); /// Comparing two ratios needs to know the parity of the denominators /// For signed integers this can be implemented with is_negative() -pub trait Parity{ - fn parity(&self)->bool; +pub trait Parity { + fn parity(&self) -> bool; } macro_rules! impl_parity_unsigned{ ($($type:ty),*)=>{ @@ -107,191 +122,196 @@ macro_rules! impl_parity_float{ }; } -impl_parity_unsigned!(u8,u16,u32,u64,u128,usize); -impl_parity_signed!(i8,i16,i32,i64,i128,isize); -impl_parity_float!(f32,f64); +impl_parity_unsigned!(u8, u16, u32, u64, u128, usize); +impl_parity_signed!(i8, i16, i32, i64, i128, isize); +impl_parity_float!(f32, f64); -macro_rules! impl_ratio_ord_method{ - ($method:ident, $ratio_method:ident, $output:ty)=>{ - impl Ratio{ - #[inline] - pub fn $ratio_method(self,rhs:Ratio)->$output - where - LhsNum:core::ops::Mul, - LhsDen:core::ops::Mul, - T:Ord, - { - match self.den.parity()^rhs.den.parity(){ - true=>(self.den*rhs.num).$method(&(self.num*rhs.den)), - false=>(self.num*rhs.den).$method(&(self.den*rhs.num)), - } - } - } - } +macro_rules! impl_ratio_ord_method { + ($method:ident, $ratio_method:ident, $output:ty) => { + impl Ratio { + #[inline] + pub fn $ratio_method( + self, + rhs: Ratio, + ) -> $output + where + LhsNum: core::ops::Mul, + LhsDen: core::ops::Mul, + T: Ord, + { + match self.den.parity() ^ rhs.den.parity() { + true => (self.den * rhs.num).$method(&(self.num * rhs.den)), + false => (self.num * rhs.den).$method(&(self.den * rhs.num)), + } + } + } + }; } //PartialEq -impl_ratio_ord_method!(eq,eq_ratio,bool); +impl_ratio_ord_method!(eq, eq_ratio, bool); //PartialOrd -impl_ratio_ord_method!(lt,lt_ratio,bool); -impl_ratio_ord_method!(gt,gt_ratio,bool); -impl_ratio_ord_method!(le,le_ratio,bool); -impl_ratio_ord_method!(ge,ge_ratio,bool); -impl_ratio_ord_method!(partial_cmp,partial_cmp_ratio,Option); +impl_ratio_ord_method!(lt, lt_ratio, bool); +impl_ratio_ord_method!(gt, gt_ratio, bool); +impl_ratio_ord_method!(le, le_ratio, bool); +impl_ratio_ord_method!(ge, ge_ratio, bool); +impl_ratio_ord_method!(partial_cmp, partial_cmp_ratio, Option); //Ord -impl_ratio_ord_method!(cmp,cmp_ratio,core::cmp::Ordering); +impl_ratio_ord_method!(cmp, cmp_ratio, core::cmp::Ordering); /* generic rhs mul is not possible! impl core::ops::Mul> for Lhs - where - Lhs:core::ops::Mul, + where + Lhs:core::ops::Mul, { - type Output=Ratio<>::Output,RhsDen>; - #[inline] - fn mul(self,rhs:Ratio)->Self::Output{ - Ratio::new(self*rhs.num,rhs.den) - } + type Output=Ratio<>::Output,RhsDen>; + #[inline] + fn mul(self,rhs:Ratio)->Self::Output{ + Ratio::new(self*rhs.num,rhs.den) + } } */ //operators -impl core::ops::Neg for Ratio - where - LhsNum:core::ops::Neg, +impl core::ops::Neg for Ratio +where + LhsNum: core::ops::Neg, { - type Output=Ratio<::Output,LhsDen>; - #[inline] - fn neg(self)->Self::Output{ - Ratio::new(-self.num,self.den) - } + type Output = Ratio<::Output, LhsDen>; + #[inline] + fn neg(self) -> Self::Output { + Ratio::new(-self.num, self.den) + } } -impl core::ops::Mul for Ratio - where - LhsNum:core::ops::Mul, +impl core::ops::Mul for Ratio +where + LhsNum: core::ops::Mul, { - type Output=Ratio<>::Output,LhsDen>; - #[inline] - fn mul(self,rhs:Rhs)->Self::Output{ - Ratio::new(self.num*rhs,self.den) - } + type Output = Ratio<>::Output, LhsDen>; + #[inline] + fn mul(self, rhs: Rhs) -> Self::Output { + Ratio::new(self.num * rhs, self.den) + } } -impl core::ops::Div for Ratio - where - LhsDen:core::ops::Mul, +impl core::ops::Div for Ratio +where + LhsDen: core::ops::Mul, { - type Output=Ratio>::Output>; - #[inline] - fn div(self,rhs:Rhs)->Self::Output{ - Ratio::new(self.num,self.den*rhs) - } + type Output = Ratio>::Output>; + #[inline] + fn div(self, rhs: Rhs) -> Self::Output { + Ratio::new(self.num, self.den * rhs) + } } macro_rules! impl_ratio_operator { - ($trait:ident, $method:ident) => { - impl core::ops::$trait for Ratio - where - LhsNum:core::ops::$trait, - LhsDen:Copy, - Rhs:core::ops::Mul, - { - type Output=Ratio<>::Output,LhsDen>; - #[inline] - fn $method(self,rhs:Rhs)->Self::Output{ - Ratio::new(self.num.$method(rhs*self.den),self.den) - } - } - }; + ($trait:ident, $method:ident) => { + impl core::ops::$trait for Ratio + where + LhsNum: core::ops::$trait, + LhsDen: Copy, + Rhs: core::ops::Mul, + { + type Output = Ratio<>::Output, LhsDen>; + #[inline] + fn $method(self, rhs: Rhs) -> Self::Output { + Ratio::new(self.num.$method(rhs * self.den), self.den) + } + } + }; } -impl_ratio_operator!(Add,add); -impl_ratio_operator!(Sub,sub); -impl_ratio_operator!(Rem,rem); +impl_ratio_operator!(Add, add); +impl_ratio_operator!(Sub, sub); +impl_ratio_operator!(Rem, rem); //assign operators -impl core::ops::MulAssign for Ratio - where - LhsNum:core::ops::MulAssign, +impl core::ops::MulAssign for Ratio +where + LhsNum: core::ops::MulAssign, { - #[inline] - fn mul_assign(&mut self,rhs:Rhs){ - self.num*=rhs; - } + #[inline] + fn mul_assign(&mut self, rhs: Rhs) { + self.num *= rhs; + } } -impl core::ops::DivAssign for Ratio - where - LhsDen:core::ops::MulAssign, +impl core::ops::DivAssign for Ratio +where + LhsDen: core::ops::MulAssign, { - #[inline] - fn div_assign(&mut self,rhs:Rhs){ - self.den*=rhs; - } + #[inline] + fn div_assign(&mut self, rhs: Rhs) { + self.den *= rhs; + } } macro_rules! impl_ratio_assign_operator { - ($trait:ident, $method:ident) => { - impl core::ops::$trait for Ratio - where - LhsNum:core::ops::$trait, - LhsDen:Copy, - Rhs:core::ops::Mul, - { - #[inline] - fn $method(&mut self,rhs:Rhs){ - self.num.$method(rhs*self.den) - } - } - }; + ($trait:ident, $method:ident) => { + impl core::ops::$trait for Ratio + where + LhsNum: core::ops::$trait, + LhsDen: Copy, + Rhs: core::ops::Mul, + { + #[inline] + fn $method(&mut self, rhs: Rhs) { + self.num.$method(rhs * self.den) + } + } + }; } -impl_ratio_assign_operator!(AddAssign,add_assign); -impl_ratio_assign_operator!(SubAssign,sub_assign); -impl_ratio_assign_operator!(RemAssign,rem_assign); +impl_ratio_assign_operator!(AddAssign, add_assign); +impl_ratio_assign_operator!(SubAssign, sub_assign); +impl_ratio_assign_operator!(RemAssign, rem_assign); // Only implement PartialEq // Rust's operators aren't actually that good -impl PartialEq> for Ratio - where - LhsNum:Copy, - LhsDen:Copy, - RhsNum:Copy, - RhsDen:Copy, - LhsNum:core::ops::Mul, - RhsNum:core::ops::Mul, - T:PartialEq, +impl PartialEq> + for Ratio +where + LhsNum: Copy, + LhsDen: Copy, + RhsNum: Copy, + RhsDen: Copy, + LhsNum: core::ops::Mul, + RhsNum: core::ops::Mul, + T: PartialEq, { - #[inline] - fn eq(&self,other:&Ratio)->bool{ - (self.num*other.den).eq(&(other.num*self.den)) - } + #[inline] + fn eq(&self, other: &Ratio) -> bool { + (self.num * other.den).eq(&(other.num * self.den)) + } } -impl Eq for Ratio where Self:PartialEq{} +impl Eq for Ratio where Self: PartialEq {} -impl PartialOrd> for Ratio - where - LhsNum:Copy, - LhsDen:Copy, - RhsNum:Copy, - RhsDen:Copy, - LhsNum:core::ops::Mul, - RhsNum:core::ops::Mul, - T:PartialOrd, +impl PartialOrd> + for Ratio +where + LhsNum: Copy, + LhsDen: Copy, + RhsNum: Copy, + RhsDen: Copy, + LhsNum: core::ops::Mul, + RhsNum: core::ops::Mul, + T: PartialOrd, { - #[inline] - fn partial_cmp(&self,other:&Ratio)->Option{ - (self.num*other.den).partial_cmp(&(other.num*self.den)) - } + #[inline] + fn partial_cmp(&self, other: &Ratio) -> Option { + (self.num * other.den).partial_cmp(&(other.num * self.den)) + } } -impl Ord for Ratio - where - Num:Copy, - Den:Copy, - Num:core::ops::Mul, - T:Ord, +impl Ord for Ratio +where + Num: Copy, + Den: Copy, + Num: core::ops::Mul, + T: Ord, { - #[inline] - fn cmp(&self,other:&Self)->std::cmp::Ordering{ - (self.num*other.den).cmp(&(other.num*self.den)) - } + #[inline] + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + (self.num * other.den).cmp(&(other.num * self.den)) + } } diff --git a/lib/ratio_ops/src/tests.rs b/lib/ratio_ops/src/tests.rs index 739313e..0c275c1 100644 --- a/lib/ratio_ops/src/tests.rs +++ b/lib/ratio_ops/src/tests.rs @@ -1,55 +1,55 @@ use crate::ratio::Ratio; -macro_rules! test_op{ - ($ratio_op:ident,$op:ident,$a:expr,$b:expr,$c:expr,$d:expr)=>{ - assert_eq!( - Ratio::new($a,$b).$ratio_op(Ratio::new($c,$d)), - (($a as f32)/($b as f32)).$op(&(($c as f32)/($d as f32))) - ); - }; +macro_rules! test_op { + ($ratio_op:ident,$op:ident,$a:expr,$b:expr,$c:expr,$d:expr) => { + assert_eq!( + Ratio::new($a, $b).$ratio_op(Ratio::new($c, $d)), + (($a as f32) / ($b as f32)).$op(&(($c as f32) / ($d as f32))) + ); + }; } -macro_rules! test_many_ops{ - ($ratio_op:ident,$op:ident)=>{ - test_op!($ratio_op,$op,1,2,3,4); - test_op!($ratio_op,$op,1,2,-3,4); - test_op!($ratio_op,$op,-1,2,-3,4); - test_op!($ratio_op,$op,-1,-2,-3,4); - test_op!($ratio_op,$op,2,1,6,3); - test_op!($ratio_op,$op,-2,1,6,3); - test_op!($ratio_op,$op,2,-1,-6,3); - test_op!($ratio_op,$op,2,1,6,-3); - }; +macro_rules! test_many_ops { + ($ratio_op:ident,$op:ident) => { + test_op!($ratio_op, $op, 1, 2, 3, 4); + test_op!($ratio_op, $op, 1, 2, -3, 4); + test_op!($ratio_op, $op, -1, 2, -3, 4); + test_op!($ratio_op, $op, -1, -2, -3, 4); + test_op!($ratio_op, $op, 2, 1, 6, 3); + test_op!($ratio_op, $op, -2, 1, 6, 3); + test_op!($ratio_op, $op, 2, -1, -6, 3); + test_op!($ratio_op, $op, 2, 1, 6, -3); + }; } #[test] -fn test_lt(){ - test_many_ops!(lt_ratio,lt); +fn test_lt() { + test_many_ops!(lt_ratio, lt); } #[test] -fn test_gt(){ - test_many_ops!(gt_ratio,gt); +fn test_gt() { + test_many_ops!(gt_ratio, gt); } #[test] -fn test_le(){ - test_many_ops!(le_ratio,le); +fn test_le() { + test_many_ops!(le_ratio, le); } #[test] -fn test_ge(){ - test_many_ops!(ge_ratio,ge); +fn test_ge() { + test_many_ops!(ge_ratio, ge); } #[test] -fn test_eq(){ - test_many_ops!(eq_ratio,eq); +fn test_eq() { + test_many_ops!(eq_ratio, eq); } #[test] -fn test_partial_cmp(){ - test_many_ops!(partial_cmp_ratio,partial_cmp); +fn test_partial_cmp() { + test_many_ops!(partial_cmp_ratio, partial_cmp); } // #[test] diff --git a/lib/rbx_loader/src/lib.rs b/lib/rbx_loader/src/lib.rs index 06fbb07..40c41c3 100644 --- a/lib/rbx_loader/src/lib.rs +++ b/lib/rbx_loader/src/lib.rs @@ -1,107 +1,113 @@ -use std::io::Read; use rbx_dom_weak::WeakDom; +use std::io::Read; -mod rbx; mod mesh; mod primitives; +mod rbx; -pub mod data{ - pub struct RobloxMeshBytes(Vec); - impl RobloxMeshBytes{ - pub fn new(bytes:Vec)->Self{ - Self(bytes) - } - pub(crate) fn cursor(self)->std::io::Cursor>{ - std::io::Cursor::new(self.0) - } - } +pub mod data { + pub struct RobloxMeshBytes(Vec); + impl RobloxMeshBytes { + pub fn new(bytes: Vec) -> Self { + Self(bytes) + } + pub(crate) fn cursor(self) -> std::io::Cursor> { + std::io::Cursor::new(self.0) + } + } } -pub struct Model{ - dom:WeakDom, +pub struct Model { + dom: WeakDom, } -impl Model{ - fn new(dom:WeakDom)->Self{ - Self{dom} - } - pub fn into_place(self)->Place{ - let Self{mut dom}=self; - let context=roblox_emulator::context::Context::from_mut(&mut dom); - let services=context.convert_into_place(); - Place{dom,services} - } +impl Model { + fn new(dom: WeakDom) -> Self { + Self { dom } + } + pub fn into_place(self) -> Place { + let Self { mut dom } = self; + let context = roblox_emulator::context::Context::from_mut(&mut dom); + let services = context.convert_into_place(); + Place { dom, services } + } } -impl AsRef for Model{ - fn as_ref(&self)->&WeakDom{ - &self.dom - } +impl AsRef for Model { + fn as_ref(&self) -> &WeakDom { + &self.dom + } } -pub struct Place{ - dom:WeakDom, - services:roblox_emulator::context::Services, +pub struct Place { + dom: WeakDom, + services: roblox_emulator::context::Services, } -impl Place{ - fn new(dom:WeakDom)->Option{ - let context=roblox_emulator::context::Context::from_ref(&dom); - Some(Self{ - services:context.find_services()?, - dom, - }) - } - pub fn run_scripts(&mut self){ - let Place{dom,services}=self; - let runner=roblox_emulator::runner::Runner::new().unwrap(); - let context=roblox_emulator::context::Context::from_mut(dom); - let scripts=context.scripts(); - let runnable=runner.runnable_context_with_services(context,services).unwrap(); - for script in scripts{ - if let Err(e)=runnable.run_script(script){ - println!("runner error: {e}"); - } - } - } +impl Place { + fn new(dom: WeakDom) -> Option { + let context = roblox_emulator::context::Context::from_ref(&dom); + Some(Self { + services: context.find_services()?, + dom, + }) + } + pub fn run_scripts(&mut self) { + let Place { dom, services } = self; + let runner = roblox_emulator::runner::Runner::new().unwrap(); + let context = roblox_emulator::context::Context::from_mut(dom); + let scripts = context.scripts(); + let runnable = runner + .runnable_context_with_services(context, services) + .unwrap(); + for script in scripts { + if let Err(e) = runnable.run_script(script) { + println!("runner error: {e}"); + } + } + } } -impl AsRef for Place{ - fn as_ref(&self)->&WeakDom{ - &self.dom - } +impl AsRef for Place { + fn as_ref(&self) -> &WeakDom { + &self.dom + } } #[derive(Debug)] -pub enum ReadError{ - RbxBinary(rbx_binary::DecodeError), - RbxXml(rbx_xml::DecodeError), - Io(std::io::Error), - UnknownFileFormat, +pub enum ReadError { + RbxBinary(rbx_binary::DecodeError), + RbxXml(rbx_xml::DecodeError), + Io(std::io::Error), + UnknownFileFormat, } -impl std::fmt::Display for ReadError{ - fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ - write!(f,"{self:?}") - } +impl std::fmt::Display for ReadError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{self:?}") + } } -impl std::error::Error for ReadError{} +impl std::error::Error for ReadError {} -pub fn read(input:R)->Result{ - let mut buf=std::io::BufReader::new(input); - let peek=std::io::BufRead::fill_buf(&mut buf).map_err(ReadError::Io)?; - match &peek[0..8]{ - b"rbx_binary::from_reader(buf).map(Model::new).map_err(ReadError::RbxBinary), - b"rbx_xml::from_reader_default(buf).map(Model::new).map_err(ReadError::RbxXml), - _=>Err(ReadError::UnknownFileFormat), - } +pub fn read(input: R) -> Result { + let mut buf = std::io::BufReader::new(input); + let peek = std::io::BufRead::fill_buf(&mut buf).map_err(ReadError::Io)?; + match &peek[0..8] { + b" rbx_binary::from_reader(buf) + .map(Model::new) + .map_err(ReadError::RbxBinary), + b" rbx_xml::from_reader_default(buf) + .map(Model::new) + .map_err(ReadError::RbxXml), + _ => Err(ReadError::UnknownFileFormat), + } } //ConvertError -pub fn convert( - dom:impl AsRef, - acquire_render_config_id:AcquireRenderConfigId, - acquire_mesh_id:AcquireMeshId -)->rbx::PartialMap1 +pub fn convert( + dom: impl AsRef, + 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, + AcquireRenderConfigId: FnMut(Option<&str>) -> strafesnet_common::model::RenderConfigId, + AcquireMeshId: FnMut(&str) -> strafesnet_common::model::MeshId, { - rbx::convert(&dom.as_ref(),acquire_render_config_id,acquire_mesh_id) + rbx::convert(&dom.as_ref(), acquire_render_config_id, acquire_mesh_id) } diff --git a/lib/rbx_loader/src/mesh.rs b/lib/rbx_loader/src/mesh.rs index ae5d161..677ee42 100644 --- a/lib/rbx_loader/src/mesh.rs +++ b/lib/rbx_loader/src/mesh.rs @@ -1,210 +1,284 @@ use std::collections::HashMap; use rbx_mesh::mesh::{Vertex2, Vertex2Truncated}; -use strafesnet_common::{integer::vec3,model::{self, ColorId, IndexedVertex, NormalId, PolygonGroup, PolygonList, PositionId, TextureCoordinateId, VertexId}}; +use strafesnet_common::{ + integer::vec3, + 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) +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::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{self:?}") + } } -impl std::error::Error for Error{} +impl std::error::Error for Error {} -fn ingest_vertices2< - AcquirePosId, - AcquireTexId, - AcquireNormalId, - AcquireColorId, - AcquireVertexId, ->( - vertices:Vec, - 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,Error> +fn ingest_vertices2( + vertices: Vec, + 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, Error> where - AcquirePosId:FnMut([f32;3])->Result, - AcquireTexId:FnMut([f32;2])->TextureCoordinateId, - AcquireNormalId:FnMut([f32;3])->Result, - AcquireColorId:FnMut([f32;4])->ColorId, - AcquireVertexId:FnMut(IndexedVertex)->VertexId, + AcquirePosId: FnMut([f32; 3]) -> Result, + AcquireTexId: FnMut([f32; 2]) -> TextureCoordinateId, + AcquireNormalId: FnMut([f32; 3]) -> Result, + 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::>()?) + //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::>()?) } -fn ingest_vertices_truncated2< - AcquirePosId, - AcquireTexId, - AcquireNormalId, - AcquireVertexId, ->( - vertices:Vec, - 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,Error> +fn ingest_vertices_truncated2( + vertices: Vec, + 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, Error> where - AcquirePosId:FnMut([f32;3])->Result, - AcquireTexId:FnMut([f32;2])->TextureCoordinateId, - AcquireNormalId:FnMut([f32;3])->Result, - AcquireVertexId:FnMut(IndexedVertex)->VertexId, + AcquirePosId: FnMut([f32; 3]) -> Result, + AcquireTexId: FnMut([f32; 2]) -> TextureCoordinateId, + AcquireNormalId: FnMut([f32; 3]) -> Result, + 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::>()?) + //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::>()?) } fn ingest_faces2_lods3( - polygon_groups:&mut Vec, - vertex_id_map:&HashMap, - faces:&Vec, - lods:&Vec -){ - //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())) - )) + polygon_groups: &mut Vec, + vertex_id_map: &HashMap, + faces: &Vec, + lods: &Vec, +) { + //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{ - //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=vec3::try_from_f32_array(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=vec3::try_from_f32_array(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::>()?))); - }, - 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(), - }) +pub fn convert(roblox_mesh_bytes: crate::data::RobloxMeshBytes) -> Result { + //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 = vec3::try_from_f32_array(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 = vec3::try_from_f32_array(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::>()?, + ))); + } + 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(), + }) } diff --git a/lib/rbx_loader/src/primitives.rs b/lib/rbx_loader/src/primitives.rs index b70c859..8e238a5 100644 --- a/lib/rbx_loader/src/primitives.rs +++ b/lib/rbx_loader/src/primitives.rs @@ -1,510 +1,536 @@ -use strafesnet_common::model::{Color4,TextureCoordinate,Mesh,IndexedGraphicsGroup,IndexedPhysicsGroup,IndexedVertex,PolygonGroupId,PolygonGroup,PolygonList,IndexedVertexList,PositionId,TextureCoordinateId,NormalId,ColorId,VertexId,RenderConfigId}; -use strafesnet_common::integer::{vec3,Planar64Vec3}; +use strafesnet_common::integer::{vec3, Planar64Vec3}; +use strafesnet_common::model::{ + Color4, ColorId, IndexedGraphicsGroup, IndexedPhysicsGroup, IndexedVertex, IndexedVertexList, + Mesh, NormalId, PolygonGroup, PolygonGroupId, PolygonList, PositionId, RenderConfigId, + TextureCoordinate, TextureCoordinateId, VertexId, +}; #[derive(Debug)] -pub enum Primitives{ - Sphere, - Cube, - Cylinder, - Wedge, - CornerWedge, +pub enum Primitives { + Sphere, + Cube, + Cylinder, + Wedge, + CornerWedge, } -#[derive(Hash,PartialEq,Eq)] -pub enum CubeFace{ - Right, - Top, - Back, - Left, - Bottom, - Front, +#[derive(Hash, PartialEq, Eq)] +pub enum CubeFace { + Right, + Top, + Back, + Left, + Bottom, + Front, } -const CUBE_DEFAULT_TEXTURE_COORDS:[TextureCoordinate;4]=[ - TextureCoordinate::new(0.0,0.0), - TextureCoordinate::new(1.0,0.0), - TextureCoordinate::new(1.0,1.0), - TextureCoordinate::new(0.0,1.0), +const CUBE_DEFAULT_TEXTURE_COORDS: [TextureCoordinate; 4] = [ + TextureCoordinate::new(0.0, 0.0), + TextureCoordinate::new(1.0, 0.0), + TextureCoordinate::new(1.0, 1.0), + TextureCoordinate::new(0.0, 1.0), ]; -const CUBE_DEFAULT_VERTICES:[Planar64Vec3;8]=[ - vec3::int(-1,-1, 1),//0 left bottom back - vec3::int( 1,-1, 1),//1 right bottom back - vec3::int( 1, 1, 1),//2 right top back - vec3::int(-1, 1, 1),//3 left top back - vec3::int(-1, 1,-1),//4 left top front - vec3::int( 1, 1,-1),//5 right top front - vec3::int( 1,-1,-1),//6 right bottom front - vec3::int(-1,-1,-1),//7 left bottom front +const CUBE_DEFAULT_VERTICES: [Planar64Vec3; 8] = [ + vec3::int(-1, -1, 1), //0 left bottom back + vec3::int(1, -1, 1), //1 right bottom back + vec3::int(1, 1, 1), //2 right top back + vec3::int(-1, 1, 1), //3 left top back + vec3::int(-1, 1, -1), //4 left top front + vec3::int(1, 1, -1), //5 right top front + vec3::int(1, -1, -1), //6 right bottom front + vec3::int(-1, -1, -1), //7 left bottom front ]; -const CUBE_DEFAULT_NORMALS:[Planar64Vec3;6]=[ - vec3::int( 1, 0, 0),//CubeFace::Right - vec3::int( 0, 1, 0),//CubeFace::Top - vec3::int( 0, 0, 1),//CubeFace::Back - vec3::int(-1, 0, 0),//CubeFace::Left - vec3::int( 0,-1, 0),//CubeFace::Bottom - vec3::int( 0, 0,-1),//CubeFace::Front +const CUBE_DEFAULT_NORMALS: [Planar64Vec3; 6] = [ + vec3::int(1, 0, 0), //CubeFace::Right + vec3::int(0, 1, 0), //CubeFace::Top + vec3::int(0, 0, 1), //CubeFace::Back + vec3::int(-1, 0, 0), //CubeFace::Left + vec3::int(0, -1, 0), //CubeFace::Bottom + vec3::int(0, 0, -1), //CubeFace::Front ]; -const CUBE_DEFAULT_POLYS:[[[u32;3];4];6]=[ - // right (1, 0, 0) - [ - [6,2,0],//[vertex,tex,norm] - [5,1,0], - [2,0,0], - [1,3,0], - ], - // top (0, 1, 0) - [ - [5,3,1], - [4,2,1], - [3,1,1], - [2,0,1], - ], - // back (0, 0, 1) - [ - [0,3,2], - [1,2,2], - [2,1,2], - [3,0,2], - ], - // left (-1, 0, 0) - [ - [0,2,3], - [3,1,3], - [4,0,3], - [7,3,3], - ], - // bottom (0,-1, 0) - [ - [1,1,4], - [0,0,4], - [7,3,4], - [6,2,4], - ], - // front (0, 0,-1) - [ - [4,1,5], - [5,0,5], - [6,3,5], - [7,2,5], - ], +const CUBE_DEFAULT_POLYS: [[[u32; 3]; 4]; 6] = [ + // right (1, 0, 0) + [ + [6, 2, 0], //[vertex,tex,norm] + [5, 1, 0], + [2, 0, 0], + [1, 3, 0], + ], + // top (0, 1, 0) + [[5, 3, 1], [4, 2, 1], [3, 1, 1], [2, 0, 1]], + // back (0, 0, 1) + [[0, 3, 2], [1, 2, 2], [2, 1, 2], [3, 0, 2]], + // left (-1, 0, 0) + [[0, 2, 3], [3, 1, 3], [4, 0, 3], [7, 3, 3]], + // bottom (0,-1, 0) + [[1, 1, 4], [0, 0, 4], [7, 3, 4], [6, 2, 4]], + // front (0, 0,-1) + [[4, 1, 5], [5, 0, 5], [6, 3, 5], [7, 2, 5]], ]; -#[derive(Hash,PartialEq,Eq)] -pub enum WedgeFace{ - Right, - TopFront, - Back, - Left, - Bottom, +#[derive(Hash, PartialEq, Eq)] +pub enum WedgeFace { + Right, + TopFront, + Back, + Left, + Bottom, } -const WEDGE_DEFAULT_NORMALS:[Planar64Vec3;5]=[ - vec3::int( 1, 0, 0),//Wedge::Right - vec3::int( 0, 1,-1),//Wedge::TopFront - vec3::int( 0, 0, 1),//Wedge::Back - vec3::int(-1, 0, 0),//Wedge::Left - vec3::int( 0,-1, 0),//Wedge::Bottom +const WEDGE_DEFAULT_NORMALS: [Planar64Vec3; 5] = [ + vec3::int(1, 0, 0), //Wedge::Right + vec3::int(0, 1, -1), //Wedge::TopFront + vec3::int(0, 0, 1), //Wedge::Back + vec3::int(-1, 0, 0), //Wedge::Left + vec3::int(0, -1, 0), //Wedge::Bottom ]; /* local cornerWedgeVerticies = { - Vector3.new(-1/2,-1/2,-1/2),7 - Vector3.new(-1/2,-1/2, 1/2),0 - Vector3.new( 1/2,-1/2,-1/2),6 - Vector3.new( 1/2,-1/2, 1/2),1 - Vector3.new( 1/2, 1/2,-1/2),5 + Vector3.new(-1/2,-1/2,-1/2),7 + Vector3.new(-1/2,-1/2, 1/2),0 + Vector3.new( 1/2,-1/2,-1/2),6 + Vector3.new( 1/2,-1/2, 1/2),1 + Vector3.new( 1/2, 1/2,-1/2),5 } */ -#[derive(Hash,PartialEq,Eq)] -pub enum CornerWedgeFace{ - Right, - TopBack, - TopLeft, - Bottom, - Front, +#[derive(Hash, PartialEq, Eq)] +pub enum CornerWedgeFace { + Right, + TopBack, + TopLeft, + Bottom, + Front, } -const CORNERWEDGE_DEFAULT_NORMALS:[Planar64Vec3;5]=[ - vec3::int( 1, 0, 0),//CornerWedge::Right - vec3::int( 0, 1, 1),//CornerWedge::BackTop - vec3::int(-1, 1, 0),//CornerWedge::LeftTop - vec3::int( 0,-1, 0),//CornerWedge::Bottom - vec3::int( 0, 0,-1),//CornerWedge::Front +const CORNERWEDGE_DEFAULT_NORMALS: [Planar64Vec3; 5] = [ + vec3::int(1, 0, 0), //CornerWedge::Right + vec3::int(0, 1, 1), //CornerWedge::BackTop + vec3::int(-1, 1, 0), //CornerWedge::LeftTop + vec3::int(0, -1, 0), //CornerWedge::Bottom + vec3::int(0, 0, -1), //CornerWedge::Front ]; -pub fn unit_sphere(render:RenderConfigId)->Mesh{ - unit_cube(render) +pub fn unit_sphere(render: RenderConfigId) -> Mesh { + unit_cube(render) } #[derive(Default)] -pub struct CubeFaceDescription([Option;6]); -impl CubeFaceDescription{ - pub fn insert(&mut self,index:CubeFace,value:FaceDescription){ - self.0[index as usize]=Some(value); - } - pub fn pairs(self)->std::iter::FilterMap,6>>,impl FnMut((usize,Option))->Option<(usize,FaceDescription)>>{ - self.0.into_iter().enumerate().filter_map(|v|v.1.map(|u|(v.0,u))) - } +pub struct CubeFaceDescription([Option; 6]); +impl CubeFaceDescription { + pub fn insert(&mut self, index: CubeFace, value: FaceDescription) { + self.0[index as usize] = Some(value); + } + pub fn pairs( + self, + ) -> std::iter::FilterMap< + std::iter::Enumerate, 6>>, + impl FnMut((usize, Option)) -> Option<(usize, FaceDescription)>, + > { + self.0 + .into_iter() + .enumerate() + .filter_map(|v| v.1.map(|u| (v.0, u))) + } } -pub fn unit_cube(render:RenderConfigId)->Mesh{ - let mut t=CubeFaceDescription::default(); - t.insert(CubeFace::Right,FaceDescription::new_with_render_id(render)); - t.insert(CubeFace::Top,FaceDescription::new_with_render_id(render)); - t.insert(CubeFace::Back,FaceDescription::new_with_render_id(render)); - t.insert(CubeFace::Left,FaceDescription::new_with_render_id(render)); - t.insert(CubeFace::Bottom,FaceDescription::new_with_render_id(render)); - t.insert(CubeFace::Front,FaceDescription::new_with_render_id(render)); - generate_partial_unit_cube(t) +pub fn unit_cube(render: RenderConfigId) -> Mesh { + let mut t = CubeFaceDescription::default(); + t.insert(CubeFace::Right, FaceDescription::new_with_render_id(render)); + t.insert(CubeFace::Top, FaceDescription::new_with_render_id(render)); + t.insert(CubeFace::Back, FaceDescription::new_with_render_id(render)); + t.insert(CubeFace::Left, FaceDescription::new_with_render_id(render)); + t.insert( + CubeFace::Bottom, + FaceDescription::new_with_render_id(render), + ); + t.insert(CubeFace::Front, FaceDescription::new_with_render_id(render)); + generate_partial_unit_cube(t) } -pub fn unit_cylinder(render:RenderConfigId)->Mesh{ - //lmao - unit_cube(render) +pub fn unit_cylinder(render: RenderConfigId) -> Mesh { + //lmao + unit_cube(render) } #[derive(Default)] -pub struct WedgeFaceDescription([Option;5]); -impl WedgeFaceDescription{ - pub fn insert(&mut self,index:WedgeFace,value:FaceDescription){ - self.0[index as usize]=Some(value); - } - pub fn pairs(self)->std::iter::FilterMap,5>>,impl FnMut((usize,Option))->Option<(usize,FaceDescription)>>{ - self.0.into_iter().enumerate().filter_map(|v|v.1.map(|u|(v.0,u))) - } +pub struct WedgeFaceDescription([Option; 5]); +impl WedgeFaceDescription { + pub fn insert(&mut self, index: WedgeFace, value: FaceDescription) { + self.0[index as usize] = Some(value); + } + pub fn pairs( + self, + ) -> std::iter::FilterMap< + std::iter::Enumerate, 5>>, + impl FnMut((usize, Option)) -> Option<(usize, FaceDescription)>, + > { + self.0 + .into_iter() + .enumerate() + .filter_map(|v| v.1.map(|u| (v.0, u))) + } } -pub fn unit_wedge(render:RenderConfigId)->Mesh{ - let mut t=WedgeFaceDescription::default(); - t.insert(WedgeFace::Right,FaceDescription::new_with_render_id(render)); - t.insert(WedgeFace::TopFront,FaceDescription::new_with_render_id(render)); - t.insert(WedgeFace::Back,FaceDescription::new_with_render_id(render)); - t.insert(WedgeFace::Left,FaceDescription::new_with_render_id(render)); - t.insert(WedgeFace::Bottom,FaceDescription::new_with_render_id(render)); - generate_partial_unit_wedge(t) +pub fn unit_wedge(render: RenderConfigId) -> Mesh { + let mut t = WedgeFaceDescription::default(); + t.insert( + WedgeFace::Right, + FaceDescription::new_with_render_id(render), + ); + t.insert( + WedgeFace::TopFront, + FaceDescription::new_with_render_id(render), + ); + t.insert(WedgeFace::Back, FaceDescription::new_with_render_id(render)); + t.insert(WedgeFace::Left, FaceDescription::new_with_render_id(render)); + t.insert( + WedgeFace::Bottom, + FaceDescription::new_with_render_id(render), + ); + generate_partial_unit_wedge(t) } #[derive(Default)] -pub struct CornerWedgeFaceDescription([Option;5]); -impl CornerWedgeFaceDescription{ - pub fn insert(&mut self,index:CornerWedgeFace,value:FaceDescription){ - self.0[index as usize]=Some(value); - } - pub fn pairs(self)->std::iter::FilterMap,5>>,impl FnMut((usize,Option))->Option<(usize,FaceDescription)>>{ - self.0.into_iter().enumerate().filter_map(|v|v.1.map(|u|(v.0,u))) - } +pub struct CornerWedgeFaceDescription([Option; 5]); +impl CornerWedgeFaceDescription { + pub fn insert(&mut self, index: CornerWedgeFace, value: FaceDescription) { + self.0[index as usize] = Some(value); + } + pub fn pairs( + self, + ) -> std::iter::FilterMap< + std::iter::Enumerate, 5>>, + impl FnMut((usize, Option)) -> Option<(usize, FaceDescription)>, + > { + self.0 + .into_iter() + .enumerate() + .filter_map(|v| v.1.map(|u| (v.0, u))) + } } -pub fn unit_cornerwedge(render:RenderConfigId)->Mesh{ - let mut t=CornerWedgeFaceDescription::default(); - t.insert(CornerWedgeFace::Right,FaceDescription::new_with_render_id(render)); - t.insert(CornerWedgeFace::TopBack,FaceDescription::new_with_render_id(render)); - t.insert(CornerWedgeFace::TopLeft,FaceDescription::new_with_render_id(render)); - t.insert(CornerWedgeFace::Bottom,FaceDescription::new_with_render_id(render)); - t.insert(CornerWedgeFace::Front,FaceDescription::new_with_render_id(render)); - generate_partial_unit_cornerwedge(t) +pub fn unit_cornerwedge(render: RenderConfigId) -> Mesh { + let mut t = CornerWedgeFaceDescription::default(); + t.insert( + CornerWedgeFace::Right, + FaceDescription::new_with_render_id(render), + ); + t.insert( + CornerWedgeFace::TopBack, + FaceDescription::new_with_render_id(render), + ); + t.insert( + CornerWedgeFace::TopLeft, + FaceDescription::new_with_render_id(render), + ); + t.insert( + CornerWedgeFace::Bottom, + FaceDescription::new_with_render_id(render), + ); + t.insert( + CornerWedgeFace::Front, + FaceDescription::new_with_render_id(render), + ); + generate_partial_unit_cornerwedge(t) } #[derive(Clone)] -pub struct FaceDescription{ - pub render:RenderConfigId, - pub transform:glam::Affine2, - pub color:Color4, +pub struct FaceDescription { + pub render: RenderConfigId, + pub transform: glam::Affine2, + pub color: Color4, } -impl FaceDescription{ - pub fn new_with_render_id(render:RenderConfigId)->Self { - Self{ - render, - transform:glam::Affine2::IDENTITY, - color:Color4::new(1.0,1.0,1.0,0.0),//zero alpha to hide the default texture - } - } +impl FaceDescription { + pub fn new_with_render_id(render: RenderConfigId) -> Self { + Self { + render, + transform: glam::Affine2::IDENTITY, + color: Color4::new(1.0, 1.0, 1.0, 0.0), //zero alpha to hide the default texture + } + } } -pub fn generate_partial_unit_cube(face_descriptions:CubeFaceDescription)->Mesh{ - let mut generated_pos=Vec::new(); - let mut generated_tex=Vec::new(); - let mut generated_normal=Vec::new(); - let mut generated_color=Vec::new(); - let mut generated_vertices=Vec::new(); - let mut polygon_groups=Vec::new(); - let mut graphics_groups=Vec::new(); - let mut physics_group=IndexedPhysicsGroup::default(); - 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. - for (face_id,face_description) in face_descriptions.pairs(){ - //assume that scanning short lists is faster than hashing. - let transform_index=if let Some(transform_index)=transforms.iter().position(|&transform|transform==face_description.transform){ - transform_index - }else{ - //create new transform_index - let transform_index=transforms.len(); - transforms.push(face_description.transform); - generated_tex.extend(CUBE_DEFAULT_TEXTURE_COORDS.map(|tex| - face_description.transform.transform_point2(tex) - )); - transform_index - } as u32; - let color_index=if let Some(color_index)=generated_color.iter().position(|&color|color==face_description.color){ - color_index - }else{ - //create new color_index - let color_index=generated_color.len(); - generated_color.push(face_description.color); - color_index - } as u32; - //always push normal - let normal_index=generated_normal.len() as u32; - generated_normal.push(CUBE_DEFAULT_NORMALS[face_id]); - //push vertices as they are needed - let group_id=PolygonGroupId::new(polygon_groups.len() as u32); - polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(vec![ - CUBE_DEFAULT_POLYS[face_id].map(|tup|{ - let pos=CUBE_DEFAULT_VERTICES[tup[0] as usize]; - let pos_index=if let Some(pos_index)=generated_pos.iter().position(|&p|p==pos){ - pos_index - }else{ - //create new pos_index - let pos_index=generated_pos.len(); - generated_pos.push(pos); - pos_index - } as u32; - //always push vertex - let vertex=IndexedVertex{ - pos:PositionId::new(pos_index), - tex:TextureCoordinateId::new(tup[1]+4*transform_index), - normal:NormalId::new(normal_index), - color:ColorId::new(color_index), - }; - let vert_index=generated_vertices.len(); - generated_vertices.push(vertex); - VertexId::new(vert_index as u32) - }).to_vec(), - ]))); - graphics_groups.push(IndexedGraphicsGroup{ - render:face_description.render, - groups:vec![group_id], - }); - physics_group.groups.push(group_id); - } - Mesh{ - unique_pos:generated_pos, - unique_tex:generated_tex, - unique_normal:generated_normal, - unique_color:generated_color, - unique_vertices:generated_vertices, - polygon_groups, - graphics_groups, - physics_groups:vec![physics_group], - } +pub fn generate_partial_unit_cube(face_descriptions: CubeFaceDescription) -> Mesh { + let mut generated_pos = Vec::new(); + let mut generated_tex = Vec::new(); + let mut generated_normal = Vec::new(); + let mut generated_color = Vec::new(); + let mut generated_vertices = Vec::new(); + let mut polygon_groups = Vec::new(); + let mut graphics_groups = Vec::new(); + let mut physics_group = IndexedPhysicsGroup::default(); + 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. + for (face_id, face_description) in face_descriptions.pairs() { + //assume that scanning short lists is faster than hashing. + let transform_index = if let Some(transform_index) = transforms + .iter() + .position(|&transform| transform == face_description.transform) + { + transform_index + } else { + //create new transform_index + let transform_index = transforms.len(); + transforms.push(face_description.transform); + generated_tex.extend( + CUBE_DEFAULT_TEXTURE_COORDS + .map(|tex| face_description.transform.transform_point2(tex)), + ); + transform_index + } as u32; + let color_index = if let Some(color_index) = generated_color + .iter() + .position(|&color| color == face_description.color) + { + color_index + } else { + //create new color_index + let color_index = generated_color.len(); + generated_color.push(face_description.color); + color_index + } as u32; + //always push normal + let normal_index = generated_normal.len() as u32; + generated_normal.push(CUBE_DEFAULT_NORMALS[face_id]); + //push vertices as they are needed + let group_id = PolygonGroupId::new(polygon_groups.len() as u32); + polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(vec![ + CUBE_DEFAULT_POLYS[face_id] + .map(|tup| { + let pos = CUBE_DEFAULT_VERTICES[tup[0] as usize]; + let pos_index = + if let Some(pos_index) = generated_pos.iter().position(|&p| p == pos) { + pos_index + } else { + //create new pos_index + let pos_index = generated_pos.len(); + generated_pos.push(pos); + pos_index + } as u32; + //always push vertex + let vertex = IndexedVertex { + pos: PositionId::new(pos_index), + tex: TextureCoordinateId::new(tup[1] + 4 * transform_index), + normal: NormalId::new(normal_index), + color: ColorId::new(color_index), + }; + let vert_index = generated_vertices.len(); + generated_vertices.push(vertex); + VertexId::new(vert_index as u32) + }) + .to_vec(), + ]))); + graphics_groups.push(IndexedGraphicsGroup { + render: face_description.render, + groups: vec![group_id], + }); + physics_group.groups.push(group_id); + } + Mesh { + unique_pos: generated_pos, + unique_tex: generated_tex, + unique_normal: generated_normal, + unique_color: generated_color, + unique_vertices: generated_vertices, + polygon_groups, + graphics_groups, + physics_groups: vec![physics_group], + } } //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)->Mesh{ - let wedge_default_polys=[ - // right (1, 0, 0) - vec![ - [6,2,0],//[vertex,tex,norm] - [2,0,0], - [1,3,0], - ], - // FrontTop (0, 1, -1) - vec![ - [3,1,1], - [2,0,1], - [6,3,1], - [7,2,1], - ], - // back (0, 0, 1) - vec![ - [0,3,2], - [1,2,2], - [2,1,2], - [3,0,2], - ], - // left (-1, 0, 0) - vec![ - [0,2,3], - [3,1,3], - [7,3,3], - ], - // bottom (0,-1, 0) - vec![ - [1,1,4], - [0,0,4], - [7,3,4], - [6,2,4], - ], - ]; - let mut generated_pos=Vec::new(); - let mut generated_tex=Vec::new(); - let mut generated_normal=Vec::new(); - let mut generated_color=Vec::new(); - let mut generated_vertices=Vec::new(); - let mut polygon_groups=Vec::new(); - let mut graphics_groups=Vec::new(); - let mut physics_group=IndexedPhysicsGroup::default(); - 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. - for (face_id,face_description) in face_descriptions.pairs(){ - //assume that scanning short lists is faster than hashing. - let transform_index=if let Some(transform_index)=transforms.iter().position(|&transform|transform==face_description.transform){ - transform_index - }else{ - //create new transform_index - let transform_index=transforms.len(); - transforms.push(face_description.transform); - generated_tex.extend(CUBE_DEFAULT_TEXTURE_COORDS.map(|tex| - face_description.transform.transform_point2(tex) - )); - transform_index - } as u32; - let color_index=if let Some(color_index)=generated_color.iter().position(|&color|color==face_description.color){ - color_index - }else{ - //create new color_index - let color_index=generated_color.len(); - generated_color.push(face_description.color); - color_index - } as u32; - //always push normal - let normal_index=generated_normal.len() as u32; - generated_normal.push(WEDGE_DEFAULT_NORMALS[face_id]); - //push vertices as they are needed - let group_id=PolygonGroupId::new(polygon_groups.len() as u32); - polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(vec![ - wedge_default_polys[face_id].iter().map(|tup|{ - let pos=CUBE_DEFAULT_VERTICES[tup[0] as usize]; - let pos_index=if let Some(pos_index)=generated_pos.iter().position(|&p|p==pos){ - pos_index - }else{ - //create new pos_index - let pos_index=generated_pos.len(); - generated_pos.push(pos); - pos_index - } as u32; - //always push vertex - let vertex=IndexedVertex{ - pos:PositionId::new(pos_index), - tex:TextureCoordinateId::new(tup[1]+4*transform_index), - normal:NormalId::new(normal_index), - color:ColorId::new(color_index), - }; - let vert_index=generated_vertices.len(); - generated_vertices.push(vertex); - VertexId::new(vert_index as u32) - }).collect() - ]))); - graphics_groups.push(IndexedGraphicsGroup{ - render:face_description.render, - groups:vec![group_id], - }); - physics_group.groups.push(group_id); - } - Mesh{ - unique_pos:generated_pos, - unique_tex:generated_tex, - unique_normal:generated_normal, - unique_color:generated_color, - unique_vertices:generated_vertices, - polygon_groups, - graphics_groups, - physics_groups:vec![physics_group], - } +pub fn generate_partial_unit_wedge(face_descriptions: WedgeFaceDescription) -> Mesh { + let wedge_default_polys = [ + // right (1, 0, 0) + vec![ + [6, 2, 0], //[vertex,tex,norm] + [2, 0, 0], + [1, 3, 0], + ], + // FrontTop (0, 1, -1) + vec![[3, 1, 1], [2, 0, 1], [6, 3, 1], [7, 2, 1]], + // back (0, 0, 1) + vec![[0, 3, 2], [1, 2, 2], [2, 1, 2], [3, 0, 2]], + // left (-1, 0, 0) + vec![[0, 2, 3], [3, 1, 3], [7, 3, 3]], + // bottom (0,-1, 0) + vec![[1, 1, 4], [0, 0, 4], [7, 3, 4], [6, 2, 4]], + ]; + let mut generated_pos = Vec::new(); + let mut generated_tex = Vec::new(); + let mut generated_normal = Vec::new(); + let mut generated_color = Vec::new(); + let mut generated_vertices = Vec::new(); + let mut polygon_groups = Vec::new(); + let mut graphics_groups = Vec::new(); + let mut physics_group = IndexedPhysicsGroup::default(); + 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. + for (face_id, face_description) in face_descriptions.pairs() { + //assume that scanning short lists is faster than hashing. + let transform_index = if let Some(transform_index) = transforms + .iter() + .position(|&transform| transform == face_description.transform) + { + transform_index + } else { + //create new transform_index + let transform_index = transforms.len(); + transforms.push(face_description.transform); + generated_tex.extend( + CUBE_DEFAULT_TEXTURE_COORDS + .map(|tex| face_description.transform.transform_point2(tex)), + ); + transform_index + } as u32; + let color_index = if let Some(color_index) = generated_color + .iter() + .position(|&color| color == face_description.color) + { + color_index + } else { + //create new color_index + let color_index = generated_color.len(); + generated_color.push(face_description.color); + color_index + } as u32; + //always push normal + let normal_index = generated_normal.len() as u32; + generated_normal.push(WEDGE_DEFAULT_NORMALS[face_id]); + //push vertices as they are needed + let group_id = PolygonGroupId::new(polygon_groups.len() as u32); + polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(vec![ + wedge_default_polys[face_id] + .iter() + .map(|tup| { + let pos = CUBE_DEFAULT_VERTICES[tup[0] as usize]; + let pos_index = + if let Some(pos_index) = generated_pos.iter().position(|&p| p == pos) { + pos_index + } else { + //create new pos_index + let pos_index = generated_pos.len(); + generated_pos.push(pos); + pos_index + } as u32; + //always push vertex + let vertex = IndexedVertex { + pos: PositionId::new(pos_index), + tex: TextureCoordinateId::new(tup[1] + 4 * transform_index), + normal: NormalId::new(normal_index), + color: ColorId::new(color_index), + }; + let vert_index = generated_vertices.len(); + generated_vertices.push(vertex); + VertexId::new(vert_index as u32) + }) + .collect(), + ]))); + graphics_groups.push(IndexedGraphicsGroup { + render: face_description.render, + groups: vec![group_id], + }); + physics_group.groups.push(group_id); + } + Mesh { + unique_pos: generated_pos, + unique_tex: generated_tex, + unique_normal: generated_normal, + unique_color: generated_color, + unique_vertices: generated_vertices, + polygon_groups, + graphics_groups, + physics_groups: vec![physics_group], + } } -pub fn generate_partial_unit_cornerwedge(face_descriptions:CornerWedgeFaceDescription)->Mesh{ - let cornerwedge_default_polys=[ - // right (1, 0, 0) - vec![ - [6,2,0],//[vertex,tex,norm] - [5,1,0], - [1,3,0], - ], - // BackTop (0, 1, 1) - vec![ - [5,3,1], - [0,1,1], - [1,0,1], - ], - // LeftTop (-1, 1, 0) - vec![ - [5,3,2], - [7,2,2], - [0,1,2], - ], - // bottom (0,-1, 0) - vec![ - [1,1,3], - [0,0,3], - [7,3,3], - [6,2,3], - ], - // front (0, 0,-1) - vec![ - [5,0,4], - [6,3,4], - [7,2,4], - ], - ]; - let mut generated_pos=Vec::new(); - let mut generated_tex=Vec::new(); - let mut generated_normal=Vec::new(); - let mut generated_color=Vec::new(); - let mut generated_vertices=Vec::new(); - let mut polygon_groups=Vec::new(); - let mut graphics_groups=Vec::new(); - let mut physics_group=IndexedPhysicsGroup::default(); - 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. - for (face_id,face_description) in face_descriptions.pairs(){ - //assume that scanning short lists is faster than hashing. - let transform_index=if let Some(transform_index)=transforms.iter().position(|&transform|transform==face_description.transform){ - transform_index - }else{ - //create new transform_index - let transform_index=transforms.len(); - transforms.push(face_description.transform); - generated_tex.extend(CUBE_DEFAULT_TEXTURE_COORDS.map(|tex| - face_description.transform.transform_point2(tex) - )); - transform_index - } as u32; - let color_index=if let Some(color_index)=generated_color.iter().position(|&color|color==face_description.color){ - color_index - }else{ - //create new color_index - let color_index=generated_color.len(); - generated_color.push(face_description.color); - color_index - } as u32; - //always push normal - let normal_index=generated_normal.len() as u32; - generated_normal.push(CORNERWEDGE_DEFAULT_NORMALS[face_id]); - //push vertices as they are needed - let group_id=PolygonGroupId::new(polygon_groups.len() as u32); - polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(vec![ - cornerwedge_default_polys[face_id].iter().map(|tup|{ - let pos=CUBE_DEFAULT_VERTICES[tup[0] as usize]; - let pos_index=if let Some(pos_index)=generated_pos.iter().position(|&p|p==pos){ - pos_index - }else{ - //create new pos_index - let pos_index=generated_pos.len(); - generated_pos.push(pos); - pos_index - } as u32; - //always push vertex - let vertex=IndexedVertex{ - pos:PositionId::new(pos_index), - tex:TextureCoordinateId::new(tup[1]+4*transform_index), - normal:NormalId::new(normal_index), - color:ColorId::new(color_index), - }; - let vert_index=generated_vertices.len(); - generated_vertices.push(vertex); - VertexId::new(vert_index as u32) - }).collect(), - ]))); - graphics_groups.push(IndexedGraphicsGroup{ - render:face_description.render, - groups:vec![group_id], - }); - physics_group.groups.push(group_id); - } - Mesh{ - unique_pos:generated_pos, - unique_tex:generated_tex, - unique_normal:generated_normal, - unique_color:generated_color, - unique_vertices:generated_vertices, - polygon_groups, - graphics_groups, - physics_groups:vec![physics_group], - } +pub fn generate_partial_unit_cornerwedge(face_descriptions: CornerWedgeFaceDescription) -> Mesh { + let cornerwedge_default_polys = [ + // right (1, 0, 0) + vec![ + [6, 2, 0], //[vertex,tex,norm] + [5, 1, 0], + [1, 3, 0], + ], + // BackTop (0, 1, 1) + vec![[5, 3, 1], [0, 1, 1], [1, 0, 1]], + // LeftTop (-1, 1, 0) + vec![[5, 3, 2], [7, 2, 2], [0, 1, 2]], + // bottom (0,-1, 0) + vec![[1, 1, 3], [0, 0, 3], [7, 3, 3], [6, 2, 3]], + // front (0, 0,-1) + vec![[5, 0, 4], [6, 3, 4], [7, 2, 4]], + ]; + let mut generated_pos = Vec::new(); + let mut generated_tex = Vec::new(); + let mut generated_normal = Vec::new(); + let mut generated_color = Vec::new(); + let mut generated_vertices = Vec::new(); + let mut polygon_groups = Vec::new(); + let mut graphics_groups = Vec::new(); + let mut physics_group = IndexedPhysicsGroup::default(); + 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. + for (face_id, face_description) in face_descriptions.pairs() { + //assume that scanning short lists is faster than hashing. + let transform_index = if let Some(transform_index) = transforms + .iter() + .position(|&transform| transform == face_description.transform) + { + transform_index + } else { + //create new transform_index + let transform_index = transforms.len(); + transforms.push(face_description.transform); + generated_tex.extend( + CUBE_DEFAULT_TEXTURE_COORDS + .map(|tex| face_description.transform.transform_point2(tex)), + ); + transform_index + } as u32; + let color_index = if let Some(color_index) = generated_color + .iter() + .position(|&color| color == face_description.color) + { + color_index + } else { + //create new color_index + let color_index = generated_color.len(); + generated_color.push(face_description.color); + color_index + } as u32; + //always push normal + let normal_index = generated_normal.len() as u32; + generated_normal.push(CORNERWEDGE_DEFAULT_NORMALS[face_id]); + //push vertices as they are needed + let group_id = PolygonGroupId::new(polygon_groups.len() as u32); + polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(vec![ + cornerwedge_default_polys[face_id] + .iter() + .map(|tup| { + let pos = CUBE_DEFAULT_VERTICES[tup[0] as usize]; + let pos_index = + if let Some(pos_index) = generated_pos.iter().position(|&p| p == pos) { + pos_index + } else { + //create new pos_index + let pos_index = generated_pos.len(); + generated_pos.push(pos); + pos_index + } as u32; + //always push vertex + let vertex = IndexedVertex { + pos: PositionId::new(pos_index), + tex: TextureCoordinateId::new(tup[1] + 4 * transform_index), + normal: NormalId::new(normal_index), + color: ColorId::new(color_index), + }; + let vert_index = generated_vertices.len(); + generated_vertices.push(vertex); + VertexId::new(vert_index as u32) + }) + .collect(), + ]))); + graphics_groups.push(IndexedGraphicsGroup { + render: face_description.render, + groups: vec![group_id], + }); + physics_group.groups.push(group_id); + } + Mesh { + unique_pos: generated_pos, + unique_tex: generated_tex, + unique_normal: generated_normal, + unique_color: generated_color, + unique_vertices: generated_vertices, + polygon_groups, + graphics_groups, + physics_groups: vec![physics_group], + } } diff --git a/lib/rbx_loader/src/rbx.rs b/lib/rbx_loader/src/rbx.rs index 0535724..f8d22db 100644 --- a/lib/rbx_loader/src/rbx.rs +++ b/lib/rbx_loader/src/rbx.rs @@ -1,111 +1,166 @@ -use std::collections::HashMap; use crate::primitives; -use strafesnet_common::map; -use strafesnet_common::model; +use std::collections::HashMap; +use strafesnet_common::gameplay_attributes as attr; use strafesnet_common::gameplay_modes; use strafesnet_common::gameplay_style; -use strafesnet_common::gameplay_attributes as attr; -use strafesnet_common::integer::{self,vec3,Planar64,Planar64Vec3,Planar64Mat3,Planar64Affine3}; +use strafesnet_common::integer::{ + self, vec3, Planar64, Planar64Affine3, Planar64Mat3, Planar64Vec3, +}; +use strafesnet_common::map; +use strafesnet_common::model; use strafesnet_common::model::RenderConfigId; use strafesnet_common::updatable::Updatable; fn class_is_a(class: &str, superclass: &str) -> bool { - if class==superclass { - return true - } - let class_descriptor=rbx_reflection_database::get().classes.get(class); - if let Some(descriptor) = &class_descriptor { - if let Some(class_super) = &descriptor.superclass { - return class_is_a(&class_super, superclass) - } - } - false + if class == superclass { + return true; + } + let class_descriptor = rbx_reflection_database::get().classes.get(class); + if let Some(descriptor) = &class_descriptor { + if let Some(class_super) = &descriptor.superclass { + return class_is_a(&class_super, superclass); + } + } + false } -fn recursive_collect_superclass(objects: &mut std::vec::Vec,dom: &rbx_dom_weak::WeakDom, instance: &rbx_dom_weak::Instance, superclass: &str){ - let mut stack=vec![instance]; - while let Some(item)=stack.pop(){ - for &referent in item.children(){ - if let Some(c)=dom.get_by_ref(referent){ - if class_is_a(c.class.as_str(),superclass){ - objects.push(c.referent());//copy ref - } - stack.push(c); - } - } - } +fn recursive_collect_superclass( + objects: &mut std::vec::Vec, + dom: &rbx_dom_weak::WeakDom, + instance: &rbx_dom_weak::Instance, + superclass: &str, +) { + let mut stack = vec![instance]; + while let Some(item) = stack.pop() { + for &referent in item.children() { + if let Some(c) = dom.get_by_ref(referent) { + if class_is_a(c.class.as_str(), superclass) { + objects.push(c.referent()); //copy ref + } + stack.push(c); + } + } + } } -fn planar64_affine3_from_roblox(cf:&rbx_dom_weak::types::CFrame,size:&rbx_dom_weak::types::Vector3)->Planar64Affine3{ - Planar64Affine3::new( - Planar64Mat3::from_cols([ - vec3::try_from_f32_array([cf.orientation.x.x,cf.orientation.y.x,cf.orientation.z.x]).unwrap() - *integer::try_from_f32(size.x/2.0).unwrap(), - vec3::try_from_f32_array([cf.orientation.x.y,cf.orientation.y.y,cf.orientation.z.y]).unwrap() - *integer::try_from_f32(size.y/2.0).unwrap(), - vec3::try_from_f32_array([cf.orientation.x.z,cf.orientation.y.z,cf.orientation.z.z]).unwrap() - *integer::try_from_f32(size.z/2.0).unwrap(), - ].map(|t|t.fix_1())), - vec3::try_from_f32_array([cf.position.x,cf.position.y,cf.position.z]).unwrap() - ) +fn planar64_affine3_from_roblox( + cf: &rbx_dom_weak::types::CFrame, + size: &rbx_dom_weak::types::Vector3, +) -> Planar64Affine3 { + Planar64Affine3::new( + Planar64Mat3::from_cols( + [ + vec3::try_from_f32_array([ + cf.orientation.x.x, + cf.orientation.y.x, + cf.orientation.z.x, + ]) + .unwrap() + * integer::try_from_f32(size.x / 2.0).unwrap(), + vec3::try_from_f32_array([ + cf.orientation.x.y, + cf.orientation.y.y, + cf.orientation.z.y, + ]) + .unwrap() + * integer::try_from_f32(size.y / 2.0).unwrap(), + vec3::try_from_f32_array([ + cf.orientation.x.z, + cf.orientation.y.z, + cf.orientation.z.z, + ]) + .unwrap() + * integer::try_from_f32(size.z / 2.0).unwrap(), + ] + .map(|t| t.fix_1()), + ), + vec3::try_from_f32_array([cf.position.x, cf.position.y, cf.position.z]).unwrap(), + ) } -struct ModeBuilder{ - mode:gameplay_modes::Mode, - final_stage_id_from_builder_stage_id:HashMap, +struct ModeBuilder { + mode: gameplay_modes::Mode, + final_stage_id_from_builder_stage_id: HashMap, } #[derive(Default)] -struct ModesBuilder{ - modes:HashMap, - stages:HashMap>, - mode_updates:Vec<(gameplay_modes::ModeId,gameplay_modes::ModeUpdate)>, - stage_updates:Vec<(gameplay_modes::ModeId,gameplay_modes::StageId,gameplay_modes::StageUpdate)>, +struct ModesBuilder { + modes: HashMap, + stages: + HashMap>, + 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_key(|&(mode_id,_)|mode_id); - let (mut modes,final_mode_id_from_builder_mode_id):(Vec,HashMap) - =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| +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_key(|&(mode_id, _)| mode_id); + let (mut modes, final_mode_id_from_builder_mode_id): ( + Vec, + HashMap, + ) = 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)) @@ -115,475 +170,575 @@ impl ModesBuilder{ 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(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:model::ModelId,modes_builder:&mut ModesBuilder,wormhole_in_model_to_id:&mut HashMap,wormhole_id_to_out_model:&mut HashMap)->attr::CollisionAttributes{ - 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_intersecting=false; - match name{ - "Water"=>{ - force_can_collide=false; - //TODO: read stupid CustomPhysicalProperties - intersecting.water=Some(attr::IntersectingWater{density:Planar64::ONE,viscosity:Planar64::ONE/10,velocity}); - }, - "Accelerator"=>{ - //although the new game supports collidable accelerators, this is a roblox compatability map loader - force_can_collide=false; - general.accelerator=Some(attr::Accelerator{acceleration:velocity}); - }, - // "UnorderedCheckpoint"=>general.teleport_behaviour=Some(model::TeleportBehaviour::StageElement(attr::StageElement{ - // mode_id:0, - // stage_id:0, - // force:false, - // behaviour:model::StageElementBehaviour::Unordered - // })), - "SetVelocity"=>general.trajectory=Some(attr::SetTrajectory::Velocity(velocity)), - "MapStart"=>{ - force_can_collide=false; - force_intersecting=true; - modes_builder.insert_mode( - gameplay_modes::ModeId::MAIN, - gameplay_modes::Mode::empty( - 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,None),//roblox does not know which stage the platform belongs to - ), - ); - }, - other=>{ - 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::().unwrap()), - gameplay_modes::Mode::empty( - 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::().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){ - force_intersecting=true; - let stage_id=gameplay_modes::StageId::new(captures[3].parse::().unwrap()); - let stage_element=gameplay_modes::StageElement::new( - //stage_id: - stage_id, - //force: - match captures.get(1){ - Some(m)=>m.as_str()=="Force", - None=>false, - }, - //behaviour: - match &captures[2]{ - "Spawn"=>{ - modes_builder.insert_stage( - gameplay_modes::ModeId::MAIN, - stage_id, - gameplay_modes::Stage::empty(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 - //NOT a decoration - "Trigger"=>{force_can_collide=false;gameplay_modes::StageElementBehaviour::Trigger}, - "Teleport"=>{force_can_collide=false;gameplay_modes::StageElementBehaviour::Teleport}, - "Platform"=>gameplay_modes::StageElementBehaviour::Platform, - _=>panic!("regex1[2] messed up bad"), - }, - None - ); - modes_builder.push_mode_update( - 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::element( - model_id, - //jump_limit: - gameplay_modes::StageElement::new( - gameplay_modes::StageId::FIRST, - false, - gameplay_modes::StageElementBehaviour::Check, - Some(captures[2].parse::().unwrap()) - ) - ), - ), - "WormholeIn"=>{ - force_can_collide=false; - force_intersecting=true; - assert!(wormhole_in_model_to_id.insert(model_id,captures[2].parse::().unwrap()).is_none(),"Impossible"); - }, - _=>panic!("regex2[1] messed up bad"), - } - }else if let Some(captures)=lazy_regex::regex!(r"^Bonus(Finish|Anticheat)(\d+)$") - .captures(other){ - force_can_collide=false; - force_intersecting=true; - modes_builder.push_mode_update( - gameplay_modes::ModeId::new(captures[2].parse::().unwrap()), - gameplay_modes::ModeUpdate::zone( - model_id, - //zone: - match &captures[1]{ - "Finish"=>gameplay_modes::Zone::Finish, - "Anticheat"=>gameplay_modes::Zone::Anticheat, - _=>panic!("regex3[1] messed up bad"), - }, - ), - ); - } - // else if let Some(captures)=lazy_regex::regex!(r"^Stage(\d+)OrderedCheckpoint(\d+)$") - // .captures(other){ - // match &captures[1]{ - // "OrderedCheckpoint"=>modes_builder.push_stage_update( - // gameplay_modes::ModeId::MAIN, - // gameplay_modes::StageId::new(0), - // gameplay_modes::StageUpdate::ordered_checkpoint(captures[2].parse::().unwrap()), - // ), - // _=>panic!("regex3[1] messed up bad"), - // } - // } - } - } - //need some way to skip this - if velocity!=vec3::ZERO{ - general.booster=Some(attr::Booster::Velocity(velocity)); - } - match force_can_collide{ - true=>{ - match name{ - "Bounce"=>contacting.contact_behaviour=Some(attr::ContactingBehaviour::Elastic(u32::MAX)), - "Surf"=>contacting.contact_behaviour=Some(attr::ContactingBehaviour::Surf), - "Ladder"=>contacting.contact_behaviour=Some(attr::ContactingBehaviour::Ladder(attr::ContactingLadder{sticky:true})), - _=>(), - } - attr::CollisionAttributes::Contact(attr::ContactAttributes{contacting,general}) - }, - false=>if force_intersecting - ||general.any() - ||intersecting.any() - { - attr::CollisionAttributes::Intersect(attr::IntersectAttributes{intersecting,general}) - }else{ - attr::CollisionAttributes::Decoration - }, - } -} - -#[derive(Clone,Copy,PartialEq)] -struct RobloxTextureTransform{ - offset_u:f32, - offset_v:f32, - scale_u:f32, - scale_v:f32, -} -impl std::cmp::Eq for RobloxTextureTransform{}//???? -impl std::default::Default for RobloxTextureTransform{ - fn default()->Self{ - Self{offset_u:0.0,offset_v:0.0,scale_u:1.0,scale_v:1.0} + 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)); } } -impl std::hash::Hash for RobloxTextureTransform{ - fn hash(&self,state:&mut H) { - self.offset_u.to_ne_bytes().hash(state); - self.offset_v.to_ne_bytes().hash(state); - self.scale_u.to_ne_bytes().hash(state); - self.scale_v.to_ne_bytes().hash(state); - } +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, + wormhole_id_to_out_model: &mut HashMap, +) -> attr::CollisionAttributes { + 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_intersecting = false; + match name { + "Water" => { + force_can_collide = false; + //TODO: read stupid CustomPhysicalProperties + intersecting.water = Some(attr::IntersectingWater { + density: Planar64::ONE, + viscosity: Planar64::ONE / 10, + velocity, + }); + } + "Accelerator" => { + //although the new game supports collidable accelerators, this is a roblox compatability map loader + force_can_collide = false; + general.accelerator = Some(attr::Accelerator { + acceleration: velocity, + }); + } + // "UnorderedCheckpoint"=>general.teleport_behaviour=Some(model::TeleportBehaviour::StageElement(attr::StageElement{ + // mode_id:0, + // stage_id:0, + // force:false, + // behaviour:model::StageElementBehaviour::Unordered + // })), + "SetVelocity" => general.trajectory = Some(attr::SetTrajectory::Velocity(velocity)), + "MapStart" => { + force_can_collide = false; + force_intersecting = true; + modes_builder.insert_mode( + gameplay_modes::ModeId::MAIN, + gameplay_modes::Mode::empty( + 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, + None, + ), //roblox does not know which stage the platform belongs to + ), + ); + } + other => { + 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::().unwrap()), + gameplay_modes::Mode::empty( + 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::().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) + { + force_intersecting = true; + let stage_id = gameplay_modes::StageId::new(captures[3].parse::().unwrap()); + let stage_element = gameplay_modes::StageElement::new( + //stage_id: + stage_id, + //force: + match captures.get(1) { + Some(m) => m.as_str() == "Force", + None => false, + }, + //behaviour: + match &captures[2] { + "Spawn" => { + modes_builder.insert_stage( + gameplay_modes::ModeId::MAIN, + stage_id, + gameplay_modes::Stage::empty(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 + //NOT a decoration + "Trigger" => { + force_can_collide = false; + gameplay_modes::StageElementBehaviour::Trigger + } + "Teleport" => { + force_can_collide = false; + gameplay_modes::StageElementBehaviour::Teleport + } + "Platform" => gameplay_modes::StageElementBehaviour::Platform, + _ => panic!("regex1[2] messed up bad"), + }, + None, + ); + modes_builder.push_mode_update( + 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::element( + model_id, + //jump_limit: + gameplay_modes::StageElement::new( + gameplay_modes::StageId::FIRST, + false, + gameplay_modes::StageElementBehaviour::Check, + Some(captures[2].parse::().unwrap()), + ), + ), + ), + "WormholeIn" => { + force_can_collide = false; + force_intersecting = true; + assert!( + wormhole_in_model_to_id + .insert(model_id, captures[2].parse::().unwrap()) + .is_none(), + "Impossible" + ); + } + _ => panic!("regex2[1] messed up bad"), + } + } else if let Some(captures) = + lazy_regex::regex!(r"^Bonus(Finish|Anticheat)(\d+)$").captures(other) + { + force_can_collide = false; + force_intersecting = true; + modes_builder.push_mode_update( + gameplay_modes::ModeId::new(captures[2].parse::().unwrap()), + gameplay_modes::ModeUpdate::zone( + model_id, + //zone: + match &captures[1] { + "Finish" => gameplay_modes::Zone::Finish, + "Anticheat" => gameplay_modes::Zone::Anticheat, + _ => panic!("regex3[1] messed up bad"), + }, + ), + ); + } + // else if let Some(captures)=lazy_regex::regex!(r"^Stage(\d+)OrderedCheckpoint(\d+)$") + // .captures(other){ + // match &captures[1]{ + // "OrderedCheckpoint"=>modes_builder.push_stage_update( + // gameplay_modes::ModeId::MAIN, + // gameplay_modes::StageId::new(0), + // gameplay_modes::StageUpdate::ordered_checkpoint(captures[2].parse::().unwrap()), + // ), + // _=>panic!("regex3[1] messed up bad"), + // } + // } + } + } + //need some way to skip this + if velocity != vec3::ZERO { + general.booster = Some(attr::Booster::Velocity(velocity)); + } + match force_can_collide { + true => { + match name { + "Bounce" => { + contacting.contact_behaviour = + Some(attr::ContactingBehaviour::Elastic(u32::MAX)) + } + "Surf" => contacting.contact_behaviour = Some(attr::ContactingBehaviour::Surf), + "Ladder" => { + contacting.contact_behaviour = + Some(attr::ContactingBehaviour::Ladder(attr::ContactingLadder { + sticky: true, + })) + } + _ => (), + } + attr::CollisionAttributes::Contact(attr::ContactAttributes { + contacting, + general, + }) + } + false => { + if force_intersecting || general.any() || intersecting.any() { + attr::CollisionAttributes::Intersect(attr::IntersectAttributes { + intersecting, + general, + }) + } else { + attr::CollisionAttributes::Decoration + } + } + } } -#[derive(Clone,PartialEq)] -struct RobloxFaceTextureDescription{ - render:RenderConfigId, - color:glam::Vec4, - transform:RobloxTextureTransform, + +#[derive(Clone, Copy, PartialEq)] +struct RobloxTextureTransform { + offset_u: f32, + offset_v: f32, + scale_u: f32, + scale_v: f32, } -impl std::cmp::Eq for RobloxFaceTextureDescription{}//???? -impl std::hash::Hash for RobloxFaceTextureDescription{ - fn hash(&self,state:&mut H){ - self.render.hash(state); - self.transform.hash(state); - for &el in self.color.as_ref().iter(){ +impl std::cmp::Eq for RobloxTextureTransform {} //???? +impl std::default::Default for RobloxTextureTransform { + fn default() -> Self { + Self { + offset_u: 0.0, + offset_v: 0.0, + scale_u: 1.0, + scale_v: 1.0, + } + } +} +impl std::hash::Hash for RobloxTextureTransform { + fn hash(&self, state: &mut H) { + self.offset_u.to_ne_bytes().hash(state); + self.offset_v.to_ne_bytes().hash(state); + self.scale_u.to_ne_bytes().hash(state); + self.scale_v.to_ne_bytes().hash(state); + } +} +#[derive(Clone, PartialEq)] +struct RobloxFaceTextureDescription { + render: RenderConfigId, + color: glam::Vec4, + transform: RobloxTextureTransform, +} +impl std::cmp::Eq for RobloxFaceTextureDescription {} //???? +impl std::hash::Hash for RobloxFaceTextureDescription { + fn hash(&self, state: &mut H) { + self.render.hash(state); + self.transform.hash(state); + for &el in self.color.as_ref().iter() { el.to_ne_bytes().hash(state); } } } -impl RobloxFaceTextureDescription{ - fn to_face_description(&self)->primitives::FaceDescription{ - primitives::FaceDescription{ - render:self.render, - transform:glam::Affine2::from_translation( - glam::vec2(self.transform.offset_u,self.transform.offset_v) - ) - *glam::Affine2::from_scale( - glam::vec2(self.transform.scale_u,self.transform.scale_v) - ), - color:self.color, - } - } +impl RobloxFaceTextureDescription { + fn to_face_description(&self) -> primitives::FaceDescription { + primitives::FaceDescription { + render: self.render, + transform: glam::Affine2::from_translation(glam::vec2( + self.transform.offset_u, + self.transform.offset_v, + )) * glam::Affine2::from_scale(glam::vec2( + self.transform.scale_u, + self.transform.scale_v, + )), + color: self.color, + } + } } -type RobloxPartDescription=[Option;6]; -type RobloxWedgeDescription=[Option;5]; -type RobloxCornerWedgeDescription=[Option;5]; -#[derive(Clone,Eq,Hash,PartialEq)] -enum RobloxBasePartDescription{ - Sphere(RobloxPartDescription), - Part(RobloxPartDescription), - Cylinder(RobloxPartDescription), - Wedge(RobloxWedgeDescription), - CornerWedge(RobloxCornerWedgeDescription), +type RobloxPartDescription = [Option; 6]; +type RobloxWedgeDescription = [Option; 5]; +type RobloxCornerWedgeDescription = [Option; 5]; +#[derive(Clone, Eq, Hash, PartialEq)] +enum RobloxBasePartDescription { + Sphere(RobloxPartDescription), + Part(RobloxPartDescription), + Cylinder(RobloxPartDescription), + Wedge(RobloxWedgeDescription), + CornerWedge(RobloxCornerWedgeDescription), } -enum Shape{ - Primitive(primitives::Primitives), - MeshPart, +enum Shape { + Primitive(primitives::Primitives), + MeshPart, } -enum MeshAvailability{ - Immediate, - Deferred(RenderConfigId), +enum MeshAvailability { + Immediate, + Deferred(RenderConfigId), } -struct DeferredModelDeferredAttributes{ - render:RenderConfigId, - model:ModelDeferredAttributes, +struct DeferredModelDeferredAttributes { + render: RenderConfigId, + model: ModelDeferredAttributes, } -struct ModelDeferredAttributes{ - mesh:model::MeshId, - deferred_attributes:GetAttributesArgs, - color:model::Color4,//transparency is in here - transform:Planar64Affine3, +struct ModelDeferredAttributes { + mesh: model::MeshId, + deferred_attributes: GetAttributesArgs, + color: model::Color4, //transparency is in here + transform: Planar64Affine3, } -struct ModelOwnedAttributes{ - mesh:model::MeshId, - attributes:attr::CollisionAttributes, - color:model::Color4,//transparency is in here - transform:Planar64Affine3, +struct ModelOwnedAttributes { + mesh: model::MeshId, + attributes: attr::CollisionAttributes, + color: model::Color4, //transparency is in here + transform: Planar64Affine3, } -struct GetAttributesArgs{ - name:Box, - can_collide:bool, - velocity:Planar64Vec3, +struct GetAttributesArgs { + name: Box, + can_collide: bool, + velocity: Planar64Vec3, } -pub fn convert( - dom:&rbx_dom_weak::WeakDom, - mut acquire_render_config_id:AcquireRenderConfigId, - mut acquire_mesh_id:AcquireMeshId, -)->PartialMap1 +pub fn convert( + 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, + AcquireRenderConfigId: FnMut(Option<&str>) -> model::RenderConfigId, + AcquireMeshId: FnMut(&str) -> model::MeshId, { + let mut deferred_models_deferred_attributes = Vec::new(); + let mut primitive_models_deferred_attributes = Vec::new(); + let mut primitive_meshes = Vec::new(); + let mut mesh_id_from_description = HashMap::new(); - let mut deferred_models_deferred_attributes=Vec::new(); - let mut primitive_models_deferred_attributes=Vec::new(); - let mut primitive_meshes=Vec::new(); - let mut mesh_id_from_description=HashMap::new(); + //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); - //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 mut object_refs = Vec::new(); + let mut temp_objects = Vec::new(); + recursive_collect_superclass(&mut object_refs, &dom, dom.root(), "BasePart"); + for object_ref in object_refs { + if let Some(object) = dom.get_by_ref(object_ref) { + if let ( + Some(rbx_dom_weak::types::Variant::CFrame(cf)), + Some(rbx_dom_weak::types::Variant::Vector3(size)), + Some(rbx_dom_weak::types::Variant::Vector3(velocity)), + Some(rbx_dom_weak::types::Variant::Float32(transparency)), + Some(rbx_dom_weak::types::Variant::Color3uint8(color3)), + Some(rbx_dom_weak::types::Variant::Bool(can_collide)), + ) = ( + object.properties.get("CFrame"), + object.properties.get("Size"), + object.properties.get("Velocity"), + object.properties.get("Transparency"), + object.properties.get("Color"), + object.properties.get("CanCollide"), + ) { + let model_transform = planar64_affine3_from_roblox(cf, size); - let mut object_refs=Vec::new(); - let mut temp_objects=Vec::new(); - recursive_collect_superclass(&mut object_refs, &dom, dom.root(),"BasePart"); - for object_ref in object_refs { - if let Some(object)=dom.get_by_ref(object_ref){ - if let ( - Some(rbx_dom_weak::types::Variant::CFrame(cf)), - Some(rbx_dom_weak::types::Variant::Vector3(size)), - Some(rbx_dom_weak::types::Variant::Vector3(velocity)), - Some(rbx_dom_weak::types::Variant::Float32(transparency)), - Some(rbx_dom_weak::types::Variant::Color3uint8(color3)), - Some(rbx_dom_weak::types::Variant::Bool(can_collide)), - ) = ( - object.properties.get("CFrame"), - object.properties.get("Size"), - object.properties.get("Velocity"), - object.properties.get("Transparency"), - object.properties.get("Color"), - object.properties.get("CanCollide"), - ) - { - let model_transform=planar64_affine3_from_roblox(cf,size); + if model_transform.matrix3.det().is_zero() { + let mut parent_ref = object.parent(); + let mut full_path = object.name.clone(); + while let Some(parent) = dom.get_by_ref(parent_ref) { + full_path = format!("{}.{}", parent.name, full_path); + parent_ref = parent.parent(); + } + println!("Zero determinant CFrame at location {}", full_path); + println!("matrix3:{}", model_transform.matrix3); + continue; + } - if model_transform.matrix3.det().is_zero(){ - let mut parent_ref=object.parent(); - let mut full_path=object.name.clone(); - while let Some(parent)=dom.get_by_ref(parent_ref){ - full_path=format!("{}.{}",parent.name,full_path); - parent_ref=parent.parent(); - } - println!("Zero determinant CFrame at location {}",full_path); - println!("matrix3:{}",model_transform.matrix3); - continue; - } + //at this point a new model is going to be generated for sure. + let model_id = + model::ModelId::new(primitive_models_deferred_attributes.len() as u32); - //at this point a new model is going to be generated for sure. - let model_id=model::ModelId::new(primitive_models_deferred_attributes.len() as u32); + //TODO: also detect "CylinderMesh" etc here + let shape = match object.class.as_str() { + "Part" => { + if let Some(rbx_dom_weak::types::Variant::Enum(shape)) = + object.properties.get("Shape") + { + Shape::Primitive(match shape.to_u32() { + 0 => primitives::Primitives::Sphere, + 1 => primitives::Primitives::Cube, + 2 => primitives::Primitives::Cylinder, + 3 => primitives::Primitives::Wedge, + 4 => primitives::Primitives::CornerWedge, + other => panic!("Funky roblox PartType={};", other), + }) + } else { + panic!("Part has no Shape!"); + } + } + "TrussPart" => Shape::Primitive(primitives::Primitives::Cube), + "WedgePart" => Shape::Primitive(primitives::Primitives::Wedge), + "CornerWedgePart" => Shape::Primitive(primitives::Primitives::CornerWedge), + "MeshPart" => Shape::MeshPart, + _ => { + println!( + "Unsupported BasePart ClassName={}; defaulting to cube", + object.class + ); + Shape::Primitive(primitives::Primitives::Cube) + } + }; - //TODO: also detect "CylinderMesh" etc here - let shape=match object.class.as_str(){ - "Part"=>if let Some(rbx_dom_weak::types::Variant::Enum(shape))=object.properties.get("Shape"){ - Shape::Primitive(match shape.to_u32(){ - 0=>primitives::Primitives::Sphere, - 1=>primitives::Primitives::Cube, - 2=>primitives::Primitives::Cylinder, - 3=>primitives::Primitives::Wedge, - 4=>primitives::Primitives::CornerWedge, - other=>panic!("Funky roblox PartType={};",other), - }) - }else{ - panic!("Part has no Shape!"); - }, - "TrussPart"=>Shape::Primitive(primitives::Primitives::Cube), - "WedgePart"=>Shape::Primitive(primitives::Primitives::Wedge), - "CornerWedgePart"=>Shape::Primitive(primitives::Primitives::CornerWedge), - "MeshPart"=>Shape::MeshPart, - _=>{ - println!("Unsupported BasePart ClassName={}; defaulting to cube",object.class); - 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... - let mut part_texture_description:RobloxPartDescription=[None,None,None,None,None,None]; - temp_objects.clear(); - recursive_collect_superclass(&mut temp_objects, &dom, object,"Decal"); - for &decal_ref in &temp_objects{ - if let Some(decal)=dom.get_by_ref(decal_ref){ - if let ( - Some(rbx_dom_weak::types::Variant::Content(content)), - Some(rbx_dom_weak::types::Variant::Enum(normalid)), - Some(rbx_dom_weak::types::Variant::Color3(decal_color3)), - Some(rbx_dom_weak::types::Variant::Float32(decal_transparency)), - ) = ( - decal.properties.get("Texture"), - decal.properties.get("Face"), - decal.properties.get("Color3"), - decal.properties.get("Transparency"), - ) { - let render_id=acquire_render_config_id(Some(content.as_ref())); - let normal_id=normalid.to_u32(); - if normal_id<6{ - let (roblox_texture_color,roblox_texture_transform)=if decal.class=="Texture"{ - //generate tranform - if let ( - Some(rbx_dom_weak::types::Variant::Float32(ox)), - Some(rbx_dom_weak::types::Variant::Float32(oy)), - Some(rbx_dom_weak::types::Variant::Float32(sx)), - Some(rbx_dom_weak::types::Variant::Float32(sy)), - ) = ( - decal.properties.get("OffsetStudsU"), - decal.properties.get("OffsetStudsV"), - decal.properties.get("StudsPerTileU"), - decal.properties.get("StudsPerTileV"), - ) - { - let (size_u,size_v)=match normal_id{ - 0=>(size.z,size.y),//right - 1=>(size.x,size.z),//top - 2=>(size.x,size.y),//back - 3=>(size.z,size.y),//left - 4=>(size.x,size.z),//bottom - 5=>(size.x,size.y),//front - _=>unreachable!(), - }; - ( - glam::vec4(decal_color3.r,decal_color3.g,decal_color3.b,1.0-*decal_transparency), - RobloxTextureTransform{ - offset_u:*ox/(*sx),offset_v:*oy/(*sy), - scale_u:size_u/(*sx),scale_v:size_v/(*sy), - } - ) - }else{ - (glam::Vec4::ONE,RobloxTextureTransform::default()) - } - }else{ - (glam::Vec4::ONE,RobloxTextureTransform::default()) - }; - part_texture_description[normal_id as usize]=Some(RobloxFaceTextureDescription{ - render:render_id, - color:roblox_texture_color, - transform:roblox_texture_transform, - }); - }else{ - println!("NormalId={} unsupported for shape={:?}",normal_id,primitive_shape); - } - } - } - } - //obscure rust syntax "slice pattern" - let [ + let (availability, mesh_id) = match shape { + Shape::Primitive(primitive_shape) => { + //TODO: TAB TAB + //use the biggest one and cut it down later... + let mut part_texture_description: RobloxPartDescription = + [None, None, None, None, None, None]; + temp_objects.clear(); + recursive_collect_superclass(&mut temp_objects, &dom, object, "Decal"); + for &decal_ref in &temp_objects { + if let Some(decal) = dom.get_by_ref(decal_ref) { + if let ( + Some(rbx_dom_weak::types::Variant::Content(content)), + Some(rbx_dom_weak::types::Variant::Enum(normalid)), + Some(rbx_dom_weak::types::Variant::Color3(decal_color3)), + Some(rbx_dom_weak::types::Variant::Float32(decal_transparency)), + ) = ( + decal.properties.get("Texture"), + decal.properties.get("Face"), + decal.properties.get("Color3"), + decal.properties.get("Transparency"), + ) { + let render_id = + acquire_render_config_id(Some(content.as_ref())); + let normal_id = normalid.to_u32(); + if normal_id < 6 { + let (roblox_texture_color, roblox_texture_transform) = + if decal.class == "Texture" { + //generate tranform + if let ( + Some(rbx_dom_weak::types::Variant::Float32(ox)), + Some(rbx_dom_weak::types::Variant::Float32(oy)), + Some(rbx_dom_weak::types::Variant::Float32(sx)), + Some(rbx_dom_weak::types::Variant::Float32(sy)), + ) = ( + decal.properties.get("OffsetStudsU"), + decal.properties.get("OffsetStudsV"), + decal.properties.get("StudsPerTileU"), + decal.properties.get("StudsPerTileV"), + ) { + let (size_u, size_v) = match normal_id { + 0 => (size.z, size.y), //right + 1 => (size.x, size.z), //top + 2 => (size.x, size.y), //back + 3 => (size.z, size.y), //left + 4 => (size.x, size.z), //bottom + 5 => (size.x, size.y), //front + _ => unreachable!(), + }; + ( + glam::vec4( + decal_color3.r, + decal_color3.g, + decal_color3.b, + 1.0 - *decal_transparency, + ), + RobloxTextureTransform { + offset_u: *ox / (*sx), + offset_v: *oy / (*sy), + scale_u: size_u / (*sx), + scale_v: size_v / (*sy), + }, + ) + } else { + ( + glam::Vec4::ONE, + RobloxTextureTransform::default(), + ) + } + } else { + (glam::Vec4::ONE, RobloxTextureTransform::default()) + }; + part_texture_description[normal_id as usize] = + Some(RobloxFaceTextureDescription { + render: render_id, + color: roblox_texture_color, + transform: roblox_texture_transform, + }); + } else { + println!( + "NormalId={} unsupported for shape={:?}", + normal_id, primitive_shape + ); + } + } + } + } + //obscure rust syntax "slice pattern" + let [ f0,//Cube::Right f1,//Cube::Top f2,//Cube::Back @@ -591,41 +746,57 @@ where f4,//Cube::Bottom f5,//Cube::Front ]=part_texture_description; - let basepart_description=match primitive_shape{ - primitives::Primitives::Sphere=>RobloxBasePartDescription::Sphere([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]), - //use front face texture first and use top face texture as a fallback - primitives::Primitives::Wedge=>RobloxBasePartDescription::Wedge([ - f0,//Cube::Right->Wedge::Right - if f5.is_some(){f5}else{f1},//Cube::Front|Cube::Top->Wedge::TopFront - f2,//Cube::Back->Wedge::Back - f3,//Cube::Left->Wedge::Left - f4,//Cube::Bottom->Wedge::Bottom - ]), - //TODO: fix Left+Back texture coordinates to match roblox when not overwridden by Top - primitives::Primitives::CornerWedge=>RobloxBasePartDescription::CornerWedge([ - f0,//Cube::Right->CornerWedge::Right - if f2.is_some(){f2}else{f1.clone()},//Cube::Back|Cube::Top->CornerWedge::TopBack - if f3.is_some(){f3}else{f1},//Cube::Left|Cube::Top->CornerWedge::TopLeft - f4,//Cube::Bottom->CornerWedge::Bottom - f5,//Cube::Front->CornerWedge::Front - ]), - }; - //make new model if unit cube has not been created before - let mesh_id=if let Some(&mesh_id)=mesh_id_from_description.get(&basepart_description){ - //push to existing texture model - mesh_id - }else{ - let mesh_id=model::MeshId::new(primitive_meshes.len() as u32); - mesh_id_from_description.insert(basepart_description.clone(),mesh_id);//borrow checker going crazy - let mesh=match basepart_description{ - RobloxBasePartDescription::Sphere(part_texture_description) - |RobloxBasePartDescription::Cylinder(part_texture_description) - |RobloxBasePartDescription::Part(part_texture_description)=>{ - let mut cube_face_description=primitives::CubeFaceDescription::default(); - for (face_id,roblox_face_description) in part_texture_description.iter().enumerate(){ - cube_face_description.insert( + let basepart_description = match primitive_shape { + primitives::Primitives::Sphere => { + RobloxBasePartDescription::Sphere([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]) + } + //use front face texture first and use top face texture as a fallback + primitives::Primitives::Wedge => RobloxBasePartDescription::Wedge([ + f0, //Cube::Right->Wedge::Right + if f5.is_some() { f5 } else { f1 }, //Cube::Front|Cube::Top->Wedge::TopFront + f2, //Cube::Back->Wedge::Back + f3, //Cube::Left->Wedge::Left + f4, //Cube::Bottom->Wedge::Bottom + ]), + //TODO: fix Left+Back texture coordinates to match roblox when not overwridden by Top + primitives::Primitives::CornerWedge => { + RobloxBasePartDescription::CornerWedge([ + f0, //Cube::Right->CornerWedge::Right + if f2.is_some() { f2 } else { f1.clone() }, //Cube::Back|Cube::Top->CornerWedge::TopBack + if f3.is_some() { f3 } else { f1 }, //Cube::Left|Cube::Top->CornerWedge::TopLeft + f4, //Cube::Bottom->CornerWedge::Bottom + f5, //Cube::Front->CornerWedge::Front + ]) + } + }; + //make new model if unit cube has not been created before + let mesh_id = if let Some(&mesh_id) = + mesh_id_from_description.get(&basepart_description) + { + //push to existing texture model + mesh_id + } else { + let mesh_id = model::MeshId::new(primitive_meshes.len() as u32); + mesh_id_from_description.insert(basepart_description.clone(), mesh_id); //borrow checker going crazy + let mesh = + match basepart_description { + RobloxBasePartDescription::Sphere(part_texture_description) + | RobloxBasePartDescription::Cylinder( + part_texture_description, + ) + | RobloxBasePartDescription::Part(part_texture_description) => { + let mut cube_face_description = + primitives::CubeFaceDescription::default(); + for (face_id, roblox_face_description) in + part_texture_description.iter().enumerate() + { + cube_face_description.insert( match face_id{ 0=>primitives::CubeFace::Right, 1=>primitives::CubeFace::Top, @@ -639,13 +810,18 @@ where Some(roblox_texture_transform)=>roblox_texture_transform.to_face_description(), None=>primitives::FaceDescription::new_with_render_id(textureless_render_group), }); - } - primitives::generate_partial_unit_cube(cube_face_description) - }, - RobloxBasePartDescription::Wedge(wedge_texture_description)=>{ - let mut wedge_face_description=primitives::WedgeFaceDescription::default(); - for (face_id,roblox_face_description) in wedge_texture_description.iter().enumerate(){ - wedge_face_description.insert( + } + primitives::generate_partial_unit_cube( + cube_face_description, + ) + } + RobloxBasePartDescription::Wedge(wedge_texture_description) => { + let mut wedge_face_description = + primitives::WedgeFaceDescription::default(); + for (face_id, roblox_face_description) in + wedge_texture_description.iter().enumerate() + { + wedge_face_description.insert( match face_id{ 0=>primitives::WedgeFace::Right, 1=>primitives::WedgeFace::TopFront, @@ -658,13 +834,20 @@ where Some(roblox_texture_transform)=>roblox_texture_transform.to_face_description(), None=>primitives::FaceDescription::new_with_render_id(textureless_render_group), }); - } - primitives::generate_partial_unit_wedge(wedge_face_description) - }, - RobloxBasePartDescription::CornerWedge(cornerwedge_texture_description)=>{ - let mut cornerwedge_face_description=primitives::CornerWedgeFaceDescription::default(); - for (face_id,roblox_face_description) in cornerwedge_texture_description.iter().enumerate(){ - cornerwedge_face_description.insert( + } + primitives::generate_partial_unit_wedge( + wedge_face_description, + ) + } + RobloxBasePartDescription::CornerWedge( + cornerwedge_texture_description, + ) => { + let mut cornerwedge_face_description = + primitives::CornerWedgeFaceDescription::default(); + for (face_id, roblox_face_description) in + cornerwedge_texture_description.iter().enumerate() + { + cornerwedge_face_description.insert( match face_id{ 0=>primitives::CornerWedgeFace::Right, 1=>primitives::CornerWedgeFace::TopBack, @@ -677,233 +860,319 @@ where Some(roblox_texture_transform)=>roblox_texture_transform.to_face_description(), None=>primitives::FaceDescription::new_with_render_id(textureless_render_group), }); - } - primitives::generate_partial_unit_cornerwedge(cornerwedge_face_description) - }, - }; - primitive_meshes.push(mesh); - mesh_id - }; - (MeshAvailability::Immediate,mesh_id) - }, - Shape::MeshPart=>if let ( - 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:vec3::try_from_f32_array([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, - } + } + primitives::generate_partial_unit_cornerwedge( + cornerwedge_face_description, + ) + } + }; + primitive_meshes.push(mesh); + mesh_id + }; + (MeshAvailability::Immediate, mesh_id) + } + Shape::MeshPart => { + if let ( + 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: vec3::try_from_f32_array([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, +struct MeshWithAabb { + mesh: model::Mesh, + aabb: strafesnet_common::aabb::Aabb, } -pub struct PartialMap1{ - primitive_meshes:Vec, - primitive_models_deferred_attributes:Vec, - deferred_models_deferred_attributes:Vec, +pub struct PartialMap1 { + primitive_meshes: Vec, + primitive_models_deferred_attributes: Vec, + deferred_models_deferred_attributes: Vec, } -impl PartialMap1{ - pub fn add_meshpart_meshes_and_calculate_attributes( - mut self, - meshpart_meshes:impl IntoIterator, - )->PartialMap2{ - //calculate attributes - let mut modes_builder=ModesBuilder::default(); - let mut unique_attributes=Vec::new(); - let mut attributes_id_from_attributes=HashMap::new(); +impl PartialMap1 { + pub fn add_meshpart_meshes_and_calculate_attributes( + mut self, + meshpart_meshes: impl IntoIterator, + ) -> 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(); + 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= - 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(); + //decode roblox meshes + //generate mesh_id_map based on meshes that failed to load + let loaded_meshes: HashMap = 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= - 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).divide().fix_1(), - (deferred_model_deferred_attributes.model.transform.matrix3.y_axis*2/size.y).divide().fix_1(), - (deferred_model_deferred_attributes.model.transform.matrix3.z_axis*2/size.z).divide().fix_1() - ]), - 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, - &mut modes_builder, - &mut wormhole_in_model_to_id, - &mut wormhole_id_to_out_model, - ), - color:model_deferred_attributes.color, - transform:model_deferred_attributes.transform, - } - }).collect(); - let models=models_owned_attributes.into_iter().enumerate().map(|(model_id,mut model_owned_attributes)|{ - //TODO: TAB - let model_id=model::ModelId::new(model_id as u32); - //update attributes with wormhole id - //TODO: errors/prints - 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 model_owned_attributes.attributes{ - attr::CollisionAttributes::Contact(attr::ContactAttributes{contacting:_,general}) - |attr::CollisionAttributes::Intersect(attr::IntersectAttributes{intersecting:_,general}) - =>general.wormhole=Some(attr::Wormhole{destination_model:wormhole_out_model_id}), - attr::CollisionAttributes::Decoration=>println!("Not a wormhole"), - } - } - } + 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 = 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) + .divide() + .fix_1(), + (deferred_model_deferred_attributes + .model + .transform + .matrix3 + .y_axis + * 2 + / size.y) + .divide() + .fix_1(), + (deferred_model_deferred_attributes + .model + .transform + .matrix3 + .z_axis + * 2 + / size.z) + .divide() + .fix_1(), + ]), + 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, + &mut modes_builder, + &mut wormhole_in_model_to_id, + &mut wormhole_id_to_out_model, + ), + color: model_deferred_attributes.color, + transform: model_deferred_attributes.transform, + } + }) + .collect(); + let models = models_owned_attributes + .into_iter() + .enumerate() + .map(|(model_id, mut model_owned_attributes)| { + //TODO: TAB + let model_id = model::ModelId::new(model_id as u32); + //update attributes with wormhole id + //TODO: errors/prints + 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 model_owned_attributes.attributes { + attr::CollisionAttributes::Contact(attr::ContactAttributes { + contacting: _, + general, + }) + | attr::CollisionAttributes::Intersect(attr::IntersectAttributes { + 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(&model_owned_attributes.attributes){ - attributes_id - }else{ - let attributes_id=attr::CollisionAttributesId::new(unique_attributes.len() as u32); - attributes_id_from_attributes.insert(model_owned_attributes.attributes.clone(),attributes_id); - unique_attributes.push(model_owned_attributes.attributes); - attributes_id - }; - model::Model{ - mesh:model_owned_attributes.mesh, - transform:model_owned_attributes.transform, - color:model_owned_attributes.color, - attributes:attributes_id, - } - }).collect(); - PartialMap2{ - meshes:self.primitive_meshes, - models, - modes:modes_builder.build(), - attributes:unique_attributes, - } - } + //index the attributes + let attributes_id = if let Some(&attributes_id) = + attributes_id_from_attributes.get(&model_owned_attributes.attributes) + { + attributes_id + } else { + let attributes_id = + attr::CollisionAttributesId::new(unique_attributes.len() as u32); + attributes_id_from_attributes + .insert(model_owned_attributes.attributes.clone(), attributes_id); + unique_attributes.push(model_owned_attributes.attributes); + attributes_id + }; + model::Model { + mesh: model_owned_attributes.mesh, + transform: model_owned_attributes.transform, + color: model_owned_attributes.color, + attributes: attributes_id, + } + }) + .collect(); + PartialMap2 { + meshes: self.primitive_meshes, + models, + modes: modes_builder.build(), + attributes: unique_attributes, + } + } } -pub struct PartialMap2{ - meshes:Vec, - models:Vec, - modes:gameplay_modes::Modes, - attributes:Vec, +pub struct PartialMap2 { + meshes: Vec, + models: Vec, + modes: gameplay_modes::Modes, + attributes: Vec, } -impl PartialMap2{ - pub fn add_render_configs_and_textures( - self, - render_configs:impl IntoIterator, - textures:impl IntoIterator)>, - )->map::CompleteMap{ - let (textures,texture_id_map):(Vec>,HashMap) - =textures.into_iter().enumerate().map(|(new_texture_id,(old_texture_id,texture))|{ - (texture,(old_texture_id,model::TextureId::new(new_texture_id as u32))) - }).unzip(); - let render_configs=render_configs.into_iter().map(|(render_config_id,mut render_config)|{ - //this may generate duplicate no-texture render configs but idc - render_config.texture=render_config.texture.and_then(|texture_id| - texture_id_map.get(&texture_id).copied() - ); - render_config - }).collect(); - map::CompleteMap{ - modes:self.modes, - attributes:self.attributes, - meshes:self.meshes, - models:self.models, - //the roblox legacy texture thing always works - textures, - render_configs, - } - } +impl PartialMap2 { + pub fn add_render_configs_and_textures( + self, + render_configs: impl IntoIterator, + textures: impl IntoIterator)>, + ) -> map::CompleteMap { + let (textures, texture_id_map): ( + Vec>, + HashMap, + ) = textures + .into_iter() + .enumerate() + .map(|(new_texture_id, (old_texture_id, texture))| { + ( + texture, + (old_texture_id, model::TextureId::new(new_texture_id as u32)), + ) + }) + .unzip(); + let render_configs = render_configs + .into_iter() + .map(|(render_config_id, mut render_config)| { + //this may generate duplicate no-texture render configs but idc + render_config.texture = render_config + .texture + .and_then(|texture_id| texture_id_map.get(&texture_id).copied()); + render_config + }) + .collect(); + map::CompleteMap { + modes: self.modes, + attributes: self.attributes, + meshes: self.meshes, + models: self.models, + //the roblox legacy texture thing always works + textures, + render_configs, + } + } } diff --git a/lib/roblox_emulator/src/context.rs b/lib/roblox_emulator/src/context.rs index 92b1424..50708f3 100644 --- a/lib/roblox_emulator/src/context.rs +++ b/lib/roblox_emulator/src/context.rs @@ -1,93 +1,106 @@ -use rbx_dom_weak::{types::Ref,InstanceBuilder,WeakDom}; +use rbx_dom_weak::{types::Ref, InstanceBuilder, WeakDom}; -pub fn class_is_a(class:&str,superclass:&str)->bool{ - class==superclass - ||rbx_reflection_database::get().classes.get(class) - .is_some_and(|descriptor| - descriptor.superclass.as_ref().is_some_and(|class_super| - class_is_a(class_super,superclass) - ) - ) +pub fn class_is_a(class: &str, superclass: &str) -> bool { + class == superclass + || rbx_reflection_database::get() + .classes + .get(class) + .is_some_and(|descriptor| { + descriptor + .superclass + .as_ref() + .is_some_and(|class_super| class_is_a(class_super, superclass)) + }) } #[repr(transparent)] -pub struct Context{ - pub(crate)dom:WeakDom, +pub struct Context { + pub(crate) dom: WeakDom, } -impl Context{ - pub const fn new(dom:WeakDom)->Self{ - Self{dom} - } - pub fn script_singleton(source:String)->(Context,crate::runner::instance::Instance,Services){ - let script=InstanceBuilder::new("Script") - .with_property("Source",rbx_types::Variant::String(source)); - let script_ref=script.referent(); - let mut context=Self::new(WeakDom::new( - InstanceBuilder::new("DataModel") - .with_child(script) - )); - let services=context.convert_into_place(); - (context,crate::runner::instance::Instance::new(script_ref),services) - } - pub fn from_ref(dom:&WeakDom)->&Context{ - unsafe{&*(dom as *const WeakDom as *const Context)} - } - pub fn from_mut(dom:&mut WeakDom)->&mut Context{ - unsafe{&mut *(dom as *mut WeakDom as *mut Context)} - } - /// Creates an iterator over all items of a particular class. - pub fn superclass_iter<'a>(&'a self,superclass:&'a str)->impl Iterator+'a{ - self.dom.descendants().filter(|&instance| - class_is_a(instance.class.as_ref(),superclass) - ).map(|instance|instance.referent()) - } - pub fn scripts(&self)->Vec{ - self.superclass_iter("LuaSourceContainer").map(crate::runner::instance::Instance::new).collect() - } +impl Context { + pub const fn new(dom: WeakDom) -> Self { + Self { dom } + } + pub fn script_singleton( + source: String, + ) -> (Context, crate::runner::instance::Instance, Services) { + let script = InstanceBuilder::new("Script") + .with_property("Source", rbx_types::Variant::String(source)); + let script_ref = script.referent(); + let mut context = Self::new(WeakDom::new( + InstanceBuilder::new("DataModel").with_child(script), + )); + let services = context.convert_into_place(); + ( + context, + crate::runner::instance::Instance::new(script_ref), + services, + ) + } + pub fn from_ref(dom: &WeakDom) -> &Context { + unsafe { &*(dom as *const WeakDom as *const Context) } + } + pub fn from_mut(dom: &mut WeakDom) -> &mut Context { + unsafe { &mut *(dom as *mut WeakDom as *mut Context) } + } + /// Creates an iterator over all items of a particular class. + pub fn superclass_iter<'a>(&'a self, superclass: &'a str) -> impl Iterator + 'a { + self.dom + .descendants() + .filter(|&instance| class_is_a(instance.class.as_ref(), superclass)) + .map(|instance| instance.referent()) + } + pub fn scripts(&self) -> Vec { + self.superclass_iter("LuaSourceContainer") + .map(crate::runner::instance::Instance::new) + .collect() + } - pub fn find_services(&self)->Option{ - Some(Services{ - workspace:*self.dom.root().children().iter().find(|&&r| - self.dom.get_by_ref(r).is_some_and(|instance|instance.class=="Workspace") - )?, - game:self.dom.root_ref(), - }) - } - pub fn convert_into_place(&mut self)->Services{ - //snapshot root instances - let children=self.dom.root().children().to_owned(); + pub fn find_services(&self) -> Option { + Some(Services { + workspace: *self.dom.root().children().iter().find(|&&r| { + self.dom + .get_by_ref(r) + .is_some_and(|instance| instance.class == "Workspace") + })?, + game: self.dom.root_ref(), + }) + } + pub fn convert_into_place(&mut self) -> Services { + //snapshot root instances + let children = self.dom.root().children().to_owned(); - //insert services - let game=self.dom.root_ref(); - let terrain_bldr=InstanceBuilder::new("Terrain"); - let workspace=self.dom.insert(game, - InstanceBuilder::new("Workspace") - //Set Workspace.Terrain property equal to Terrain - .with_property("Terrain",terrain_bldr.referent()) - .with_child(terrain_bldr) - ); - { - //Lowercase and upper case workspace property! - let game=self.dom.root_mut(); - game.properties.insert("workspace".to_owned(),rbx_types::Variant::Ref(workspace)); - game.properties.insert("Workspace".to_owned(),rbx_types::Variant::Ref(workspace)); - } - self.dom.insert(game,InstanceBuilder::new("Lighting")); + //insert services + let game = self.dom.root_ref(); + let terrain_bldr = InstanceBuilder::new("Terrain"); + let workspace = self.dom.insert( + game, + InstanceBuilder::new("Workspace") + //Set Workspace.Terrain property equal to Terrain + .with_property("Terrain", terrain_bldr.referent()) + .with_child(terrain_bldr), + ); + { + //Lowercase and upper case workspace property! + let game = self.dom.root_mut(); + game.properties + .insert("workspace".to_owned(), rbx_types::Variant::Ref(workspace)); + game.properties + .insert("Workspace".to_owned(), rbx_types::Variant::Ref(workspace)); + } + self.dom.insert(game, InstanceBuilder::new("Lighting")); - //transfer original root instances into workspace - for instance in children{ - self.dom.transfer_within(instance,workspace); - } + //transfer original root instances into workspace + for instance in children { + self.dom.transfer_within(instance, workspace); + } - Services{ - game, - workspace, - } - } + Services { game, workspace } + } } -pub struct Services{ - pub game:Ref, - pub workspace:Ref, +pub struct Services { + pub game: Ref, + pub workspace: Ref, } diff --git a/lib/roblox_emulator/src/lib.rs b/lib/roblox_emulator/src/lib.rs index fd38760..9927b33 100644 --- a/lib/roblox_emulator/src/lib.rs +++ b/lib/roblox_emulator/src/lib.rs @@ -1,6 +1,6 @@ -pub mod runner; pub mod context; -#[cfg(feature="run-service")] +pub mod runner; +#[cfg(feature = "run-service")] pub(crate) mod scheduler; #[cfg(test)] diff --git a/lib/roblox_emulator/src/runner/cframe.rs b/lib/roblox_emulator/src/runner/cframe.rs index bab3c3e..59e2ff6 100644 --- a/lib/roblox_emulator/src/runner/cframe.rs +++ b/lib/roblox_emulator/src/runner/cframe.rs @@ -1,174 +1,233 @@ use super::vector3::Vector3; -#[derive(Clone,Copy)] -pub struct CFrame(pub(crate)glam::Affine3A); +#[derive(Clone, Copy)] +pub struct CFrame(pub(crate) glam::Affine3A); -impl CFrame{ - pub fn new( - x:f32,y:f32,z:f32, - xx:f32,yx:f32,zx:f32, - xy:f32,yy:f32,zy:f32, - xz:f32,yz:f32,zz:f32, - )->Self{ - Self(glam::Affine3A::from_mat3_translation( - glam::mat3( - glam::vec3(xx,yx,zx), - glam::vec3(xy,yy,zy), - glam::vec3(xz,yz,zz) - ), - glam::vec3(x,y,z) - )) - } - pub fn point(x:f32,y:f32,z:f32)->Self{ - Self(glam::Affine3A::from_translation(glam::vec3(x,y,z))) - } - pub fn angles(x:f32,y:f32,z:f32)->Self{ - Self(glam::Affine3A::from_mat3(glam::Mat3::from_euler(glam::EulerRot::YXZ,y,x,z))) - } +impl CFrame { + pub fn new( + x: f32, + y: f32, + z: f32, + xx: f32, + yx: f32, + zx: f32, + xy: f32, + yy: f32, + zy: f32, + xz: f32, + yz: f32, + zz: f32, + ) -> Self { + Self(glam::Affine3A::from_mat3_translation( + glam::mat3( + glam::vec3(xx, yx, zx), + glam::vec3(xy, yy, zy), + glam::vec3(xz, yz, zz), + ), + glam::vec3(x, y, z), + )) + } + pub fn point(x: f32, y: f32, z: f32) -> Self { + Self(glam::Affine3A::from_translation(glam::vec3(x, y, z))) + } + pub fn angles(x: f32, y: f32, z: f32) -> Self { + Self(glam::Affine3A::from_mat3(glam::Mat3::from_euler( + glam::EulerRot::YXZ, + y, + x, + z, + ))) + } } -fn vec3_to_glam(v:glam::Vec3A)->rbx_types::Vector3{ - rbx_types::Vector3::new(v.x,v.y,v.z) +fn vec3_to_glam(v: glam::Vec3A) -> rbx_types::Vector3 { + rbx_types::Vector3::new(v.x, v.y, v.z) } -fn vec3_from_glam(v:rbx_types::Vector3)->glam::Vec3A{ - glam::vec3a(v.x,v.y,v.z) +fn vec3_from_glam(v: rbx_types::Vector3) -> glam::Vec3A { + glam::vec3a(v.x, v.y, v.z) } -impl Into for CFrame{ - fn into(self)->rbx_types::CFrame{ - rbx_types::CFrame::new( - vec3_to_glam(self.0.translation), - rbx_types::Matrix3::new( - vec3_to_glam(self.0.matrix3.x_axis), - vec3_to_glam(self.0.matrix3.y_axis), - vec3_to_glam(self.0.matrix3.z_axis), - ) - ) - } +impl Into for CFrame { + fn into(self) -> rbx_types::CFrame { + rbx_types::CFrame::new( + vec3_to_glam(self.0.translation), + rbx_types::Matrix3::new( + vec3_to_glam(self.0.matrix3.x_axis), + vec3_to_glam(self.0.matrix3.y_axis), + vec3_to_glam(self.0.matrix3.z_axis), + ), + ) + } } -impl From for CFrame{ - fn from(value:rbx_types::CFrame)->Self{ - CFrame(glam::Affine3A{ - matrix3:glam::mat3a( - vec3_from_glam(value.orientation.x), - vec3_from_glam(value.orientation.y), - vec3_from_glam(value.orientation.z), - ), - translation:vec3_from_glam(value.position) - }) - } +impl From for CFrame { + fn from(value: rbx_types::CFrame) -> Self { + CFrame(glam::Affine3A { + matrix3: glam::mat3a( + vec3_from_glam(value.orientation.x), + vec3_from_glam(value.orientation.y), + vec3_from_glam(value.orientation.z), + ), + translation: vec3_from_glam(value.position), + }) + } } -pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{ - let cframe_table=lua.create_table()?; +pub fn set_globals(lua: &mlua::Lua, globals: &mlua::Table) -> Result<(), mlua::Error> { + let cframe_table = lua.create_table()?; - //CFrame.new - cframe_table.raw_set("new", - lua.create_function(|_,tuple:( - mlua::Value,mlua::Value,Option, - Option,Option,Option, - Option,Option,Option, - Option,Option,Option, - )|match tuple{ - //CFrame.new(pos) - ( - mlua::Value::UserData(pos),mlua::Value::Nil,None, - None,None,None, - None,None,None, - None,None,None, - )=>{ - let pos:Vector3=pos.take()?; - Ok(CFrame::point(pos.0.x,pos.0.y,pos.0.z)) - }, - //TODO: CFrame.new(pos,look) - ( - mlua::Value::UserData(pos),mlua::Value::UserData(look),None, - None,None,None, - None,None,None, - None,None,None, - )=>{ - let _pos:Vector3=pos.take()?; - let _look:Vector3=look.take()?; - Err(mlua::Error::runtime("Not yet implemented")) - }, - //CFrame.new(x,y,z) - ( - mlua::Value::Number(x),mlua::Value::Number(y),Some(z), - None,None,None, - None,None,None, - None,None,None, - )=>Ok(CFrame::point(x as f32,y as f32,z)), - //CFrame.new(x,y,z,xx,yx,zx,xy,yy,zy,xz,yz,zz) - ( - mlua::Value::Number(x),mlua::Value::Number(y),Some(z), - Some(xx),Some(yx),Some(zx), - Some(xy),Some(yy),Some(zy), - Some(xz),Some(yz),Some(zz), - )=>Ok(CFrame::new(x as f32,y as f32,z, - xx,yx,zx, - xy,yy,zy, - xz,yz,zz, - )), - _=>Err(mlua::Error::runtime("Invalid arguments")) - })? - )?; + //CFrame.new + cframe_table.raw_set( + "new", + lua.create_function( + |_, + tuple: ( + mlua::Value, + mlua::Value, + Option, + Option, + Option, + Option, + Option, + Option, + Option, + Option, + Option, + Option, + )| match tuple { + //CFrame.new(pos) + ( + mlua::Value::UserData(pos), + mlua::Value::Nil, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + ) => { + let pos: Vector3 = pos.take()?; + Ok(CFrame::point(pos.0.x, pos.0.y, pos.0.z)) + } + //TODO: CFrame.new(pos,look) + ( + mlua::Value::UserData(pos), + mlua::Value::UserData(look), + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + ) => { + let _pos: Vector3 = pos.take()?; + let _look: Vector3 = look.take()?; + Err(mlua::Error::runtime("Not yet implemented")) + } + //CFrame.new(x,y,z) + ( + mlua::Value::Number(x), + mlua::Value::Number(y), + Some(z), + None, + None, + None, + None, + None, + None, + None, + None, + None, + ) => Ok(CFrame::point(x as f32, y as f32, z)), + //CFrame.new(x,y,z,xx,yx,zx,xy,yy,zy,xz,yz,zz) + ( + mlua::Value::Number(x), + mlua::Value::Number(y), + Some(z), + Some(xx), + Some(yx), + Some(zx), + Some(xy), + Some(yy), + Some(zy), + Some(xz), + Some(yz), + Some(zz), + ) => Ok(CFrame::new( + x as f32, y as f32, z, xx, yx, zx, xy, yy, zy, xz, yz, zz, + )), + _ => Err(mlua::Error::runtime("Invalid arguments")), + }, + )?, + )?; - //CFrame.Angles - cframe_table.raw_set("Angles", - lua.create_function(|_,(x,y,z):(f32,f32,f32)| - Ok(CFrame::angles(x,y,z)) - )? - )?; + //CFrame.Angles + cframe_table.raw_set( + "Angles", + lua.create_function(|_, (x, y, z): (f32, f32, f32)| Ok(CFrame::angles(x, y, z)))?, + )?; - globals.set("CFrame",cframe_table)?; + globals.set("CFrame", cframe_table)?; - Ok(()) + Ok(()) } -impl mlua::UserData for CFrame{ - fn add_fields>(fields:&mut F){ - //CFrame.p - fields.add_field_method_get("p",|_,this|Ok(Vector3(this.0.translation))); - } +impl mlua::UserData for CFrame { + fn add_fields>(fields: &mut F) { + //CFrame.p + fields.add_field_method_get("p", |_, this| Ok(Vector3(this.0.translation))); + } - fn add_methods>(methods:&mut M){ - methods.add_method("components",|_,this,()|Ok(( - this.0.translation.x, - this.0.translation.y, - this.0.translation.z, - this.0.matrix3.x_axis.x, - this.0.matrix3.y_axis.x, - this.0.matrix3.z_axis.x, - this.0.matrix3.x_axis.y, - this.0.matrix3.y_axis.y, - this.0.matrix3.z_axis.y, - this.0.matrix3.x_axis.z, - this.0.matrix3.y_axis.z, - this.0.matrix3.z_axis.z, - ))); - methods.add_method("VectorToWorldSpace",|_,this,v:Vector3| - Ok(Vector3(this.0.transform_vector3a(v.0))) - ); + fn add_methods>(methods: &mut M) { + methods.add_method("components", |_, this, ()| { + Ok(( + this.0.translation.x, + this.0.translation.y, + this.0.translation.z, + this.0.matrix3.x_axis.x, + this.0.matrix3.y_axis.x, + this.0.matrix3.z_axis.x, + this.0.matrix3.x_axis.y, + this.0.matrix3.y_axis.y, + this.0.matrix3.z_axis.y, + this.0.matrix3.x_axis.z, + this.0.matrix3.y_axis.z, + this.0.matrix3.z_axis.z, + )) + }); + methods.add_method("VectorToWorldSpace", |_, this, v: Vector3| { + Ok(Vector3(this.0.transform_vector3a(v.0))) + }); - //methods.add_meta_method(mlua::MetaMethod::Mul,|_,this,val:&Vector3|Ok(Vector3(this.0.matrix3*val.0+this.0.translation))); - methods.add_meta_function(mlua::MetaMethod::Mul,|_,(this,val):(Self,Self)|Ok(Self(this.0*val.0))); - methods.add_meta_function(mlua::MetaMethod::ToString,|_,this:Self| - Ok(format!("CFrame.new({},{},{},{},{},{},{},{},{},{},{},{})", - this.0.translation.x, - this.0.translation.y, - this.0.translation.z, - this.0.matrix3.x_axis.x, - this.0.matrix3.y_axis.x, - this.0.matrix3.z_axis.x, - this.0.matrix3.x_axis.y, - this.0.matrix3.y_axis.y, - this.0.matrix3.z_axis.y, - this.0.matrix3.x_axis.z, - this.0.matrix3.y_axis.z, - this.0.matrix3.z_axis.z, - )) - ); - } + //methods.add_meta_method(mlua::MetaMethod::Mul,|_,this,val:&Vector3|Ok(Vector3(this.0.matrix3*val.0+this.0.translation))); + methods.add_meta_function(mlua::MetaMethod::Mul, |_, (this, val): (Self, Self)| { + Ok(Self(this.0 * val.0)) + }); + methods.add_meta_function(mlua::MetaMethod::ToString, |_, this: Self| { + Ok(format!( + "CFrame.new({},{},{},{},{},{},{},{},{},{},{},{})", + this.0.translation.x, + this.0.translation.y, + this.0.translation.z, + this.0.matrix3.x_axis.x, + this.0.matrix3.y_axis.x, + this.0.matrix3.z_axis.x, + this.0.matrix3.x_axis.y, + this.0.matrix3.y_axis.y, + this.0.matrix3.z_axis.y, + this.0.matrix3.x_axis.z, + this.0.matrix3.y_axis.z, + this.0.matrix3.z_axis.z, + )) + }); + } } type_from_lua_userdata!(CFrame); diff --git a/lib/roblox_emulator/src/runner/color3.rs b/lib/roblox_emulator/src/runner/color3.rs index 93b8c68..f933a27 100644 --- a/lib/roblox_emulator/src/runner/color3.rs +++ b/lib/roblox_emulator/src/runner/color3.rs @@ -1,68 +1,72 @@ -#[derive(Clone,Copy)] -pub struct Color3{ - r:f32, - g:f32, - b:f32, +#[derive(Clone, Copy)] +pub struct Color3 { + r: f32, + g: f32, + b: f32, } -impl Color3{ - pub const fn new(r:f32,g:f32,b:f32)->Self{ - Self{r,g,b} - } +impl Color3 { + pub const fn new(r: f32, g: f32, b: f32) -> Self { + Self { r, g, b } + } } -impl Into for Color3{ - fn into(self)->rbx_types::Color3{ - rbx_types::Color3::new(self.r,self.g,self.b) - } +impl Into for Color3 { + fn into(self) -> rbx_types::Color3 { + rbx_types::Color3::new(self.r, self.g, self.b) + } } -pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{ - let color3_table=lua.create_table()?; +pub fn set_globals(lua: &mlua::Lua, globals: &mlua::Table) -> Result<(), mlua::Error> { + let color3_table = lua.create_table()?; - color3_table.raw_set("new", - lua.create_function(|_,(r,g,b):(f32,f32,f32)| - Ok(Color3::new(r,g,b)) - )? - )?; - color3_table.raw_set("fromRGB", - lua.create_function(|_,(r,g,b):(u8,u8,u8)| - Ok(Color3::new(r as f32/255.0,g as f32/255.0,b as f32/255.0)) - )? - )?; + color3_table.raw_set( + "new", + lua.create_function(|_, (r, g, b): (f32, f32, f32)| Ok(Color3::new(r, g, b)))?, + )?; + color3_table.raw_set( + "fromRGB", + lua.create_function(|_, (r, g, b): (u8, u8, u8)| { + Ok(Color3::new( + r as f32 / 255.0, + g as f32 / 255.0, + b as f32 / 255.0, + )) + })?, + )?; - globals.set("Color3",color3_table)?; + globals.set("Color3", color3_table)?; - Ok(()) + Ok(()) } -fn lerp(lhs:f32,rhs:f32,t:f32)->f32{ - lhs+(rhs-lhs)*t +fn lerp(lhs: f32, rhs: f32, t: f32) -> f32 { + lhs + (rhs - lhs) * t } -impl mlua::UserData for Color3{ - fn add_fields>(fields:&mut F){ - fields.add_field_method_get("r",|_,this|Ok(this.r)); - fields.add_field_method_set("r",|_,this,val|{ - this.r=val; - Ok(()) - }); - fields.add_field_method_get("g",|_,this|Ok(this.g)); - fields.add_field_method_set("g",|_,this,val|{ - this.g=val; - Ok(()) - }); - fields.add_field_method_get("b",|_,this|Ok(this.b)); - fields.add_field_method_set("b",|_,this,val|{ - this.b=val; - Ok(()) - }); - } - fn add_methods>(methods:&mut M){ - methods.add_method("Lerp",|_,this,(other,t):(Self,f32)| - Ok(Color3::new( - lerp(this.r,other.r,t), - lerp(this.g,other.g,t), - lerp(this.b,other.b,t), - )) - ) - } +impl mlua::UserData for Color3 { + fn add_fields>(fields: &mut F) { + fields.add_field_method_get("r", |_, this| Ok(this.r)); + fields.add_field_method_set("r", |_, this, val| { + this.r = val; + Ok(()) + }); + fields.add_field_method_get("g", |_, this| Ok(this.g)); + fields.add_field_method_set("g", |_, this, val| { + this.g = val; + Ok(()) + }); + fields.add_field_method_get("b", |_, this| Ok(this.b)); + fields.add_field_method_set("b", |_, this, val| { + this.b = val; + Ok(()) + }); + } + fn add_methods>(methods: &mut M) { + methods.add_method("Lerp", |_, this, (other, t): (Self, f32)| { + Ok(Color3::new( + lerp(this.r, other.r, t), + lerp(this.g, other.g, t), + lerp(this.b, other.b, t), + )) + }) + } } type_from_lua_userdata!(Color3); diff --git a/lib/roblox_emulator/src/runner/color_sequence.rs b/lib/roblox_emulator/src/runner/color_sequence.rs index 819fa2e..680452f 100644 --- a/lib/roblox_emulator/src/runner/color_sequence.rs +++ b/lib/roblox_emulator/src/runner/color_sequence.rs @@ -1,31 +1,30 @@ -#[derive(Clone,Copy)] -pub struct ColorSequence{} -impl ColorSequence{ - pub const fn new()->Self{ - Self{} - } +#[derive(Clone, Copy)] +pub struct ColorSequence {} +impl ColorSequence { + pub const fn new() -> Self { + Self {} + } } -impl Into for ColorSequence{ - fn into(self)->rbx_types::ColorSequence{ - rbx_types::ColorSequence{ - keypoints:Vec::new() - } - } +impl Into for ColorSequence { + fn into(self) -> rbx_types::ColorSequence { + rbx_types::ColorSequence { + keypoints: Vec::new(), + } + } } -pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{ - let number_sequence_table=lua.create_table()?; +pub fn set_globals(lua: &mlua::Lua, globals: &mlua::Table) -> Result<(), mlua::Error> { + let number_sequence_table = lua.create_table()?; - number_sequence_table.raw_set("new", - lua.create_function(|_,_:mlua::MultiValue| - Ok(ColorSequence::new()) - )? - )?; + number_sequence_table.raw_set( + "new", + lua.create_function(|_, _: mlua::MultiValue| Ok(ColorSequence::new()))?, + )?; - globals.set("ColorSequence",number_sequence_table)?; + globals.set("ColorSequence", number_sequence_table)?; - Ok(()) + Ok(()) } -impl mlua::UserData for ColorSequence{} +impl mlua::UserData for ColorSequence {} type_from_lua_userdata!(ColorSequence); diff --git a/lib/roblox_emulator/src/runner/enum.rs b/lib/roblox_emulator/src/runner/enum.rs index 0b6bd47..f5fd736 100644 --- a/lib/roblox_emulator/src/runner/enum.rs +++ b/lib/roblox_emulator/src/runner/enum.rs @@ -1,63 +1,67 @@ use mlua::IntoLua; -#[derive(Clone,Copy)] +#[derive(Clone, Copy)] pub struct Enum(u32); -#[derive(Clone,Copy)] +#[derive(Clone, Copy)] pub struct EnumItems; -#[derive(Clone,Copy)] -pub struct EnumItem<'a>{ - ed:&'a rbx_reflection::EnumDescriptor<'a>, +#[derive(Clone, Copy)] +pub struct EnumItem<'a> { + ed: &'a rbx_reflection::EnumDescriptor<'a>, } -impl Into for Enum{ - fn into(self)->rbx_types::Enum{ - rbx_types::Enum::from_u32(self.0) - } +impl Into for Enum { + fn into(self) -> rbx_types::Enum { + rbx_types::Enum::from_u32(self.0) + } } -impl<'a> EnumItem<'a>{ - const fn new(ed:&'a rbx_reflection::EnumDescriptor)->Self{ - Self{ed} - } +impl<'a> EnumItem<'a> { + const fn new(ed: &'a rbx_reflection::EnumDescriptor) -> Self { + Self { ed } + } } -pub fn set_globals(_lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{ - globals.set("Enum",EnumItems) +pub fn set_globals(_lua: &mlua::Lua, globals: &mlua::Table) -> Result<(), mlua::Error> { + globals.set("Enum", EnumItems) } -impl mlua::UserData for EnumItem<'_>{ - fn add_fields>(_fields:&mut F){ - } - fn add_methods>(methods:&mut M){ - methods.add_meta_function(mlua::MetaMethod::Index,|lua,(this,val):(EnumItem<'_>,mlua::String)|{ - match this.ed.items.get(&*val.to_str()?){ - Some(&id)=>Enum(id).into_lua(lua), - None=>mlua::Value::Nil.into_lua(lua), - } - }); - } +impl mlua::UserData for EnumItem<'_> { + fn add_fields>(_fields: &mut F) {} + fn add_methods>(methods: &mut M) { + methods.add_meta_function( + mlua::MetaMethod::Index, + |lua, (this, val): (EnumItem<'_>, mlua::String)| match this + .ed + .items + .get(&*val.to_str()?) + { + Some(&id) => Enum(id).into_lua(lua), + None => mlua::Value::Nil.into_lua(lua), + }, + ); + } } type_from_lua_userdata_lua_lifetime!(EnumItem); -impl mlua::UserData for EnumItems{ - fn add_fields>(_fields:&mut F){ - } - fn add_methods>(methods:&mut M){ - methods.add_meta_function(mlua::MetaMethod::Index,|lua,(_,val):(Self,mlua::String)|{ - let db=rbx_reflection_database::get(); - match db.enums.get(&*val.to_str()?){ - Some(ed)=>EnumItem::new(ed).into_lua(lua), - None=>mlua::Value::Nil.into_lua(lua), - } - }); - } +impl mlua::UserData for EnumItems { + fn add_fields>(_fields: &mut F) {} + fn add_methods>(methods: &mut M) { + methods.add_meta_function( + mlua::MetaMethod::Index, + |lua, (_, val): (Self, mlua::String)| { + let db = rbx_reflection_database::get(); + match db.enums.get(&*val.to_str()?) { + Some(ed) => EnumItem::new(ed).into_lua(lua), + None => mlua::Value::Nil.into_lua(lua), + } + }, + ); + } } type_from_lua_userdata!(EnumItems); -impl mlua::UserData for Enum{ - fn add_fields>(_fields:&mut F){ - } - fn add_methods>(_methods:&mut M){ - } +impl mlua::UserData for Enum { + fn add_fields>(_fields: &mut F) {} + fn add_methods>(_methods: &mut M) {} } type_from_lua_userdata!(Enum); diff --git a/lib/roblox_emulator/src/runner/instance/instance.rs b/lib/roblox_emulator/src/runner/instance/instance.rs index 7dc1f46..663e36c 100644 --- a/lib/roblox_emulator/src/runner/instance/instance.rs +++ b/lib/roblox_emulator/src/runner/instance/instance.rs @@ -1,99 +1,142 @@ -use std::collections::{hash_map::Entry,HashMap}; +use std::collections::{hash_map::Entry, HashMap}; -use mlua::{FromLua,FromLuaMulti,IntoLua,IntoLuaMulti}; +use mlua::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti}; +use rbx_dom_weak::{InstanceBuilder, WeakDom}; use rbx_types::Ref; -use rbx_dom_weak::{InstanceBuilder,WeakDom}; use crate::runner::vector3::Vector3; -pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{ - //class functions store - lua.set_app_data(ClassMethodsStore::default()); - lua.set_app_data(InstanceValueStore::default()); +pub fn set_globals(lua: &mlua::Lua, globals: &mlua::Table) -> Result<(), mlua::Error> { + //class functions store + lua.set_app_data(ClassMethodsStore::default()); + lua.set_app_data(InstanceValueStore::default()); - let instance_table=lua.create_table()?; + let instance_table = lua.create_table()?; - //Instance.new - instance_table.raw_set("new", - lua.create_function(|lua,(class_name,parent):(mlua::String,Option)|{ - let class_name_str=&*class_name.to_str()?; - let parent=parent.ok_or_else(||mlua::Error::runtime("Nil Parent not yet supported"))?; - dom_mut(lua,|dom|{ - //TODO: Nil instances - Ok(Instance::new(dom.insert(parent.referent,InstanceBuilder::new(class_name_str)))) - }) - })? - )?; + //Instance.new + instance_table.raw_set( + "new", + lua.create_function( + |lua, (class_name, parent): (mlua::String, Option)| { + let class_name_str = &*class_name.to_str()?; + let parent = + parent.ok_or_else(|| mlua::Error::runtime("Nil Parent not yet supported"))?; + dom_mut(lua, |dom| { + //TODO: Nil instances + Ok(Instance::new(dom.insert( + parent.referent, + InstanceBuilder::new(class_name_str), + ))) + }) + }, + )?, + )?; - globals.set("Instance",instance_table)?; + globals.set("Instance", instance_table)?; - Ok(()) + Ok(()) } // LMAO look at this function! -pub fn dom_mut(lua:&mlua::Lua,mut f:impl FnMut(&mut WeakDom)->mlua::Result)->mlua::Result{ - let mut dom=lua.app_data_mut::<&'static mut WeakDom>().ok_or_else(||mlua::Error::runtime("DataModel missing"))?; - f(&mut *dom) +pub fn dom_mut( + lua: &mlua::Lua, + mut f: impl FnMut(&mut WeakDom) -> mlua::Result, +) -> mlua::Result { + let mut dom = lua + .app_data_mut::<&'static mut WeakDom>() + .ok_or_else(|| mlua::Error::runtime("DataModel missing"))?; + f(&mut *dom) } -fn coerce_float32(value:&mlua::Value)->Option{ - match value{ - &mlua::Value::Integer(i)=>Some(i as f32), - &mlua::Value::Number(f)=>Some(f as f32), - _=>None, - } +fn coerce_float32(value: &mlua::Value) -> Option { + match value { + &mlua::Value::Integer(i) => Some(i as f32), + &mlua::Value::Number(f) => Some(f as f32), + _ => None, + } } -fn get_full_name(dom:&rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance)->String{ - let mut full_name=instance.name.clone(); - let mut pref=instance.parent(); - while let Some(parent)=dom.get_by_ref(pref){ - full_name.insert(0,'.'); - full_name.insert_str(0,parent.name.as_str()); - pref=parent.parent(); - } - full_name +fn get_full_name(dom: &rbx_dom_weak::WeakDom, instance: &rbx_dom_weak::Instance) -> String { + let mut full_name = instance.name.clone(); + let mut pref = instance.parent(); + while let Some(parent) = dom.get_by_ref(pref) { + full_name.insert(0, '.'); + full_name.insert_str(0, parent.name.as_str()); + pref = parent.parent(); + } + full_name } //helper function for script -pub fn get_name_source(lua:&mlua::Lua,script:Instance)->Result<(String,String),mlua::Error>{ - dom_mut(lua,|dom|{ - let instance=script.get(dom)?; - let source=match instance.properties.get("Source"){ - Some(rbx_dom_weak::types::Variant::String(s))=>s.clone(), - _=>Err(mlua::Error::external("Missing script.Source"))?, - }; - Ok((get_full_name(dom,instance),source)) - }) +pub fn get_name_source(lua: &mlua::Lua, script: Instance) -> Result<(String, String), mlua::Error> { + dom_mut(lua, |dom| { + let instance = script.get(dom)?; + let source = match instance.properties.get("Source") { + Some(rbx_dom_weak::types::Variant::String(s)) => s.clone(), + _ => Err(mlua::Error::external("Missing script.Source"))?, + }; + Ok((get_full_name(dom, instance), source)) + }) } -pub fn find_first_child<'a>(dom:&'a rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance,name:&str)->Option<&'a rbx_dom_weak::Instance>{ - instance.children().iter().filter_map(|&r|dom.get_by_ref(r)).find(|inst|inst.name==name) +pub fn find_first_child<'a>( + dom: &'a rbx_dom_weak::WeakDom, + instance: &rbx_dom_weak::Instance, + name: &str, +) -> Option<&'a rbx_dom_weak::Instance> { + instance + .children() + .iter() + .filter_map(|&r| dom.get_by_ref(r)) + .find(|inst| inst.name == name) } -pub fn find_first_descendant<'a>(dom:&'a rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance,name:&str)->Option<&'a rbx_dom_weak::Instance>{ - dom.descendants_of(instance.referent()).find(|&inst|inst.name==name) +pub fn find_first_descendant<'a>( + dom: &'a rbx_dom_weak::WeakDom, + instance: &rbx_dom_weak::Instance, + name: &str, +) -> Option<&'a rbx_dom_weak::Instance> { + dom.descendants_of(instance.referent()) + .find(|&inst| inst.name == name) } -pub fn find_first_child_of_class<'a>(dom:&'a rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance,class:&str)->Option<&'a rbx_dom_weak::Instance>{ - instance.children().iter().filter_map(|&r|dom.get_by_ref(r)).find(|inst|inst.class==class) +pub fn find_first_child_of_class<'a>( + dom: &'a rbx_dom_weak::WeakDom, + instance: &rbx_dom_weak::Instance, + class: &str, +) -> Option<&'a rbx_dom_weak::Instance> { + instance + .children() + .iter() + .filter_map(|&r| dom.get_by_ref(r)) + .find(|inst| inst.class == class) } -pub fn find_first_descendant_of_class<'a>(dom:&'a rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance,class:&str)->Option<&'a rbx_dom_weak::Instance>{ - dom.descendants_of(instance.referent()).find(|&inst|inst.class==class) +pub fn find_first_descendant_of_class<'a>( + dom: &'a rbx_dom_weak::WeakDom, + instance: &rbx_dom_weak::Instance, + class: &str, +) -> Option<&'a rbx_dom_weak::Instance> { + dom.descendants_of(instance.referent()) + .find(|&inst| inst.class == class) } -#[derive(Clone,Copy)] -pub struct Instance{ - referent:Ref, +#[derive(Clone, Copy)] +pub struct Instance { + referent: Ref, } -impl Instance{ - pub const fn new(referent:Ref)->Self{ - Self{referent} - } - pub fn get<'a>(&self,dom:&'a WeakDom)->mlua::Result<&'a rbx_dom_weak::Instance>{ - dom.get_by_ref(self.referent).ok_or_else(||mlua::Error::runtime("Instance missing")) - } - pub fn get_mut<'a>(&self,dom:&'a mut WeakDom)->mlua::Result<&'a mut rbx_dom_weak::Instance>{ - dom.get_by_ref_mut(self.referent).ok_or_else(||mlua::Error::runtime("Instance missing")) - } +impl Instance { + pub const fn new(referent: Ref) -> Self { + Self { referent } + } + pub fn get<'a>(&self, dom: &'a WeakDom) -> mlua::Result<&'a rbx_dom_weak::Instance> { + dom.get_by_ref(self.referent) + .ok_or_else(|| mlua::Error::runtime("Instance missing")) + } + pub fn get_mut<'a>( + &self, + dom: &'a mut WeakDom, + ) -> mlua::Result<&'a mut rbx_dom_weak::Instance> { + dom.get_by_ref_mut(self.referent) + .ok_or_else(|| mlua::Error::runtime("Instance missing")) + } } type_from_lua_userdata!(Instance); @@ -116,116 +159,116 @@ impl<'a> Iterator for SuperClassIter<'a> { } } -impl mlua::UserData for Instance{ - fn add_fields>(fields:&mut F){ - fields.add_field_method_get("Parent",|lua,this|{ - dom_mut(lua,|dom|{ - let instance=this.get(dom)?; - Ok(Instance::new(instance.parent())) - }) - }); - fields.add_field_method_set("Parent",|lua,this,val:Option|{ - let parent=val.ok_or_else(||mlua::Error::runtime("Nil Parent not yet supported"))?; - dom_mut(lua,|dom|{ - dom.transfer_within(this.referent,parent.referent); - Ok(()) - }) - }); - fields.add_field_method_get("Name",|lua,this|{ - dom_mut(lua,|dom|{ - let instance=this.get(dom)?; - Ok(instance.name.clone()) - }) - }); - fields.add_field_method_set("Name",|lua,this,val:mlua::String|{ - dom_mut(lua,|dom|{ - let instance=this.get_mut(dom)?; - //Why does this need to be cloned? - instance.name=val.to_str()?.to_owned(); - Ok(()) - }) - }); - fields.add_field_method_get("ClassName",|lua,this|{ - dom_mut(lua,|dom|{ - let instance=this.get(dom)?; - Ok(instance.class.clone()) - }) - }); - } - fn add_methods>(methods:&mut M){ - methods.add_method("GetChildren",|lua,this,_:()| - dom_mut(lua,|dom|{ - let instance=this.get(dom)?; - let children:Vec<_>=instance - .children() - .iter() - .copied() - .map(Instance::new) - .collect(); - Ok(children) - }) - ); - fn ffc(lua:&mlua::Lua,this:&Instance,(name,search_descendants):(mlua::String,Option))->mlua::Result>{ - let name_str=&*name.to_str()?; - dom_mut(lua,|dom|{ - let instance=this.get(dom)?; - Ok( - match search_descendants.unwrap_or(false){ - true=>find_first_descendant(dom,instance,name_str), - false=>find_first_child(dom,instance,name_str), - } - .map(|instance| - Instance::new(instance.referent()) - ) - ) - }) - } - methods.add_method("FindFirstChild",ffc); - methods.add_method("WaitForChild",ffc); - methods.add_method("FindFirstChildOfClass",|lua,this,(class,search_descendants):(mlua::String,Option)|{ - let class_str=&*class.to_str()?; - dom_mut(lua,|dom|{ - Ok( - match search_descendants.unwrap_or(false){ - true=>find_first_descendant_of_class(dom,this.get(dom)?,class_str), - false=>find_first_child_of_class(dom,this.get(dom)?,class_str), - } - .map(|instance| - Instance::new(instance.referent()) - ) - ) - }) - }); - methods.add_method("GetDescendants",|lua,this,_:()| - dom_mut(lua,|dom|{ - let children:Vec<_>=dom - .descendants_of(this.referent) - .map(|instance| - Instance::new(instance.referent()) - ) - .collect(); - Ok(children) - }) - ); - methods.add_method("IsA",|lua,this,classname:mlua::String| - dom_mut(lua,|dom|{ - let instance=this.get(dom)?; - Ok(crate::context::class_is_a(instance.class.as_str(),&*classname.to_str()?)) - }) - ); - methods.add_method("Destroy",|lua,this,()| - dom_mut(lua,|dom|{ - dom.destroy(this.referent); - Ok(()) - }) - ); - methods.add_meta_function(mlua::MetaMethod::ToString,|lua,this:Instance|{ - dom_mut(lua,|dom|{ - let instance=this.get(dom)?; - Ok(instance.name.clone()) - }) - }); - methods.add_meta_function(mlua::MetaMethod::Index,|lua,(this,index):(Instance,mlua::String)|{ +impl mlua::UserData for Instance { + fn add_fields>(fields: &mut F) { + fields.add_field_method_get("Parent", |lua, this| { + dom_mut(lua, |dom| { + let instance = this.get(dom)?; + Ok(Instance::new(instance.parent())) + }) + }); + fields.add_field_method_set("Parent", |lua, this, val: Option| { + let parent = val.ok_or_else(|| mlua::Error::runtime("Nil Parent not yet supported"))?; + dom_mut(lua, |dom| { + dom.transfer_within(this.referent, parent.referent); + Ok(()) + }) + }); + fields.add_field_method_get("Name", |lua, this| { + dom_mut(lua, |dom| { + let instance = this.get(dom)?; + Ok(instance.name.clone()) + }) + }); + fields.add_field_method_set("Name", |lua, this, val: mlua::String| { + dom_mut(lua, |dom| { + let instance = this.get_mut(dom)?; + //Why does this need to be cloned? + instance.name = val.to_str()?.to_owned(); + Ok(()) + }) + }); + fields.add_field_method_get("ClassName", |lua, this| { + dom_mut(lua, |dom| { + let instance = this.get(dom)?; + Ok(instance.class.clone()) + }) + }); + } + fn add_methods>(methods: &mut M) { + methods.add_method("GetChildren", |lua, this, _: ()| { + dom_mut(lua, |dom| { + let instance = this.get(dom)?; + let children: Vec<_> = instance + .children() + .iter() + .copied() + .map(Instance::new) + .collect(); + Ok(children) + }) + }); + fn ffc( + lua: &mlua::Lua, + this: &Instance, + (name, search_descendants): (mlua::String, Option), + ) -> mlua::Result> { + let name_str = &*name.to_str()?; + dom_mut(lua, |dom| { + let instance = this.get(dom)?; + Ok(match search_descendants.unwrap_or(false) { + true => find_first_descendant(dom, instance, name_str), + false => find_first_child(dom, instance, name_str), + } + .map(|instance| Instance::new(instance.referent()))) + }) + } + methods.add_method("FindFirstChild", ffc); + methods.add_method("WaitForChild", ffc); + methods.add_method( + "FindFirstChildOfClass", + |lua, this, (class, search_descendants): (mlua::String, Option)| { + let class_str = &*class.to_str()?; + dom_mut(lua, |dom| { + Ok(match search_descendants.unwrap_or(false) { + true => find_first_descendant_of_class(dom, this.get(dom)?, class_str), + false => find_first_child_of_class(dom, this.get(dom)?, class_str), + } + .map(|instance| Instance::new(instance.referent()))) + }) + }, + ); + methods.add_method("GetDescendants", |lua, this, _: ()| { + dom_mut(lua, |dom| { + let children: Vec<_> = dom + .descendants_of(this.referent) + .map(|instance| Instance::new(instance.referent())) + .collect(); + Ok(children) + }) + }); + methods.add_method("IsA", |lua, this, classname: mlua::String| { + dom_mut(lua, |dom| { + let instance = this.get(dom)?; + Ok(crate::context::class_is_a( + instance.class.as_str(), + &*classname.to_str()?, + )) + }) + }); + methods.add_method("Destroy", |lua, this, ()| { + dom_mut(lua, |dom| { + dom.destroy(this.referent); + Ok(()) + }) + }); + methods.add_meta_function(mlua::MetaMethod::ToString, |lua, this: Instance| { + dom_mut(lua, |dom| { + let instance = this.get(dom)?; + Ok(instance.name.clone()) + }) + }); + methods.add_meta_function(mlua::MetaMethod::Index,|lua,(this,index):(Instance,mlua::String)|{ let index_str=&*index.to_str()?; dom_mut(lua,|dom|{ let instance=this.get(dom)?; @@ -289,269 +332,359 @@ impl mlua::UserData for Instance{ .into_lua(lua) }) }); - methods.add_meta_function(mlua::MetaMethod::NewIndex,|lua,(this,index,value):(Instance,mlua::String,mlua::Value)|{ - dom_mut(lua,|dom|{ - let instance=this.get_mut(dom)?; - //println!("__newindex t={} i={index:?} v={value:?}",instance.name); - let index_str=&*index.to_str()?; - let db=rbx_reflection_database::get(); - let class=db.classes.get(instance.class.as_str()).ok_or_else(||mlua::Error::runtime("Class missing"))?; - let mut iter=SuperClassIter{ - database:db, - descriptor:Some(class), - }; - let property=iter.find_map(|cls|cls.properties.get(index_str)).ok_or_else(||mlua::Error::runtime(format!("Property '{index_str}' missing on class '{}'",class.name)))?; - match &property.data_type{ - rbx_reflection::DataType::Value(rbx_types::VariantType::Vector3)=>{ - let typed_value:Vector3=*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected Userdata"))?.borrow()?; - instance.properties.insert(index_str.to_owned(),rbx_types::Variant::Vector3(typed_value.into())); - }, - rbx_reflection::DataType::Value(rbx_types::VariantType::Float32)=>{ - let typed_value:f32=coerce_float32(&value).ok_or_else(||mlua::Error::runtime("Expected f32"))?; - instance.properties.insert(index_str.to_owned(),rbx_types::Variant::Float32(typed_value)); - }, - rbx_reflection::DataType::Enum(enum_name)=>{ - let typed_value=match &value{ - &mlua::Value::Integer(int)=>Ok(rbx_types::Enum::from_u32(int as u32)), - &mlua::Value::Number(num)=>Ok(rbx_types::Enum::from_u32(num as u32)), - mlua::Value::String(s)=>{ - let e=db.enums.get(enum_name).ok_or_else(||mlua::Error::runtime("Database DataType Enum name does not exist"))?; - Ok(rbx_types::Enum::from_u32(*e.items.get(&*s.to_str()?).ok_or_else(||mlua::Error::runtime("Invalid enum item"))?)) - }, - mlua::Value::UserData(any_user_data)=>{ - let e:crate::runner::r#enum::Enum=*any_user_data.borrow()?; - Ok(e.into()) - }, - _=>Err(mlua::Error::runtime("Expected Enum")), - }?; - instance.properties.insert(index_str.to_owned(),rbx_types::Variant::Enum(typed_value)); - }, - rbx_reflection::DataType::Value(rbx_types::VariantType::Color3)=>{ - let typed_value:crate::runner::color3::Color3=*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected Color3"))?.borrow()?; - instance.properties.insert(index_str.to_owned(),rbx_types::Variant::Color3(typed_value.into())); - }, - rbx_reflection::DataType::Value(rbx_types::VariantType::Bool)=>{ - let typed_value=value.as_boolean().ok_or_else(||mlua::Error::runtime("Expected boolean"))?; - instance.properties.insert(index_str.to_owned(),rbx_types::Variant::Bool(typed_value)); - }, - rbx_reflection::DataType::Value(rbx_types::VariantType::String)=>{ - let typed_value=value.as_str().ok_or_else(||mlua::Error::runtime("Expected boolean"))?; - instance.properties.insert(index_str.to_owned(),rbx_types::Variant::String(typed_value.to_owned())); - }, - rbx_reflection::DataType::Value(rbx_types::VariantType::NumberSequence)=>{ - let typed_value:crate::runner::number_sequence::NumberSequence=*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected NumberSequence"))?.borrow()?; - instance.properties.insert(index_str.to_owned(),rbx_types::Variant::NumberSequence(typed_value.into())); - }, - rbx_reflection::DataType::Value(rbx_types::VariantType::ColorSequence)=>{ - let typed_value:crate::runner::color_sequence::ColorSequence=*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected ColorSequence"))?.borrow()?; - instance.properties.insert(index_str.to_owned(),rbx_types::Variant::ColorSequence(typed_value.into())); - }, - other=>return Err(mlua::Error::runtime(format!("Unimplemented property type: {other:?}"))), - } - Ok(()) - }) - }); - } + methods.add_meta_function( + mlua::MetaMethod::NewIndex, + |lua, (this, index, value): (Instance, mlua::String, mlua::Value)| { + dom_mut(lua, |dom| { + let instance = this.get_mut(dom)?; + //println!("__newindex t={} i={index:?} v={value:?}",instance.name); + let index_str = &*index.to_str()?; + let db = rbx_reflection_database::get(); + let class = db + .classes + .get(instance.class.as_str()) + .ok_or_else(|| mlua::Error::runtime("Class missing"))?; + let mut iter = SuperClassIter { + database: db, + descriptor: Some(class), + }; + let property = iter + .find_map(|cls| cls.properties.get(index_str)) + .ok_or_else(|| { + mlua::Error::runtime(format!( + "Property '{index_str}' missing on class '{}'", + class.name + )) + })?; + match &property.data_type { + rbx_reflection::DataType::Value(rbx_types::VariantType::Vector3) => { + let typed_value: Vector3 = *value + .as_userdata() + .ok_or_else(|| mlua::Error::runtime("Expected Userdata"))? + .borrow()?; + instance.properties.insert( + index_str.to_owned(), + rbx_types::Variant::Vector3(typed_value.into()), + ); + } + rbx_reflection::DataType::Value(rbx_types::VariantType::Float32) => { + let typed_value: f32 = coerce_float32(&value) + .ok_or_else(|| mlua::Error::runtime("Expected f32"))?; + instance.properties.insert( + index_str.to_owned(), + rbx_types::Variant::Float32(typed_value), + ); + } + rbx_reflection::DataType::Enum(enum_name) => { + let typed_value = match &value { + &mlua::Value::Integer(int) => { + Ok(rbx_types::Enum::from_u32(int as u32)) + } + &mlua::Value::Number(num) => { + Ok(rbx_types::Enum::from_u32(num as u32)) + } + mlua::Value::String(s) => { + let e = db.enums.get(enum_name).ok_or_else(|| { + mlua::Error::runtime( + "Database DataType Enum name does not exist", + ) + })?; + Ok(rbx_types::Enum::from_u32( + *e.items.get(&*s.to_str()?).ok_or_else(|| { + mlua::Error::runtime("Invalid enum item") + })?, + )) + } + mlua::Value::UserData(any_user_data) => { + let e: crate::runner::r#enum::Enum = *any_user_data.borrow()?; + Ok(e.into()) + } + _ => Err(mlua::Error::runtime("Expected Enum")), + }?; + instance.properties.insert( + index_str.to_owned(), + rbx_types::Variant::Enum(typed_value), + ); + } + rbx_reflection::DataType::Value(rbx_types::VariantType::Color3) => { + let typed_value: crate::runner::color3::Color3 = *value + .as_userdata() + .ok_or_else(|| mlua::Error::runtime("Expected Color3"))? + .borrow()?; + instance.properties.insert( + index_str.to_owned(), + rbx_types::Variant::Color3(typed_value.into()), + ); + } + rbx_reflection::DataType::Value(rbx_types::VariantType::Bool) => { + let typed_value = value + .as_boolean() + .ok_or_else(|| mlua::Error::runtime("Expected boolean"))?; + instance.properties.insert( + index_str.to_owned(), + rbx_types::Variant::Bool(typed_value), + ); + } + rbx_reflection::DataType::Value(rbx_types::VariantType::String) => { + let typed_value = value + .as_str() + .ok_or_else(|| mlua::Error::runtime("Expected boolean"))?; + instance.properties.insert( + index_str.to_owned(), + rbx_types::Variant::String(typed_value.to_owned()), + ); + } + rbx_reflection::DataType::Value(rbx_types::VariantType::NumberSequence) => { + let typed_value: crate::runner::number_sequence::NumberSequence = + *value + .as_userdata() + .ok_or_else(|| mlua::Error::runtime("Expected NumberSequence"))? + .borrow()?; + instance.properties.insert( + index_str.to_owned(), + rbx_types::Variant::NumberSequence(typed_value.into()), + ); + } + rbx_reflection::DataType::Value(rbx_types::VariantType::ColorSequence) => { + let typed_value: crate::runner::color_sequence::ColorSequence = *value + .as_userdata() + .ok_or_else(|| mlua::Error::runtime("Expected ColorSequence"))? + .borrow()?; + instance.properties.insert( + index_str.to_owned(), + rbx_types::Variant::ColorSequence(typed_value.into()), + ); + } + other => { + return Err(mlua::Error::runtime(format!( + "Unimplemented property type: {other:?}" + ))) + } + } + Ok(()) + }) + }, + ); + } } /// A class function definition shorthand. -macro_rules! cf{ - ($f:expr)=>{ - |lua,mut args|{ - let this=Instance::from_lua(args.pop_front().unwrap_or(mlua::Value::Nil),lua)?; - $f(lua,this,FromLuaMulti::from_lua_multi(args,lua)?)?.into_lua_multi(lua) - } - }; +macro_rules! cf { + ($f:expr) => { + |lua, mut args| { + let this = Instance::from_lua(args.pop_front().unwrap_or(mlua::Value::Nil), lua)?; + $f(lua, this, FromLuaMulti::from_lua_multi(args, lua)?)?.into_lua_multi(lua) + } + }; } -type ClassFunctionPointer=fn(&mlua::Lua,mlua::MultiValue)->mlua::Result; +type ClassFunctionPointer = fn(&mlua::Lua, mlua::MultiValue) -> mlua::Result; // TODO: use macros to define these with better organization /// A double hash map of function pointers. /// The class tree is walked by the Instance.__index metamethod to find available class methods. -type CFD=phf::Map<&'static str,// Class name - phf::Map<&'static str,// Method name - ClassFunctionPointer - > +type CFD = phf::Map< + &'static str, // Class name + phf::Map< + &'static str, // Method name + ClassFunctionPointer, + >, >; -static CLASS_FUNCTION_DATABASE:CFD=phf::phf_map!{ - "DataModel"=>phf::phf_map!{ - "GetService"=>cf!(|lua,_this,service:mlua::String|{ - dom_mut(lua,|dom|{ - //dom.root_ref()==this.referent ? - let service=&*service.to_str()?; - match service{ - "Lighting"|"RunService"=>{ - let referent=find_first_child_of_class(dom,dom.root(),service) - .map(|instance|instance.referent()) - .unwrap_or_else(|| - dom.insert(dom.root_ref(),InstanceBuilder::new(service)) - ); - Ok(Instance::new(referent)) - }, - other=>Err::(mlua::Error::runtime(format!("Service '{other}' not supported"))), - } - }) - }), - }, - "Terrain"=>phf::phf_map!{ - "FillBlock"=>cf!(|_lua,_,_:(crate::runner::cframe::CFrame,Vector3,crate::runner::r#enum::Enum)|mlua::Result::Ok(())) - }, +static CLASS_FUNCTION_DATABASE: CFD = phf::phf_map! { + "DataModel"=>phf::phf_map!{ + "GetService"=>cf!(|lua,_this,service:mlua::String|{ + dom_mut(lua,|dom|{ + //dom.root_ref()==this.referent ? + let service=&*service.to_str()?; + match service{ + "Lighting"|"RunService"=>{ + let referent=find_first_child_of_class(dom,dom.root(),service) + .map(|instance|instance.referent()) + .unwrap_or_else(|| + dom.insert(dom.root_ref(),InstanceBuilder::new(service)) + ); + Ok(Instance::new(referent)) + }, + other=>Err::(mlua::Error::runtime(format!("Service '{other}' not supported"))), + } + }) + }), + }, + "Terrain"=>phf::phf_map!{ + "FillBlock"=>cf!(|_lua,_,_:(crate::runner::cframe::CFrame,Vector3,crate::runner::r#enum::Enum)|mlua::Result::Ok(())) + }, }; /// A store of created functions for each Roblox class. /// Functions are created the first time they are accessed and stored in this data structure. #[derive(Default)] -struct ClassMethodsStore{ - classes:HashMap<&'static str,//ClassName - HashMap<&'static str,//Method name - mlua::Function - > - > +struct ClassMethodsStore { + classes: HashMap< + &'static str, //ClassName + HashMap< + &'static str, //Method name + mlua::Function, + >, + >, } -impl ClassMethodsStore{ - /// return self.classes[class] or create the ClassMethods and then return it - fn get_or_create_class_methods(&mut self,class:&str)->Option{ - // Use get_entry to get the &'static str keys of the database - // and use it as a key for the classes hashmap - CLASS_FUNCTION_DATABASE.get_entry(class) - .map(|(&static_class_str,method_pointers)| - ClassMethods{ - method_pointers, - methods:self.classes.entry(static_class_str) - .or_insert_with(||HashMap::new()), - } - ) - } +impl ClassMethodsStore { + /// return self.classes[class] or create the ClassMethods and then return it + fn get_or_create_class_methods(&mut self, class: &str) -> Option { + // Use get_entry to get the &'static str keys of the database + // and use it as a key for the classes hashmap + CLASS_FUNCTION_DATABASE + .get_entry(class) + .map(|(&static_class_str, method_pointers)| ClassMethods { + method_pointers, + methods: self + .classes + .entry(static_class_str) + .or_insert_with(|| HashMap::new()), + }) + } } -struct ClassMethods<'a>{ - method_pointers:&'static phf::Map<&'static str,ClassFunctionPointer>, - methods:&'a mut HashMap<&'static str,mlua::Function>, +struct ClassMethods<'a> { + method_pointers: &'static phf::Map<&'static str, ClassFunctionPointer>, + methods: &'a mut HashMap<&'static str, mlua::Function>, } -impl ClassMethods<'_>{ - /// return self.methods[index] or create the function in the hashmap and then return it - fn get_or_create_function(&mut self,lua:&mlua::Lua,index:&str)->mlua::Result>{ - Ok(match self.method_pointers.get_entry(index){ - Some((&static_index_str,&function_pointer))=>Some( - match self.methods.entry(static_index_str){ - Entry::Occupied(entry)=>entry.get().clone(), - Entry::Vacant(entry)=>entry.insert( - lua.create_function(function_pointer)? - ).clone(), - } - ), - None=>None, - }) - } +impl ClassMethods<'_> { + /// return self.methods[index] or create the function in the hashmap and then return it + fn get_or_create_function( + &mut self, + lua: &mlua::Lua, + index: &str, + ) -> mlua::Result> { + Ok(match self.method_pointers.get_entry(index) { + Some((&static_index_str, &function_pointer)) => { + Some(match self.methods.entry(static_index_str) { + Entry::Occupied(entry) => entry.get().clone(), + Entry::Vacant(entry) => { + entry.insert(lua.create_function(function_pointer)?).clone() + } + }) + } + None => None, + }) + } } -fn class_methods_store_mut(lua:&mlua::Lua,mut f:impl FnMut(&mut ClassMethodsStore)->mlua::Result)->mlua::Result{ - let mut cf=lua.app_data_mut::().ok_or_else(||mlua::Error::runtime("ClassMethodsStore missing"))?; - f(&mut *cf) +fn class_methods_store_mut( + lua: &mlua::Lua, + mut f: impl FnMut(&mut ClassMethodsStore) -> mlua::Result, +) -> mlua::Result { + let mut cf = lua + .app_data_mut::() + .ok_or_else(|| mlua::Error::runtime("ClassMethodsStore missing"))?; + f(&mut *cf) } /// A virtual property pointer definition shorthand. -type VirtualPropertyFunctionPointer=fn(&rbx_types::Variant)->Option; -const fn vpp( - property:&'static str, - pointer:VirtualPropertyFunctionPointer, -)->VirtualProperty{ - VirtualProperty{ - property, - pointer, - } +type VirtualPropertyFunctionPointer = fn(&rbx_types::Variant) -> Option; +const fn vpp(property: &'static str, pointer: VirtualPropertyFunctionPointer) -> VirtualProperty { + VirtualProperty { property, pointer } } -struct VirtualProperty{ - property:&'static str,// Source property name - pointer:VirtualPropertyFunctionPointer, +struct VirtualProperty { + property: &'static str, // Source property name + pointer: VirtualPropertyFunctionPointer, } -type VPD=phf::Map<&'static str,// Class name - phf::Map<&'static str,// Virtual property name - VirtualProperty - > +type VPD = phf::Map< + &'static str, // Class name + phf::Map< + &'static str, // Virtual property name + VirtualProperty, + >, >; -static VIRTUAL_PROPERTY_DATABASE:VPD=phf::phf_map!{ - "BasePart"=>phf::phf_map!{ - "Position"=>vpp("CFrame",|c:&rbx_types::Variant|{ - let c=match c{ - rbx_types::Variant::CFrame(c)=>c, - _=>return None,//fail silently and ungracefully - }; - Some(rbx_types::Variant::Vector3(c.position)) - }), - }, +static VIRTUAL_PROPERTY_DATABASE: VPD = phf::phf_map! { + "BasePart"=>phf::phf_map!{ + "Position"=>vpp("CFrame",|c:&rbx_types::Variant|{ + let c=match c{ + rbx_types::Variant::CFrame(c)=>c, + _=>return None,//fail silently and ungracefully + }; + Some(rbx_types::Variant::Vector3(c.position)) + }), + }, }; fn find_virtual_property( - properties:&HashMap, - class:&rbx_reflection::ClassDescriptor, - index:&str -)->Option{ - //Find virtual property - let class_virtual_properties=VIRTUAL_PROPERTY_DATABASE.get(&class.name)?; - let virtual_property=class_virtual_properties.get(index)?; + properties: &HashMap, + class: &rbx_reflection::ClassDescriptor, + index: &str, +) -> Option { + //Find virtual property + let class_virtual_properties = VIRTUAL_PROPERTY_DATABASE.get(&class.name)?; + let virtual_property = class_virtual_properties.get(index)?; - //Get source property - let variant=properties.get(virtual_property.property)?; + //Get source property + let variant = properties.get(virtual_property.property)?; - //Transform Source property with provided function - (virtual_property.pointer)(variant) + //Transform Source property with provided function + (virtual_property.pointer)(variant) } // lazy-loaded per-instance userdata values // This whole thing is a bad idea and a garbage collection nightmare. // TODO: recreate rbx_dom_weak with my own instance type that owns this data. -type CreateUserData=fn(&mlua::Lua)->mlua::Result; -type LUD=phf::Map<&'static str,// Class name - phf::Map<&'static str,// Value name - CreateUserData - > +type CreateUserData = fn(&mlua::Lua) -> mlua::Result; +type LUD = phf::Map< + &'static str, // Class name + phf::Map< + &'static str, // Value name + CreateUserData, + >, >; -static LAZY_USER_DATA:LUD=phf::phf_map!{ - "RunService"=>phf::phf_map!{ - "RenderStepped"=>|lua|{ - lua.create_any_userdata(crate::runner::script_signal::ScriptSignal::new()) - }, - }, +static LAZY_USER_DATA: LUD = phf::phf_map! { + "RunService"=>phf::phf_map!{ + "RenderStepped"=>|lua|{ + lua.create_any_userdata(crate::runner::script_signal::ScriptSignal::new()) + }, + }, }; #[derive(Default)] -pub struct InstanceValueStore{ - values:HashMap - >, +pub struct InstanceValueStore { + values: HashMap>, } -pub struct InstanceValues<'a>{ - named_values:&'static phf::Map<&'static str,CreateUserData>, - values:&'a mut HashMap<&'static str,mlua::AnyUserData>, +pub struct InstanceValues<'a> { + named_values: &'static phf::Map<&'static str, CreateUserData>, + values: &'a mut HashMap<&'static str, mlua::AnyUserData>, } -impl InstanceValueStore{ - pub fn get_or_create_instance_values(&mut self,instance:&rbx_dom_weak::Instance)->Option{ - LAZY_USER_DATA.get(instance.class.as_str()) - .map(|named_values| - InstanceValues{ - named_values, - values:self.values.entry(instance.referent()) - .or_insert_with(||HashMap::new()), - } - ) - } +impl InstanceValueStore { + pub fn get_or_create_instance_values( + &mut self, + instance: &rbx_dom_weak::Instance, + ) -> Option { + LAZY_USER_DATA + .get(instance.class.as_str()) + .map(|named_values| InstanceValues { + named_values, + values: self + .values + .entry(instance.referent()) + .or_insert_with(|| HashMap::new()), + }) + } } -impl InstanceValues<'_>{ - pub fn get_or_create_value(&mut self,lua:&mlua::Lua,index:&str)->mlua::Result>{ - Ok(match self.named_values.get_entry(index){ - Some((&static_index_str,&function_pointer))=>Some( - match self.values.entry(static_index_str){ - Entry::Occupied(entry)=>entry.get().clone(), - Entry::Vacant(entry)=>entry.insert( - function_pointer(lua)? - ).clone(), - } - ), - None=>None, - }) - } +impl InstanceValues<'_> { + pub fn get_or_create_value( + &mut self, + lua: &mlua::Lua, + index: &str, + ) -> mlua::Result> { + Ok(match self.named_values.get_entry(index) { + Some((&static_index_str, &function_pointer)) => { + Some(match self.values.entry(static_index_str) { + Entry::Occupied(entry) => entry.get().clone(), + Entry::Vacant(entry) => entry.insert(function_pointer(lua)?).clone(), + }) + } + None => None, + }) + } } -pub fn instance_value_store_mut(lua:&mlua::Lua,mut f:impl FnMut(&mut InstanceValueStore)->mlua::Result)->mlua::Result{ - let mut cf=lua.app_data_mut::().ok_or_else(||mlua::Error::runtime("InstanceValueStore missing"))?; - f(&mut *cf) +pub fn instance_value_store_mut( + lua: &mlua::Lua, + mut f: impl FnMut(&mut InstanceValueStore) -> mlua::Result, +) -> mlua::Result { + let mut cf = lua + .app_data_mut::() + .ok_or_else(|| mlua::Error::runtime("InstanceValueStore missing"))?; + f(&mut *cf) } diff --git a/lib/roblox_emulator/src/runner/macros.rs b/lib/roblox_emulator/src/runner/macros.rs index 180c020..2f78058 100644 --- a/lib/roblox_emulator/src/runner/macros.rs +++ b/lib/roblox_emulator/src/runner/macros.rs @@ -1,24 +1,32 @@ -macro_rules! type_from_lua_userdata{ - ($asd:ident)=>{ - impl mlua::FromLua for $asd{ - fn from_lua(value:mlua::Value,_lua:&mlua::Lua)->Result{ - match value{ - mlua::Value::UserData(ud)=>Ok(*ud.borrow::()?), - other=>Err(mlua::Error::runtime(format!("Expected {} got {:?}",stringify!($asd),other))), - } - } - } - }; +macro_rules! type_from_lua_userdata { + ($asd:ident) => { + impl mlua::FromLua for $asd { + fn from_lua(value: mlua::Value, _lua: &mlua::Lua) -> Result { + match value { + mlua::Value::UserData(ud) => Ok(*ud.borrow::()?), + other => Err(mlua::Error::runtime(format!( + "Expected {} got {:?}", + stringify!($asd), + other + ))), + } + } + } + }; } -macro_rules! type_from_lua_userdata_lua_lifetime{ - ($asd:ident)=>{ - impl mlua::FromLua for $asd<'static>{ - fn from_lua(value:mlua::Value,_lua:&mlua::Lua)->Result{ - match value{ - mlua::Value::UserData(ud)=>Ok(*ud.borrow::()?), - other=>Err(mlua::Error::runtime(format!("Expected {} got {:?}",stringify!($asd),other))), - } - } - } - }; +macro_rules! type_from_lua_userdata_lua_lifetime { + ($asd:ident) => { + impl mlua::FromLua for $asd<'static> { + fn from_lua(value: mlua::Value, _lua: &mlua::Lua) -> Result { + match value { + mlua::Value::UserData(ud) => Ok(*ud.borrow::()?), + other => Err(mlua::Error::runtime(format!( + "Expected {} got {:?}", + stringify!($asd), + other + ))), + } + } + } + }; } diff --git a/lib/roblox_emulator/src/runner/mod.rs b/lib/roblox_emulator/src/runner/mod.rs index a208868..51a8919 100644 --- a/lib/roblox_emulator/src/runner/mod.rs +++ b/lib/roblox_emulator/src/runner/mod.rs @@ -2,13 +2,13 @@ mod macros; mod runner; -mod r#enum; -mod color3; mod cframe; -mod vector3; -pub mod instance; -mod script_signal; +mod color3; mod color_sequence; +mod r#enum; +pub mod instance; mod number_sequence; +mod script_signal; +mod vector3; -pub use runner::{Runner,Runnable,Error}; +pub use runner::{Error, Runnable, Runner}; diff --git a/lib/roblox_emulator/src/runner/number_sequence.rs b/lib/roblox_emulator/src/runner/number_sequence.rs index bfa25bb..ba4a8aa 100644 --- a/lib/roblox_emulator/src/runner/number_sequence.rs +++ b/lib/roblox_emulator/src/runner/number_sequence.rs @@ -1,31 +1,30 @@ -#[derive(Clone,Copy)] -pub struct NumberSequence{} -impl NumberSequence{ - pub const fn new()->Self{ - Self{} - } +#[derive(Clone, Copy)] +pub struct NumberSequence {} +impl NumberSequence { + pub const fn new() -> Self { + Self {} + } } -impl Into for NumberSequence{ - fn into(self)->rbx_types::NumberSequence{ - rbx_types::NumberSequence{ - keypoints:Vec::new() - } - } +impl Into for NumberSequence { + fn into(self) -> rbx_types::NumberSequence { + rbx_types::NumberSequence { + keypoints: Vec::new(), + } + } } -pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{ - let number_sequence_table=lua.create_table()?; +pub fn set_globals(lua: &mlua::Lua, globals: &mlua::Table) -> Result<(), mlua::Error> { + let number_sequence_table = lua.create_table()?; - number_sequence_table.raw_set("new", - lua.create_function(|_,_:mlua::MultiValue| - Ok(NumberSequence::new()) - )? - )?; + number_sequence_table.raw_set( + "new", + lua.create_function(|_, _: mlua::MultiValue| Ok(NumberSequence::new()))?, + )?; - globals.set("NumberSequence",number_sequence_table)?; + globals.set("NumberSequence", number_sequence_table)?; - Ok(()) + Ok(()) } -impl mlua::UserData for NumberSequence{} +impl mlua::UserData for NumberSequence {} type_from_lua_userdata!(NumberSequence); diff --git a/lib/roblox_emulator/src/runner/runner.rs b/lib/roblox_emulator/src/runner/runner.rs index 4b25bd6..af30c9a 100644 --- a/lib/roblox_emulator/src/runner/runner.rs +++ b/lib/roblox_emulator/src/runner/runner.rs @@ -1,143 +1,171 @@ use crate::context::Context; -#[cfg(feature="run-service")] +#[cfg(feature = "run-service")] use crate::scheduler::scheduler_mut; -pub struct Runner{ - lua:mlua::Lua, +pub struct Runner { + lua: mlua::Lua, } #[derive(Debug)] -pub enum Error{ - Lua{ - source:String, - error:mlua::Error - }, - RustLua(mlua::Error), - NoServices, +pub enum Error { + Lua { source: String, error: mlua::Error }, + RustLua(mlua::Error), + NoServices, } -impl std::fmt::Display for Error{ - fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ - match self{ - Self::Lua{source,error}=>write!(f,"lua error: source:\n{source}\n{error}"), - Self::RustLua(error)=>write!(f,"rust-side lua error: {error}"), - other=>write!(f,"{other:?}"), - } - } +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Lua { source, error } => write!(f, "lua error: source:\n{source}\n{error}"), + Self::RustLua(error) => write!(f, "rust-side lua error: {error}"), + other => write!(f, "{other:?}"), + } + } } -impl std::error::Error for Error{} +impl std::error::Error for Error {} -fn init(lua:&mlua::Lua)->mlua::Result<()>{ - lua.sandbox(true)?; +fn init(lua: &mlua::Lua) -> mlua::Result<()> { + lua.sandbox(true)?; - //global environment - let globals=lua.globals(); + //global environment + let globals = lua.globals(); - #[cfg(feature="run-service")] - crate::scheduler::set_globals(lua,&globals)?; - super::script_signal::set_globals(lua,&globals)?; - super::r#enum::set_globals(lua,&globals)?; - super::color3::set_globals(lua,&globals)?; - super::vector3::set_globals(lua,&globals)?; - super::cframe::set_globals(lua,&globals)?; - super::instance::instance::set_globals(lua,&globals)?; - super::number_sequence::set_globals(lua,&globals)?; - super::color_sequence::set_globals(lua,&globals)?; + #[cfg(feature = "run-service")] + crate::scheduler::set_globals(lua, &globals)?; + super::script_signal::set_globals(lua, &globals)?; + super::r#enum::set_globals(lua, &globals)?; + super::color3::set_globals(lua, &globals)?; + super::vector3::set_globals(lua, &globals)?; + super::cframe::set_globals(lua, &globals)?; + super::instance::instance::set_globals(lua, &globals)?; + super::number_sequence::set_globals(lua, &globals)?; + super::color_sequence::set_globals(lua, &globals)?; - Ok(()) + Ok(()) } -impl Runner{ - pub fn new()->Result{ - let runner=Self{ - lua:mlua::Lua::new(), - }; - init(&runner.lua).map_err(Error::RustLua)?; - Ok(runner) - } - pub fn runnable_context<'a>(self,context:&'a mut Context)->Result,Error>{ - let services=context.find_services().ok_or(Error::NoServices)?; - self.runnable_context_with_services(context,&services) - } - pub fn runnable_context_with_services<'a>(self,context:&'a mut Context,services:&crate::context::Services)->Result,Error>{ - { - let globals=self.lua.globals(); - globals.set("game",super::instance::Instance::new(services.game)).map_err(Error::RustLua)?; - globals.set("workspace",super::instance::Instance::new(services.workspace)).map_err(Error::RustLua)?; - } - //this makes set_app_data shut up about the lifetime - self.lua.set_app_data::<&'static mut rbx_dom_weak::WeakDom>(unsafe{core::mem::transmute(&mut context.dom)}); - #[cfg(feature="run-service")] - self.lua.set_app_data::(crate::scheduler::Scheduler::default()); - Ok(Runnable{ - lua:self.lua, - _lifetime:&std::marker::PhantomData - }) - } +impl Runner { + pub fn new() -> Result { + let runner = Self { + lua: mlua::Lua::new(), + }; + init(&runner.lua).map_err(Error::RustLua)?; + Ok(runner) + } + pub fn runnable_context<'a>(self, context: &'a mut Context) -> Result, Error> { + let services = context.find_services().ok_or(Error::NoServices)?; + self.runnable_context_with_services(context, &services) + } + pub fn runnable_context_with_services<'a>( + self, + context: &'a mut Context, + services: &crate::context::Services, + ) -> Result, Error> { + { + let globals = self.lua.globals(); + globals + .set("game", super::instance::Instance::new(services.game)) + .map_err(Error::RustLua)?; + globals + .set( + "workspace", + super::instance::Instance::new(services.workspace), + ) + .map_err(Error::RustLua)?; + } + //this makes set_app_data shut up about the lifetime + self.lua + .set_app_data::<&'static mut rbx_dom_weak::WeakDom>(unsafe { + core::mem::transmute(&mut context.dom) + }); + #[cfg(feature = "run-service")] + self.lua + .set_app_data::(crate::scheduler::Scheduler::default()); + Ok(Runnable { + lua: self.lua, + _lifetime: &std::marker::PhantomData, + }) + } } //Runnable is the same thing but has context set, which it holds the lifetime for. -pub struct Runnable<'a>{ - lua:mlua::Lua, - _lifetime:&'a std::marker::PhantomData<()> +pub struct Runnable<'a> { + lua: mlua::Lua, + _lifetime: &'a std::marker::PhantomData<()>, } -impl Runnable<'_>{ - pub fn drop_context(self)->Runner{ - self.lua.remove_app_data::<&'static mut rbx_dom_weak::WeakDom>(); - #[cfg(feature="run-service")] - self.lua.remove_app_data::(); - Runner{ - lua:self.lua, - } - } - pub fn run_script(&self,script:super::instance::Instance)->Result<(),Error>{ - let (name,source)=super::instance::instance::get_name_source(&self.lua,script).map_err(Error::RustLua)?; - self.lua.globals().raw_set("script",script).map_err(Error::RustLua)?; - let f=self.lua.load(source.as_str()) - .set_name(name).into_function().map_err(Error::RustLua)?; - // TODO: set_environment without losing the ability to print from Lua - let thread=self.lua.create_thread(f).map_err(Error::RustLua)?; - thread.resume::(()).map_err(|error|Error::Lua{source,error})?; - // wait() is called from inside Lua and goes to a rust function that schedules the thread and then yields - // No need to schedule the thread here - Ok(()) - } - #[cfg(feature="run-service")] - pub fn has_scheduled_threads(&self)->Result{ - scheduler_mut(&self.lua,|scheduler| - Ok(scheduler.has_scheduled_threads()) - ) - } - #[cfg(feature="run-service")] - pub fn game_tick(&self)->Result<(),mlua::Error>{ - if let Some(threads)=scheduler_mut(&self.lua,|scheduler|Ok(scheduler.tick_threads()))?{ - for thread in threads{ - //TODO: return dt and total run time - let result=thread.resume::((1.0/30.0,0.0)) - .map_err(|error|Error::Lua{source:"source unavailable".to_owned(),error}); - match result{ - Ok(_)=>(), - Err(e)=>println!("game_tick Error: {e}"), - } - } - } - Ok(()) - } - #[cfg(feature="run-service")] - pub fn run_service_step(&self)->Result<(),mlua::Error>{ - let render_stepped=super::instance::instance::dom_mut(&self.lua,|dom|{ - let run_service=super::instance::instance::find_first_child_of_class(dom,dom.root(),"RunService").ok_or_else(||mlua::Error::runtime("RunService missing"))?; - super::instance::instance::instance_value_store_mut(&self.lua,|instance_value_store|{ - //unwrap because I trust my find_first_child_of_class function to - let mut instance_values=instance_value_store.get_or_create_instance_values(run_service).ok_or_else(||mlua::Error::runtime("RunService InstanceValues missing"))?; - let render_stepped=instance_values.get_or_create_value(&self.lua,"RenderStepped")?; - //let stepped=instance_values.get_or_create_value(&self.lua,"Stepped")?; - //let heartbeat=instance_values.get_or_create_value(&self.lua,"Heartbeat")?; - Ok(render_stepped) - }) - })?; - if let Some(render_stepped)=render_stepped{ - let signal:&super::script_signal::ScriptSignal=&*render_stepped.borrow()?; - signal.fire(&mlua::MultiValue::new()); - } - Ok(()) - } +impl Runnable<'_> { + pub fn drop_context(self) -> Runner { + self.lua + .remove_app_data::<&'static mut rbx_dom_weak::WeakDom>(); + #[cfg(feature = "run-service")] + self.lua.remove_app_data::(); + Runner { lua: self.lua } + } + pub fn run_script(&self, script: super::instance::Instance) -> Result<(), Error> { + let (name, source) = super::instance::instance::get_name_source(&self.lua, script) + .map_err(Error::RustLua)?; + self.lua + .globals() + .raw_set("script", script) + .map_err(Error::RustLua)?; + let f = self + .lua + .load(source.as_str()) + .set_name(name) + .into_function() + .map_err(Error::RustLua)?; + // TODO: set_environment without losing the ability to print from Lua + let thread = self.lua.create_thread(f).map_err(Error::RustLua)?; + thread + .resume::(()) + .map_err(|error| Error::Lua { source, error })?; + // wait() is called from inside Lua and goes to a rust function that schedules the thread and then yields + // No need to schedule the thread here + Ok(()) + } + #[cfg(feature = "run-service")] + pub fn has_scheduled_threads(&self) -> Result { + scheduler_mut(&self.lua, |scheduler| Ok(scheduler.has_scheduled_threads())) + } + #[cfg(feature = "run-service")] + pub fn game_tick(&self) -> Result<(), mlua::Error> { + if let Some(threads) = scheduler_mut(&self.lua, |scheduler| Ok(scheduler.tick_threads()))? { + for thread in threads { + //TODO: return dt and total run time + let result = thread + .resume::((1.0 / 30.0, 0.0)) + .map_err(|error| Error::Lua { + source: "source unavailable".to_owned(), + error, + }); + match result { + Ok(_) => (), + Err(e) => println!("game_tick Error: {e}"), + } + } + } + Ok(()) + } + #[cfg(feature = "run-service")] + pub fn run_service_step(&self) -> Result<(), mlua::Error> { + let render_stepped = super::instance::instance::dom_mut(&self.lua, |dom| { + let run_service = + super::instance::instance::find_first_child_of_class(dom, dom.root(), "RunService") + .ok_or_else(|| mlua::Error::runtime("RunService missing"))?; + super::instance::instance::instance_value_store_mut(&self.lua, |instance_value_store| { + //unwrap because I trust my find_first_child_of_class function to + let mut instance_values = instance_value_store + .get_or_create_instance_values(run_service) + .ok_or_else(|| mlua::Error::runtime("RunService InstanceValues missing"))?; + let render_stepped = + instance_values.get_or_create_value(&self.lua, "RenderStepped")?; + //let stepped=instance_values.get_or_create_value(&self.lua,"Stepped")?; + //let heartbeat=instance_values.get_or_create_value(&self.lua,"Heartbeat")?; + Ok(render_stepped) + }) + })?; + if let Some(render_stepped) = render_stepped { + let signal: &super::script_signal::ScriptSignal = &*render_stepped.borrow()?; + signal.fire(&mlua::MultiValue::new()); + } + Ok(()) + } } diff --git a/lib/roblox_emulator/src/runner/script_signal.rs b/lib/roblox_emulator/src/runner/script_signal.rs index 877f2b6..a65b201 100644 --- a/lib/roblox_emulator/src/runner/script_signal.rs +++ b/lib/roblox_emulator/src/runner/script_signal.rs @@ -1,172 +1,189 @@ -use std::{cell::RefCell,rc::Rc}; +use std::{cell::RefCell, rc::Rc}; use mlua::UserDataFields; #[derive(Clone)] -struct FunctionList{ - functions:Vec, +struct FunctionList { + functions: Vec, } -impl FunctionList{ - pub fn new()->Self{ - Self{ - functions:Vec::new(), - } - } - // This eats the Lua error - pub fn fire(self,args:&mlua::MultiValue){ - // Make a copy of the list in case Lua attempts to modify it during the loop - for function in self.functions{ - //wee let's allocate for our function calls - if let Err(e)=function.call::(args.clone()){ - println!("Script Signal Error: {e}"); - } - } - } +impl FunctionList { + pub fn new() -> Self { + Self { + functions: Vec::new(), + } + } + // This eats the Lua error + pub fn fire(self, args: &mlua::MultiValue) { + // Make a copy of the list in case Lua attempts to modify it during the loop + for function in self.functions { + //wee let's allocate for our function calls + if let Err(e) = function.call::(args.clone()) { + println!("Script Signal Error: {e}"); + } + } + } } #[derive(Clone)] -struct RcFunctionList{ - functions:Rc>, +struct RcFunctionList { + functions: Rc>, } -impl RcFunctionList{ - pub fn new()->Self{ - Self{ - functions:Rc::new(RefCell::new(FunctionList::new())), - } - } - pub fn fire(&self,args:&mlua::MultiValue){ - // Make a copy of the list in case Lua attempts to modify it during the loop - self.functions.borrow().clone().fire(args) - } +impl RcFunctionList { + pub fn new() -> Self { + Self { + functions: Rc::new(RefCell::new(FunctionList::new())), + } + } + pub fn fire(&self, args: &mlua::MultiValue) { + // Make a copy of the list in case Lua attempts to modify it during the loop + self.functions.borrow().clone().fire(args) + } } #[derive(Clone)] -pub struct ScriptSignal{ - // Emulate the garbage roblox api. - // ScriptConnection should not exist. - // :Disconnect should be a method on ScriptSignal, and this would be avoided entirely. - connections:RcFunctionList, - once:RcFunctionList, - wait:Rc>>, +pub struct ScriptSignal { + // Emulate the garbage roblox api. + // ScriptConnection should not exist. + // :Disconnect should be a method on ScriptSignal, and this would be avoided entirely. + connections: RcFunctionList, + once: RcFunctionList, + wait: Rc>>, } -pub struct ScriptConnection{ - connection:RcFunctionList, - function:mlua::Function, +pub struct ScriptConnection { + connection: RcFunctionList, + function: mlua::Function, } -impl ScriptSignal{ - pub fn new()->Self{ - Self{ - connections:RcFunctionList::new(), - once:RcFunctionList::new(), - wait:Rc::new(RefCell::new(Vec::new())), - } - } - pub fn fire(&self,args:&mlua::MultiValue){ - self.connections.fire(args); - //Replace the FunctionList with an empty one and drop the borrow - let once=std::mem::replace(&mut *self.once.functions.borrow_mut(),FunctionList::new()); - once.fire(args); - //resume threads waiting for this signal - let threads=std::mem::replace(&mut *self.wait.borrow_mut(),Vec::new()); - for thread in threads{ - if let Err(e)=thread.resume::(args.clone()){ - println!("Script Signal thread resume Error: {e}"); - } - } - } - pub fn connect(&self,function:mlua::Function)->ScriptConnection{ - self.connections.functions.borrow_mut().functions.push(function.clone()); - ScriptConnection{ - connection:self.connections.clone(), - function, - } - } - pub fn once(&self,function:mlua::Function)->ScriptConnection{ - self.once.functions.borrow_mut().functions.push(function.clone()); - ScriptConnection{ - connection:self.once.clone(), - function, - } - } - pub fn wait(&self,thread:mlua::Thread){ - self.wait.borrow_mut().push(thread); - } +impl ScriptSignal { + pub fn new() -> Self { + Self { + connections: RcFunctionList::new(), + once: RcFunctionList::new(), + wait: Rc::new(RefCell::new(Vec::new())), + } + } + pub fn fire(&self, args: &mlua::MultiValue) { + self.connections.fire(args); + //Replace the FunctionList with an empty one and drop the borrow + let once = std::mem::replace(&mut *self.once.functions.borrow_mut(), FunctionList::new()); + once.fire(args); + //resume threads waiting for this signal + let threads = std::mem::replace(&mut *self.wait.borrow_mut(), Vec::new()); + for thread in threads { + if let Err(e) = thread.resume::(args.clone()) { + println!("Script Signal thread resume Error: {e}"); + } + } + } + pub fn connect(&self, function: mlua::Function) -> ScriptConnection { + self.connections + .functions + .borrow_mut() + .functions + .push(function.clone()); + ScriptConnection { + connection: self.connections.clone(), + function, + } + } + pub fn once(&self, function: mlua::Function) -> ScriptConnection { + self.once + .functions + .borrow_mut() + .functions + .push(function.clone()); + ScriptConnection { + connection: self.once.clone(), + function, + } + } + pub fn wait(&self, thread: mlua::Thread) { + self.wait.borrow_mut().push(thread); + } } -impl ScriptConnection{ - pub fn position(&self)->Option{ - self.connection.functions.borrow().functions.iter().position(|function|function==&self.function) - } +impl ScriptConnection { + pub fn position(&self) -> Option { + self.connection + .functions + .borrow() + .functions + .iter() + .position(|function| function == &self.function) + } } -impl mlua::UserData for ScriptSignal{ - fn add_methods>(methods:&mut M){ - methods.add_method("Connect",|_lua,this,f:mlua::Function| - Ok(this.connect(f)) - ); - methods.add_method("Once",|_lua,this,f:mlua::Function| - Ok(this.once(f)) - ); - // Fire is not allowed to be called from Lua - // methods.add_method("Fire",|_lua,this,args:mlua::MultiValue| - // Ok(this.fire(args)) - // ); - } +impl mlua::UserData for ScriptSignal { + fn add_methods>(methods: &mut M) { + methods.add_method("Connect", |_lua, this, f: mlua::Function| { + Ok(this.connect(f)) + }); + methods.add_method("Once", |_lua, this, f: mlua::Function| Ok(this.once(f))); + // Fire is not allowed to be called from Lua + // methods.add_method("Fire",|_lua,this,args:mlua::MultiValue| + // Ok(this.fire(args)) + // ); + } } -impl mlua::FromLua for ScriptSignal{ - fn from_lua(value:mlua::Value,_lua:&mlua::Lua)->Result{ - match value{ - mlua::Value::UserData(ud)=>Ok(ud.borrow::()?.clone()), - other=>Err(mlua::Error::runtime(format!("Expected {} got {:?}",stringify!(ScriptSignal),other))), - } - } +impl mlua::FromLua for ScriptSignal { + fn from_lua(value: mlua::Value, _lua: &mlua::Lua) -> Result { + match value { + mlua::Value::UserData(ud) => Ok(ud.borrow::()?.clone()), + other => Err(mlua::Error::runtime(format!( + "Expected {} got {:?}", + stringify!(ScriptSignal), + other + ))), + } + } } -impl mlua::UserData for ScriptConnection{ - fn add_fields>(fields:&mut F){ - fields.add_field_method_get("Connected",|_,this|{ - Ok(this.position().is_some()) - }); - } - fn add_methods>(methods:&mut M){ - methods.add_method("Disconnect",|_,this,_:()|{ - if let Some(index)=this.position(){ - this.connection.functions.borrow_mut().functions.remove(index); - } - Ok(()) - }); - } +impl mlua::UserData for ScriptConnection { + fn add_fields>(fields: &mut F) { + fields.add_field_method_get("Connected", |_, this| Ok(this.position().is_some())); + } + fn add_methods>(methods: &mut M) { + methods.add_method("Disconnect", |_, this, _: ()| { + if let Some(index) = this.position() { + this.connection + .functions + .borrow_mut() + .functions + .remove(index); + } + Ok(()) + }); + } } -fn wait_thread(lua:&mlua::Lua,this:ScriptSignal)->Result<(),mlua::Error>{ - Ok(this.wait(lua.current_thread())) +fn wait_thread(lua: &mlua::Lua, this: ScriptSignal) -> Result<(), mlua::Error> { + Ok(this.wait(lua.current_thread())) } // This is used to avoid calling coroutine.yield from the rust side. -const LUA_WAIT:&str= -"local coroutine_yield=coroutine.yield +const LUA_WAIT: &str = "local coroutine_yield=coroutine.yield local wait_thread=wait_thread return function(signal) wait_thread(signal) return coroutine_yield() end"; -pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{ - let coroutine_table=globals.get::("coroutine")?; - let wait_thread=lua.create_function(wait_thread)?; +pub fn set_globals(lua: &mlua::Lua, globals: &mlua::Table) -> Result<(), mlua::Error> { + let coroutine_table = globals.get::("coroutine")?; + let wait_thread = lua.create_function(wait_thread)?; - //create wait function environment - let wait_env=lua.create_table()?; - wait_env.raw_set("coroutine",coroutine_table)?; - wait_env.raw_set("wait_thread",wait_thread)?; + //create wait function environment + let wait_env = lua.create_table()?; + wait_env.raw_set("coroutine", coroutine_table)?; + wait_env.raw_set("wait_thread", wait_thread)?; - //construct wait function from Lua code - let wait=lua.load(LUA_WAIT) - .set_name("wait") - .set_environment(wait_env) - .call::(())?; + //construct wait function from Lua code + let wait = lua + .load(LUA_WAIT) + .set_name("wait") + .set_environment(wait_env) + .call::(())?; - lua.register_userdata_type::(|reg|{ - reg.add_field("Wait",wait); - mlua::UserData::register(reg); - })?; + lua.register_userdata_type::(|reg| { + reg.add_field("Wait", wait); + mlua::UserData::register(reg); + })?; - Ok(()) + Ok(()) } diff --git a/lib/roblox_emulator/src/runner/vector3.rs b/lib/roblox_emulator/src/runner/vector3.rs index 2e543de..3daf888 100644 --- a/lib/roblox_emulator/src/runner/vector3.rs +++ b/lib/roblox_emulator/src/runner/vector3.rs @@ -1,82 +1,85 @@ -#[derive(Clone,Copy)] -pub struct Vector3(pub(crate)glam::Vec3A); +#[derive(Clone, Copy)] +pub struct Vector3(pub(crate) glam::Vec3A); -impl Vector3{ - pub const fn new(x:f32,y:f32,z:f32)->Self{ - Self(glam::vec3a(x,y,z)) - } +impl Vector3 { + pub const fn new(x: f32, y: f32, z: f32) -> Self { + Self(glam::vec3a(x, y, z)) + } } -pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{ - let vector3_table=lua.create_table()?; +pub fn set_globals(lua: &mlua::Lua, globals: &mlua::Table) -> Result<(), mlua::Error> { + let vector3_table = lua.create_table()?; - //Vector3.new - vector3_table.raw_set("new", - lua.create_function(|_,(x,y,z):(f32,f32,f32)| - Ok(Vector3::new(x,y,z)) - )? - )?; + //Vector3.new + vector3_table.raw_set( + "new", + lua.create_function(|_, (x, y, z): (f32, f32, f32)| Ok(Vector3::new(x, y, z)))?, + )?; - globals.set("Vector3",vector3_table)?; + globals.set("Vector3", vector3_table)?; - Ok(()) + Ok(()) } -impl Into for Vector3{ - fn into(self)->rbx_types::Vector3{ - rbx_types::Vector3::new(self.0.x,self.0.y,self.0.z) - } +impl Into for Vector3 { + fn into(self) -> rbx_types::Vector3 { + rbx_types::Vector3::new(self.0.x, self.0.y, self.0.z) + } } -impl From for Vector3{ - fn from(value:rbx_types::Vector3)->Vector3{ - Vector3::new(value.x,value.y,value.z) - } +impl From for Vector3 { + fn from(value: rbx_types::Vector3) -> Vector3 { + Vector3::new(value.x, value.y, value.z) + } } -impl mlua::UserData for Vector3{ - fn add_fields>(fields:&mut F){ - fields.add_field_method_get("magnitude",|_,this|Ok(this.0.length())); - fields.add_field_method_get("x",|_,this|Ok(this.0.x)); - fields.add_field_method_set("x",|_,this,val|{ - this.0.x=val; - Ok(()) - }); - fields.add_field_method_get("y",|_,this|Ok(this.0.y)); - fields.add_field_method_set("y",|_,this,val|{ - this.0.y=val; - Ok(()) - }); - fields.add_field_method_get("z",|_,this|Ok(this.0.z)); - fields.add_field_method_set("z",|_,this,val|{ - this.0.z=val; - Ok(()) - }); - } +impl mlua::UserData for Vector3 { + fn add_fields>(fields: &mut F) { + fields.add_field_method_get("magnitude", |_, this| Ok(this.0.length())); + fields.add_field_method_get("x", |_, this| Ok(this.0.x)); + fields.add_field_method_set("x", |_, this, val| { + this.0.x = val; + Ok(()) + }); + fields.add_field_method_get("y", |_, this| Ok(this.0.y)); + fields.add_field_method_set("y", |_, this, val| { + this.0.y = val; + Ok(()) + }); + fields.add_field_method_get("z", |_, this| Ok(this.0.z)); + fields.add_field_method_set("z", |_, this, val| { + this.0.z = val; + Ok(()) + }); + } - fn add_methods>(methods:&mut M){ - //methods.add_method("area",|_,this,()| Ok(this.length * this.width)); + fn add_methods>(methods: &mut M) { + //methods.add_method("area",|_,this,()| Ok(this.length * this.width)); - methods.add_meta_function(mlua::MetaMethod::Add,|_,(this,val):(Self,Self)|Ok(Self(this.0+val.0))); - methods.add_meta_function(mlua::MetaMethod::Div,|_,(this,val):(Self,mlua::Value)|{ - match val{ - mlua::Value::Integer(n)=>Ok(Self(this.0/(n as f32))), - mlua::Value::Number(n)=>Ok(Self(this.0/(n as f32))), - mlua::Value::UserData(ud)=>{ - let rhs:Vector3=ud.take()?; - Ok(Self(this.0/rhs.0)) - }, - other=>Err(mlua::Error::runtime(format!("Attempt to divide Vector3 by {other:?}"))), - } - }); - methods.add_meta_function(mlua::MetaMethod::ToString,|_,this:Self| - Ok(format!("Vector3.new({},{},{})", - this.0.x, - this.0.y, - this.0.z, - )) - ); - } + methods.add_meta_function(mlua::MetaMethod::Add, |_, (this, val): (Self, Self)| { + Ok(Self(this.0 + val.0)) + }); + methods.add_meta_function( + mlua::MetaMethod::Div, + |_, (this, val): (Self, mlua::Value)| match val { + mlua::Value::Integer(n) => Ok(Self(this.0 / (n as f32))), + mlua::Value::Number(n) => Ok(Self(this.0 / (n as f32))), + mlua::Value::UserData(ud) => { + let rhs: Vector3 = ud.take()?; + Ok(Self(this.0 / rhs.0)) + } + other => Err(mlua::Error::runtime(format!( + "Attempt to divide Vector3 by {other:?}" + ))), + }, + ); + methods.add_meta_function(mlua::MetaMethod::ToString, |_, this: Self| { + Ok(format!( + "Vector3.new({},{},{})", + this.0.x, this.0.y, this.0.z, + )) + }); + } } type_from_lua_userdata!(Vector3); diff --git a/lib/roblox_emulator/src/scheduler.rs b/lib/roblox_emulator/src/scheduler.rs index f665e9d..ec9dc93 100644 --- a/lib/roblox_emulator/src/scheduler.rs +++ b/lib/roblox_emulator/src/scheduler.rs @@ -1,106 +1,114 @@ pub use tick::Tick; -mod tick{ - #[derive(Clone,Copy,Default,Hash,PartialEq,Eq,PartialOrd,Ord)] - pub struct Tick(u64); - impl std::ops::Add for Tick{ - type Output=Self; - fn add(self,rhs:u64)->Self::Output{ - Self(self.0+rhs) - } - } - impl std::ops::Sub for Tick{ - type Output=Self; - fn sub(self,rhs:u64)->Self::Output{ - Self(self.0-rhs) - } - } - impl std::ops::AddAssign for Tick{ - fn add_assign(&mut self,rhs:u64){ - self.0+=rhs; - } - } - impl std::ops::SubAssign for Tick{ - fn sub_assign(&mut self,rhs:u64){ - self.0-=rhs; - } - } +mod tick { + #[derive(Clone, Copy, Default, Hash, PartialEq, Eq, PartialOrd, Ord)] + pub struct Tick(u64); + impl std::ops::Add for Tick { + type Output = Self; + fn add(self, rhs: u64) -> Self::Output { + Self(self.0 + rhs) + } + } + impl std::ops::Sub for Tick { + type Output = Self; + fn sub(self, rhs: u64) -> Self::Output { + Self(self.0 - rhs) + } + } + impl std::ops::AddAssign for Tick { + fn add_assign(&mut self, rhs: u64) { + self.0 += rhs; + } + } + impl std::ops::SubAssign for Tick { + fn sub_assign(&mut self, rhs: u64) { + self.0 -= rhs; + } + } } #[derive(Default)] -pub struct Scheduler{ - tick:Tick, - schedule:std::collections::HashMap>, +pub struct Scheduler { + tick: Tick, + schedule: std::collections::HashMap>, } -impl Scheduler{ - pub fn has_scheduled_threads(&self)->bool{ - !self.schedule.is_empty() - } - pub fn schedule_thread(&mut self,delay:u64,thread:mlua::Thread){ - self.schedule.entry(self.tick+delay.max(1)) - .or_insert(Vec::new()) - .push(thread); - } - pub fn tick_threads(&mut self)->Option>{ - self.tick+=1; - self.schedule.remove(&self.tick) - } +impl Scheduler { + pub fn has_scheduled_threads(&self) -> bool { + !self.schedule.is_empty() + } + pub fn schedule_thread(&mut self, delay: u64, thread: mlua::Thread) { + self.schedule + .entry(self.tick + delay.max(1)) + .or_insert(Vec::new()) + .push(thread); + } + pub fn tick_threads(&mut self) -> Option> { + self.tick += 1; + self.schedule.remove(&self.tick) + } } -pub fn scheduler_mut(lua:&mlua::Lua,mut f:impl FnMut(&mut crate::scheduler::Scheduler)->mlua::Result)->mlua::Result{ - let mut scheduler=lua.app_data_mut::().ok_or_else(||mlua::Error::runtime("Scheduler missing"))?; - f(&mut *scheduler) +pub fn scheduler_mut( + lua: &mlua::Lua, + mut f: impl FnMut(&mut crate::scheduler::Scheduler) -> mlua::Result, +) -> mlua::Result { + let mut scheduler = lua + .app_data_mut::() + .ok_or_else(|| mlua::Error::runtime("Scheduler missing"))?; + f(&mut *scheduler) } -fn schedule_thread(lua:&mlua::Lua,dt:mlua::Value)->Result<(),mlua::Error>{ - let delay=match dt{ - mlua::Value::Integer(i)=>i.max(0) as u64*60, - mlua::Value::Number(f)=>{ - let delay=f.max(0.0)*60.0; - match delay.classify(){ - std::num::FpCategory::Nan=>Err(mlua::Error::runtime("NaN"))?, - // cases where the number is too large to schedule - std::num::FpCategory::Infinite=>return Ok(()), - std::num::FpCategory::Normal=>if (u64::MAX as f64)(), - } - delay as u64 - }, - mlua::Value::Nil=>0, - _=>Err(mlua::Error::runtime("Expected float"))?, - }; - scheduler_mut(lua,|scheduler|{ - scheduler.schedule_thread(delay.max(2),lua.current_thread()); - Ok(()) - }) +fn schedule_thread(lua: &mlua::Lua, dt: mlua::Value) -> Result<(), mlua::Error> { + let delay = match dt { + mlua::Value::Integer(i) => i.max(0) as u64 * 60, + mlua::Value::Number(f) => { + let delay = f.max(0.0) * 60.0; + match delay.classify() { + std::num::FpCategory::Nan => Err(mlua::Error::runtime("NaN"))?, + // cases where the number is too large to schedule + std::num::FpCategory::Infinite => return Ok(()), + std::num::FpCategory::Normal => { + if (u64::MAX as f64) < delay { + return Ok(()); + } + } + _ => (), + } + delay as u64 + } + mlua::Value::Nil => 0, + _ => Err(mlua::Error::runtime("Expected float"))?, + }; + scheduler_mut(lua, |scheduler| { + scheduler.schedule_thread(delay.max(2), lua.current_thread()); + Ok(()) + }) } // This is used to avoid calling coroutine.yield from the rust side. -const LUA_WAIT:&str= -"local coroutine_yield=coroutine.yield +const LUA_WAIT: &str = "local coroutine_yield=coroutine.yield local schedule_thread=schedule_thread return function(dt) schedule_thread(dt) return coroutine_yield() end"; -pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{ - let coroutine_table=globals.get::("coroutine")?; - let schedule_thread=lua.create_function(schedule_thread)?; +pub fn set_globals(lua: &mlua::Lua, globals: &mlua::Table) -> Result<(), mlua::Error> { + let coroutine_table = globals.get::("coroutine")?; + let schedule_thread = lua.create_function(schedule_thread)?; - //create wait function environment - let wait_env=lua.create_table()?; - wait_env.raw_set("coroutine",coroutine_table)?; - wait_env.raw_set("schedule_thread",schedule_thread)?; + //create wait function environment + let wait_env = lua.create_table()?; + wait_env.raw_set("coroutine", coroutine_table)?; + wait_env.raw_set("schedule_thread", schedule_thread)?; - //construct wait function from Lua code - let wait=lua.load(LUA_WAIT) - .set_name("wait") - .set_environment(wait_env) - .call::(())?; + //construct wait function from Lua code + let wait = lua + .load(LUA_WAIT) + .set_name("wait") + .set_environment(wait_env) + .call::(())?; - globals.raw_set("wait",wait)?; + globals.raw_set("wait", wait)?; - Ok(()) + Ok(()) } diff --git a/lib/roblox_emulator/src/tests.rs b/lib/roblox_emulator/src/tests.rs index e69de29..8b13789 100644 --- a/lib/roblox_emulator/src/tests.rs +++ b/lib/roblox_emulator/src/tests.rs @@ -0,0 +1 @@ + diff --git a/lib/snf/src/bot.rs b/lib/snf/src/bot.rs index ffdaee8..3479b7b 100644 --- a/lib/snf/src/bot.rs +++ b/lib/snf/src/bot.rs @@ -1,11 +1,11 @@ -use binrw::{BinReaderExt, binrw}; +use binrw::{binrw, BinReaderExt}; #[derive(Debug)] -pub enum Error{ - InvalidHeader, - InvalidSegment(binrw::Error), - InvalidSegmentId(SegmentId), - File(crate::file::Error), +pub enum Error { + InvalidHeader, + InvalidSegment(binrw::Error), + InvalidSegmentId(SegmentId), + File(crate::file::Error), } /* block types @@ -16,8 +16,8 @@ u128 map_resource_uuid //which map is this bot running //blocks are laid out in chronological order, but indices may jump around. u64 num_segments for _ in 0..num_segments{ - i64 time //simulation_state timestamp - u64 block_id + i64 time //simulation_state timestamp + u64 block_id } BLOCK_BOT_SEGMENT: @@ -25,74 +25,77 @@ BLOCK_BOT_SEGMENT: SimulationState simulation_state //SimulationState is just under ClientState which includes Play/Pause events that the simulation doesn't know about. //to read, greedily decode instructions until eof loop{ - //delta encode as much as possible (time,mousepos) - //strafe ticks are implied - //physics can be implied in an input-only bot file - TimedInstruction instruction + //delta encode as much as possible (time,mousepos) + //strafe ticks are implied + //physics can be implied in an input-only bot file + TimedInstruction instruction } */ //error hiding mock code -mod simulation{ - #[super::binrw] - #[brw(little)] - pub struct State{} - #[super::binrw] - #[brw(little)] - pub struct Instruction{} +mod simulation { + #[super::binrw] + #[brw(little)] + pub struct State {} + #[super::binrw] + #[brw(little)] + pub struct Instruction {} } // mod instruction{ - // #[super::binrw] - // #[brw(little)] - // pub struct TimedInstruction{ - // time:u64, - // instruction:Instruction - // } +// #[super::binrw] +// #[brw(little)] +// pub struct TimedInstruction{ +// time:u64, +// instruction:Instruction +// } // } // mod timeline{ - // #[super::binrw] - // #[brw(little)] - // pub struct Timeline{ - // #[bw(try_calc(u32::try_from(instructions.len())))] - // instruction_count:u32, - // #[br(count=instruction_count)] - // instructions:Vec> - // } +// #[super::binrw] +// #[brw(little)] +// pub struct Timeline{ +// #[bw(try_calc(u32::try_from(instructions.len())))] +// instruction_count:u32, +// #[br(count=instruction_count)] +// instructions:Vec> +// } // } //serious code #[binrw] #[brw(little)] -#[derive(Clone,Copy,Debug,id::Id)] +#[derive(Clone, Copy, Debug, id::Id)] pub struct SegmentId(u32); #[binrw] #[brw(little)] -pub struct Segment{ - state:simulation::State, - //#[bw(try_calc(u32::try_from(instructions.len())))] - //instruction_count:u32, - //#[br(count=instruction_count)] - //instructions:Vec> +pub struct Segment { + state: simulation::State, + //#[bw(try_calc(u32::try_from(instructions.len())))] + //instruction_count:u32, + //#[br(count=instruction_count)] + //instructions:Vec> - //please remember that strafe ticks are implicit! 33% smaller bot files + //please remember that strafe ticks are implicit! 33% smaller bot files } -pub struct StreamableBot{ - file:crate::file::File, - //timeline:timeline::Timeline, - segment_id_to_block_id:Vec, +pub struct StreamableBot { + file: crate::file::File, + //timeline:timeline::Timeline, + segment_id_to_block_id: Vec, } -impl StreamableBot{ - pub(crate) fn new(file:crate::file::File)->Result{ - Err(Error::InvalidHeader) - } - pub fn load_segment(&mut self,segment_id:SegmentId)->Result{ - let block_id=*self.segment_id_to_block_id.get(segment_id.get() as usize).ok_or(Error::InvalidSegmentId(segment_id))?; - let mut block=self.file.block_reader(block_id).map_err(Error::File)?; - let segment=block.read_le().map_err(Error::InvalidSegment)?; - Ok(segment) - } +impl StreamableBot { + pub(crate) fn new(file: crate::file::File) -> Result { + Err(Error::InvalidHeader) + } + pub fn load_segment(&mut self, segment_id: SegmentId) -> Result { + let block_id = *self + .segment_id_to_block_id + .get(segment_id.get() as usize) + .ok_or(Error::InvalidSegmentId(segment_id))?; + let mut block = self.file.block_reader(block_id).map_err(Error::File)?; + let segment = block.read_le().map_err(Error::InvalidSegment)?; + Ok(segment) + } } diff --git a/lib/snf/src/demo.rs b/lib/snf/src/demo.rs index 3c870e8..5296b8d 100644 --- a/lib/snf/src/demo.rs +++ b/lib/snf/src/demo.rs @@ -1,23 +1,23 @@ use binrw::BinReaderExt; #[derive(Debug)] -pub enum Error{ - InvalidHeader, +pub enum Error { + InvalidHeader, } /* BLOCK_DEMO_HEADER: u32 num_maps for map_id in 0..num_maps{ - i64 simulation_time - u128 map_resource_id - u64 map_header_block_id + i64 simulation_time + u128 map_resource_id + u64 map_header_block_id } u32 num_bots for bot_id in 0..num_bots{ - i64 simulation_time - u128 bot_resource_id - u64 bot_header_block_id + i64 simulation_time + u128 bot_resource_id + u64 bot_header_block_id } //map loading timeline @@ -28,12 +28,12 @@ how to do worldstate for deathrun!? */ -pub struct StreamableDemo{ - map:Vec>, - bots:Vec>, +pub struct StreamableDemo { + map: Vec>, + bots: Vec>, } -impl StreamableDemo{ - pub(crate) fn new(file:crate::file::File)->Result{ - Err(Error::InvalidHeader) - } +impl StreamableDemo { + pub(crate) fn new(file: crate::file::File) -> Result { + Err(Error::InvalidHeader) + } } diff --git a/lib/snf/src/file.rs b/lib/snf/src/file.rs index fb04c3e..3886bd1 100644 --- a/lib/snf/src/file.rs +++ b/lib/snf/src/file.rs @@ -1,20 +1,20 @@ //file format "sniff" -use binrw::{binrw,BinReaderExt,io::TakeSeekExt}; +use binrw::{binrw, io::TakeSeekExt, BinReaderExt}; #[derive(Debug)] -pub enum Error{ - InvalidHeader(binrw::Error), - UnexpectedEOF, - InvalidBlockId(BlockId), - Seek(std::io::Error), +pub enum Error { + InvalidHeader(binrw::Error), + UnexpectedEOF, + InvalidBlockId(BlockId), + Seek(std::io::Error), } -impl std::fmt::Display for Error{ - fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ - write!(f,"{self:?}") - } +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{} +impl std::error::Error for Error {} /* spec @@ -24,18 +24,18 @@ impl std::error::Error for Error{} b"SNFB" u32 format_version u64 priming_bytes - //how many bytes of the file must be read to guarantee all of the expected - //format-specific metadata is available to facilitate streaming the remaining contents - //used by the database to guarantee that it serves at least the bare minimum + //how many bytes of the file must be read to guarantee all of the expected + //format-specific metadata is available to facilitate streaming the remaining contents + //used by the database to guarantee that it serves at least the bare minimum u128 resource_uuid - //identifies the file from anywhere for any other file + //identifies the file from anywhere for any other file //global block layout (variable size) u64 num_blocks //the start of the first block is implicitly after the global header (32) //num_blocks+1 used in Header.block_location is implicitly the end of the file for block_id in 1..num_blocks{ - u64 first_byte + u64 first_byte } //end global header @@ -47,64 +47,69 @@ for block_id in 1..num_blocks{ */ #[binrw] #[brw(little)] -#[derive(Clone,Copy,Debug)] -pub(crate) enum FourCC{ - #[brw(magic=b"SNFM")] - Map, - #[brw(magic=b"SNFB")] - Bot, - #[brw(magic=b"SNFD")] - Demo, +#[derive(Clone, Copy, Debug)] +pub(crate) enum FourCC { + #[brw(magic = b"SNFM")] + Map, + #[brw(magic = b"SNFB")] + Bot, + #[brw(magic = b"SNFD")] + Demo, } #[binrw] #[brw(little)] #[derive(Debug)] -pub struct Header{ - /// Type of file - pub fourcc:FourCC, - /// File format version - pub version:u32, - /// Minimum data required to know the location of all streamable resources for this specific file - pub priming:u64, - /// uuid for this file - pub resource:u128, - //don't need try_calc: the struct will force field initialization anyways and it can be calculated there - pub block_count:u32, - #[br(count=block_count+1)] - pub block_location:Vec, +pub struct Header { + /// Type of file + pub fourcc: FourCC, + /// File format version + pub version: u32, + /// Minimum data required to know the location of all streamable resources for this specific file + pub priming: u64, + /// uuid for this file + pub resource: u128, + //don't need try_calc: the struct will force field initialization anyways and it can be calculated there + pub block_count: u32, + #[br(count=block_count+1)] + pub block_location: Vec, } #[binrw] #[brw(little)] -#[derive(Clone,Copy,Debug,Hash,id::Id,Eq,Ord,PartialEq,PartialOrd)] +#[derive(Clone, Copy, Debug, Hash, id::Id, Eq, Ord, PartialEq, PartialOrd)] pub struct BlockId(u32); -pub(crate) struct File{ - header:Header, - //reference to the data - data:R, +pub(crate) struct File { + header: Header, + //reference to the data + data: R, } -impl File{ - pub(crate) fn new(mut input:R)->Result,Error>{ - Ok(File{ - header:input.read_le().map_err(Error::InvalidHeader)?, - data:input, - }) - } - pub(crate) fn data_mut(&mut self)->&mut R{ - &mut self.data - } - pub(crate) fn block_reader(&mut self,block_id:BlockId)->Result,Error>{ - if self.header.block_location.len() as u32<=block_id.get(){ - return Err(Error::InvalidBlockId(block_id)) - } - let block_start=self.header.block_location[block_id.get() as usize]; - let block_end=self.header.block_location[block_id.get() as usize+1]; - self.data.seek(std::io::SeekFrom::Start(block_start)).map_err(Error::Seek)?; - Ok(self.data_mut().take_seek(block_end-block_start)) - } - pub(crate) fn fourcc(&self)->FourCC{ - self.header.fourcc - } +impl File { + pub(crate) fn new(mut input: R) -> Result, Error> { + Ok(File { + header: input.read_le().map_err(Error::InvalidHeader)?, + data: input, + }) + } + pub(crate) fn data_mut(&mut self) -> &mut R { + &mut self.data + } + pub(crate) fn block_reader( + &mut self, + block_id: BlockId, + ) -> Result, Error> { + if self.header.block_location.len() as u32 <= block_id.get() { + return Err(Error::InvalidBlockId(block_id)); + } + let block_start = self.header.block_location[block_id.get() as usize]; + let block_end = self.header.block_location[block_id.get() as usize + 1]; + self.data + .seek(std::io::SeekFrom::Start(block_start)) + .map_err(Error::Seek)?; + Ok(self.data_mut().take_seek(block_end - block_start)) + } + pub(crate) fn fourcc(&self) -> FourCC { + self.header.fourcc + } } diff --git a/lib/snf/src/lib.rs b/lib/snf/src/lib.rs index 675dfdb..941d6b2 100644 --- a/lib/snf/src/lib.rs +++ b/lib/snf/src/lib.rs @@ -2,67 +2,66 @@ use binrw::BinReaderExt; mod newtypes; -mod file; -pub mod map; pub mod bot; pub mod demo; +mod file; +pub mod map; #[derive(Debug)] -pub enum Error{ - UnexpectedFourCC, - Header(file::Error), - Map(map::Error), - Bot(bot::Error), - Demo(demo::Error), +pub enum Error { + UnexpectedFourCC, + Header(file::Error), + Map(map::Error), + Bot(bot::Error), + Demo(demo::Error), } -impl std::fmt::Display for Error{ - fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ - write!(f,"{self:?}") - } +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{} +impl std::error::Error for Error {} -pub enum SNF{ - Map(map::StreamableMap), - Bot(bot::StreamableBot), - Demo(demo::StreamableDemo), +pub enum SNF { + Map(map::StreamableMap), + Bot(bot::StreamableBot), + Demo(demo::StreamableDemo), } -pub fn read_snf(input:R)->Result,Error>{ - let file=file::File::new(input).map_err(Error::Header)?; - Ok(match file.fourcc(){ - file::FourCC::Map=>SNF::Map(map::StreamableMap::new(file).map_err(Error::Map)?), - file::FourCC::Bot=>SNF::Bot(bot::StreamableBot::new(file).map_err(Error::Bot)?), - file::FourCC::Demo=>SNF::Demo(demo::StreamableDemo::new(file).map_err(Error::Demo)?), - }) +pub fn read_snf(input: R) -> Result, Error> { + let file = file::File::new(input).map_err(Error::Header)?; + Ok(match file.fourcc() { + file::FourCC::Map => SNF::Map(map::StreamableMap::new(file).map_err(Error::Map)?), + file::FourCC::Bot => SNF::Bot(bot::StreamableBot::new(file).map_err(Error::Bot)?), + file::FourCC::Demo => SNF::Demo(demo::StreamableDemo::new(file).map_err(Error::Demo)?), + }) } -pub fn read_map(input:R)->Result,Error>{ - let file=file::File::new(input).map_err(Error::Header)?; - match file.fourcc(){ - file::FourCC::Map=>Ok(map::StreamableMap::new(file).map_err(Error::Map)?), - _=>Err(Error::UnexpectedFourCC) - } +pub fn read_map(input: R) -> Result, Error> { + let file = file::File::new(input).map_err(Error::Header)?; + match file.fourcc() { + file::FourCC::Map => Ok(map::StreamableMap::new(file).map_err(Error::Map)?), + _ => Err(Error::UnexpectedFourCC), + } } -pub fn read_bot(input:R)->Result,Error>{ - let file=file::File::new(input).map_err(Error::Header)?; - match file.fourcc(){ - file::FourCC::Bot=>Ok(bot::StreamableBot::new(file).map_err(Error::Bot)?), - _=>Err(Error::UnexpectedFourCC) - } +pub fn read_bot(input: R) -> Result, Error> { + let file = file::File::new(input).map_err(Error::Header)?; + match file.fourcc() { + file::FourCC::Bot => Ok(bot::StreamableBot::new(file).map_err(Error::Bot)?), + _ => Err(Error::UnexpectedFourCC), + } } -pub fn read_demo(input:R)->Result,Error>{ - let file=file::File::new(input).map_err(Error::Header)?; - match file.fourcc(){ - file::FourCC::Demo=>Ok(demo::StreamableDemo::new(file).map_err(Error::Demo)?), - _=>Err(Error::UnexpectedFourCC) - } +pub fn read_demo(input: R) -> Result, Error> { + let file = file::File::new(input).map_err(Error::Header)?; + match file.fourcc() { + file::FourCC::Demo => Ok(demo::StreamableDemo::new(file).map_err(Error::Demo)?), + _ => Err(Error::UnexpectedFourCC), + } } #[cfg(test)] mod tests { - //use super::*; + //use super::*; - #[test] - fn test() { - } + #[test] + fn test() {} } diff --git a/lib/snf/src/map.rs b/lib/snf/src/map.rs index a5ef6f7..b4715d4 100644 --- a/lib/snf/src/map.rs +++ b/lib/snf/src/map.rs @@ -1,32 +1,32 @@ -use std::io::Read; use std::collections::HashMap; +use std::io::Read; -use crate::newtypes; use crate::file::BlockId; -use binrw::{binrw,BinReaderExt,BinWriterExt}; -use strafesnet_common::model; +use crate::newtypes; +use binrw::{binrw, BinReaderExt, BinWriterExt}; use strafesnet_common::aabb::Aabb; use strafesnet_common::bvh::BvhNode; use strafesnet_common::gameplay_modes; +use strafesnet_common::model; #[derive(Debug)] -pub enum Error{ - InvalidHeader(binrw::Error), - InvalidMode(newtypes::gameplay_modes::ModeError), - InvalidBlockId(BlockId), - InvalidMeshId(model::MeshId), - InvalidModelId(model::ModelId), - InvalidTextureId(model::TextureId), - InvalidData(binrw::Error), - IO(std::io::Error), - File(crate::file::Error), +pub enum Error { + InvalidHeader(binrw::Error), + InvalidMode(newtypes::gameplay_modes::ModeError), + InvalidBlockId(BlockId), + InvalidMeshId(model::MeshId), + InvalidModelId(model::ModelId), + InvalidTextureId(model::TextureId), + InvalidData(binrw::Error), + IO(std::io::Error), + File(crate::file::Error), } -impl std::fmt::Display for Error{ - fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ - write!(f,"{self:?}") - } +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{} +impl std::error::Error for Error {} /* block types @@ -39,7 +39,7 @@ u32 num_resource_blocks u32 num_resources_external //node 0 parent node is implied to be None for node_id in 1..num_nodes{ - u32 parent_node + u32 parent_node } //NOTE: alternate realities are not necessary. @@ -49,36 +49,36 @@ for node_id in 1..num_nodes{ //ideally spacial blocks are sorted from distance to start zone //texture blocks are inserted before the first spacial block they are used in for spacial_block_id in 0..num_spacial_blocks{ - u32 node_id - u32 block_id //data block - Aabb extents + u32 node_id + u32 block_id //data block + Aabb extents } //the order of these lists uniquely generates the incremental Ids //MeshId, TextureId etc. based on resource type //the first 8 bits of resource_uuid describe the type (mesh, texture, etc) //if the map file references only external resources, num_resource_blocks = 0 for resource_idx in 0..num_resource_blocks{ - Resource resource_type - u32 block_id + Resource resource_type + u32 block_id } for resource_idx in 0..num_resources_external{ - u128 resource_uuid + u128 resource_uuid } BLOCK_MAP_RESOURCE: Resource resource_type //an individual one of the following: - - model (Mesh) - - shader (compiled SPIR-V) - - image (JpegXL) - - sound (Opus) - - video (AV1) - - animation (Trey thing) + - model (Mesh) + - shader (compiled SPIR-V) + - image (JpegXL) + - sound (Opus) + - video (AV1) + - animation (Trey thing) BLOCK_MAP_REGION: u64 num_models for model_id in 0..num_models{ - ModelInstance mode_instance + ModelInstance mode_instance } */ @@ -86,359 +86,422 @@ for model_id in 0..num_models{ //if you hash the resource itself and set the first 8 bits to this, that's the resource uuid #[binrw] #[brw(little,repr=u8)] -enum ResourceType{ - Mesh, - Texture, - //Shader, - //Sound, - //Video, - //Animation, +enum ResourceType { + Mesh, + Texture, + //Shader, + //Sound, + //Video, + //Animation, } -const RESOURCE_TYPE_VARIANT_COUNT:u8=2; +const RESOURCE_TYPE_VARIANT_COUNT: u8 = 2; #[binrw] #[brw(little)] struct ResourceId(u128); -impl ResourceId{ - fn resource_type(&self)->Option{ - let discriminant=self.0 as u8; - //TODO: use this when it is stabilized https://github.com/rust-lang/rust/issues/73662 - //if (discriminant as usize)(){ - match discriminantSome(unsafe{std::mem::transmute::(discriminant)}), - false=>None, - } - } +impl ResourceId { + fn resource_type(&self) -> Option { + let discriminant = self.0 as u8; + //TODO: use this when it is stabilized https://github.com/rust-lang/rust/issues/73662 + //if (discriminant as usize)(){ + match discriminant < RESOURCE_TYPE_VARIANT_COUNT { + true => Some(unsafe { std::mem::transmute::(discriminant) }), + false => None, + } + } } -struct ResourceMap{ - meshes:HashMap, - textures:HashMap, +struct ResourceMap { + meshes: HashMap, + textures: HashMap, } -impl Default for ResourceMap{ - fn default()->Self{ - Self{ - meshes:HashMap::new(), - textures:HashMap::new(), - } - } +impl Default for ResourceMap { + fn default() -> Self { + Self { + meshes: HashMap::new(), + textures: HashMap::new(), + } + } } #[binrw] #[brw(little)] -struct SpacialBlockHeader{ - id:BlockId, - extents:newtypes::aabb::Aabb, +struct SpacialBlockHeader { + id: BlockId, + extents: newtypes::aabb::Aabb, } #[binrw] #[brw(little)] -struct ResourceBlockHeader{ - resource:ResourceType, - id:BlockId, +struct ResourceBlockHeader { + resource: ResourceType, + id: BlockId, } #[binrw] #[brw(little)] -struct ResourceExternalHeader{ - resource_uuid:ResourceId, +struct ResourceExternalHeader { + resource_uuid: ResourceId, } #[binrw] #[brw(little)] -struct MapHeader{ - num_spacial_blocks:u32, - num_resource_blocks:u32, - //num_resources_external:u32, - num_modes:u32, - num_attributes:u32, - num_render_configs:u32, - #[br(count=num_spacial_blocks)] - spacial_blocks:Vec, - #[br(count=num_resource_blocks)] - resource_blocks:Vec, - //#[br(count=num_resources_external)] - //external_resources:Vec, - #[br(count=num_modes)] - modes:Vec, - #[br(count=num_attributes)] - attributes:Vec, - #[br(count=num_render_configs)] - render_configs:Vec, +struct MapHeader { + num_spacial_blocks: u32, + num_resource_blocks: u32, + //num_resources_external:u32, + num_modes: u32, + num_attributes: u32, + num_render_configs: u32, + #[br(count=num_spacial_blocks)] + spacial_blocks: Vec, + #[br(count=num_resource_blocks)] + resource_blocks: Vec, + //#[br(count=num_resources_external)] + //external_resources:Vec, + #[br(count=num_modes)] + modes: Vec, + #[br(count=num_attributes)] + attributes: Vec, + #[br(count=num_render_configs)] + render_configs: Vec, } #[binrw] #[brw(little)] -struct Region{ - //consider including a bvh in the region instead of needing to rebalance the physics bvh on the fly - model_count:u32, - #[br(count=model_count)] - models:Vec<(u32,newtypes::model::Model)>, +struct Region { + //consider including a bvh in the region instead of needing to rebalance the physics bvh on the fly + model_count: u32, + #[br(count=model_count)] + models: Vec<(u32, newtypes::model::Model)>, } //code deduplication reused in into_complete_map -fn read_region(file:&mut crate::file::File,block_id:BlockId)->Result,Error>{ - //load region from disk - //parse the models and determine what resources need to be loaded - //load resources into self.resources - //return Region - let mut block=file.block_reader(block_id).map_err(Error::File)?; - let region:Region=block.read_le().map_err(Error::InvalidData)?; - Ok(region.models.into_iter().map(|(model_id,model)| - (model::ModelId::new(model_id),model.into()) - ).collect()) +fn read_region( + file: &mut crate::file::File, + block_id: BlockId, +) -> Result, Error> { + //load region from disk + //parse the models and determine what resources need to be loaded + //load resources into self.resources + //return Region + let mut block = file.block_reader(block_id).map_err(Error::File)?; + let region: Region = block.read_le().map_err(Error::InvalidData)?; + Ok(region + .models + .into_iter() + .map(|(model_id, model)| (model::ModelId::new(model_id), model.into())) + .collect()) } -fn read_mesh(file:&mut crate::file::File,block_id:BlockId)->Result{ - let mut block=file.block_reader(block_id).map_err(Error::File)?; - let mesh:newtypes::model::Mesh=block.read_le().map_err(Error::InvalidData)?; - Ok(mesh.into()) +fn read_mesh( + file: &mut crate::file::File, + block_id: BlockId, +) -> Result { + let mut block = file.block_reader(block_id).map_err(Error::File)?; + let mesh: newtypes::model::Mesh = block.read_le().map_err(Error::InvalidData)?; + Ok(mesh.into()) } -fn read_texture(file:&mut crate::file::File,block_id:BlockId)->Result,Error>{ - let mut block=file.block_reader(block_id).map_err(Error::File)?; - let mut texture=Vec::new(); - block.read_to_end(&mut texture).map_err(Error::IO)?; - Ok(texture) +fn read_texture( + file: &mut crate::file::File, + block_id: BlockId, +) -> Result, Error> { + let mut block = file.block_reader(block_id).map_err(Error::File)?; + let mut texture = Vec::new(); + block.read_to_end(&mut texture).map_err(Error::IO)?; + Ok(texture) } -pub struct StreamableMap{ - file:crate::file::File, - //this includes every platform... move the unconstrained datas to their appropriate data block? - modes:gameplay_modes::Modes, - //this is every possible attribute... need some sort of streaming system - attributes:Vec, - //this is every possible render configuration... shaders and such... need streaming - render_configs:Vec, - //this makes sense to keep in memory for streaming, a map of which blocks occupy what space - bvh:BvhNode, - //something something resources hashmaps - resource_blocks:ResourceMap, - //resource_external:ResourceMap, +pub struct StreamableMap { + file: crate::file::File, + //this includes every platform... move the unconstrained datas to their appropriate data block? + modes: gameplay_modes::Modes, + //this is every possible attribute... need some sort of streaming system + attributes: Vec, + //this is every possible render configuration... shaders and such... need streaming + render_configs: Vec, + //this makes sense to keep in memory for streaming, a map of which blocks occupy what space + bvh: BvhNode, + //something something resources hashmaps + resource_blocks: ResourceMap, + //resource_external:ResourceMap, } -impl StreamableMap{ - pub(crate) fn new(mut file:crate::file::File)->Result{ - //assume the file seek is in the right place to start reading a map header - let header:MapHeader=file.data_mut().read_le().map_err(Error::InvalidHeader)?; - let modes=header.modes.into_iter().map(TryInto::try_into).collect::>().map_err(Error::InvalidMode)?; - let attributes=header.attributes.into_iter().map(Into::into).collect(); - let render_configs=header.render_configs.into_iter().map(Into::into).collect(); - let bvh=header.spacial_blocks.into_iter().map(|spacial_block| - (spacial_block.id,spacial_block.extents.into()) - ).collect(); - //generate mesh ids and texture ids from resource list order - let mut resource_blocks=ResourceMap::default(); - for resource_block_header in header.resource_blocks{ - match resource_block_header.resource{ - ResourceType::Mesh=>{ - resource_blocks.meshes.insert( - //generate the id from the current length - model::MeshId::new(resource_blocks.meshes.len() as u32), - resource_block_header.id - ); - }, - ResourceType::Texture=>{ - resource_blocks.textures.insert( - model::TextureId::new(resource_blocks.textures.len() as u32), - resource_block_header.id - ); - }, - } - } - Ok(Self{ - file, - modes:strafesnet_common::gameplay_modes::Modes::new(modes), - attributes, - render_configs, - bvh:strafesnet_common::bvh::generate_bvh(bvh), - resource_blocks, - //resource_external:Default::default(), - }) - } - pub fn get_intersecting_region_block_ids(&self,aabb:&Aabb)->Vec{ - let mut block_ids=Vec::new(); - self.bvh.the_tester(aabb,&mut |&block_id|block_ids.push(block_id)); - block_ids - } - pub fn load_region(&mut self,block_id:BlockId)->Result,Error>{ - read_region(&mut self.file,block_id) - } - pub fn load_mesh(&mut self,mesh_id:model::MeshId)->Result{ - let block_id=*self.resource_blocks.meshes.get(&mesh_id).ok_or(Error::InvalidMeshId(mesh_id))?; - read_mesh(&mut self.file,block_id) - } - pub fn load_texture(&mut self,texture_id:model::TextureId)->Result,Error>{ - let block_id=*self.resource_blocks.textures.get(&texture_id).ok_or(Error::InvalidTextureId(texture_id))?; - read_texture(&mut self.file,block_id) - } - pub fn into_complete_map(mut self)->Result{ - let mut block_ids=Vec::new(); - self.bvh.into_visitor(&mut |block_id|block_ids.push(block_id)); - //count on reading the file in sequential order being fastest - block_ids.sort_unstable(); - //load all regions - let mut model_pairs=HashMap::new(); - for block_id in block_ids{ - model_pairs.extend(read_region(&mut self.file,block_id)?); - } - let mut models=Vec::with_capacity(model_pairs.len()); - for model_id in 0..model_pairs.len() as u32{ - let model_id=model::ModelId::new(model_id); - models.push(model_pairs.remove(&model_id).ok_or(Error::InvalidModelId(model_id))?); - } - //load all meshes - let mut meshes=Vec::with_capacity(self.resource_blocks.meshes.len()); - for mesh_id in 0..self.resource_blocks.meshes.len() as u32{ - let mesh_id=model::MeshId::new(mesh_id); - let block_id=self.resource_blocks.meshes[&mesh_id]; - meshes.push(read_mesh(&mut self.file,block_id)?); - }; - //load all textures - let mut textures=Vec::with_capacity(self.resource_blocks.textures.len()); - for texture_id in 0..self.resource_blocks.textures.len() as u32{ - let texture_id=model::TextureId::new(texture_id); - let block_id=self.resource_blocks.textures[&texture_id]; - textures.push(read_texture(&mut self.file,block_id)?); - } - Ok(strafesnet_common::map::CompleteMap{ - modes:self.modes, - attributes:self.attributes, - meshes, - models, - textures, - render_configs:self.render_configs, - }) - } +impl StreamableMap { + pub(crate) fn new(mut file: crate::file::File) -> Result { + //assume the file seek is in the right place to start reading a map header + let header: MapHeader = file.data_mut().read_le().map_err(Error::InvalidHeader)?; + let modes = header + .modes + .into_iter() + .map(TryInto::try_into) + .collect::>() + .map_err(Error::InvalidMode)?; + let attributes = header.attributes.into_iter().map(Into::into).collect(); + let render_configs = header.render_configs.into_iter().map(Into::into).collect(); + let bvh = header + .spacial_blocks + .into_iter() + .map(|spacial_block| (spacial_block.id, spacial_block.extents.into())) + .collect(); + //generate mesh ids and texture ids from resource list order + let mut resource_blocks = ResourceMap::default(); + for resource_block_header in header.resource_blocks { + match resource_block_header.resource { + ResourceType::Mesh => { + resource_blocks.meshes.insert( + //generate the id from the current length + model::MeshId::new(resource_blocks.meshes.len() as u32), + resource_block_header.id, + ); + } + ResourceType::Texture => { + resource_blocks.textures.insert( + model::TextureId::new(resource_blocks.textures.len() as u32), + resource_block_header.id, + ); + } + } + } + Ok(Self { + file, + modes: strafesnet_common::gameplay_modes::Modes::new(modes), + attributes, + render_configs, + bvh: strafesnet_common::bvh::generate_bvh(bvh), + resource_blocks, + //resource_external:Default::default(), + }) + } + pub fn get_intersecting_region_block_ids(&self, aabb: &Aabb) -> Vec { + let mut block_ids = Vec::new(); + self.bvh + .the_tester(aabb, &mut |&block_id| block_ids.push(block_id)); + block_ids + } + pub fn load_region( + &mut self, + block_id: BlockId, + ) -> Result, Error> { + read_region(&mut self.file, block_id) + } + pub fn load_mesh(&mut self, mesh_id: model::MeshId) -> Result { + let block_id = *self + .resource_blocks + .meshes + .get(&mesh_id) + .ok_or(Error::InvalidMeshId(mesh_id))?; + read_mesh(&mut self.file, block_id) + } + pub fn load_texture(&mut self, texture_id: model::TextureId) -> Result, Error> { + let block_id = *self + .resource_blocks + .textures + .get(&texture_id) + .ok_or(Error::InvalidTextureId(texture_id))?; + read_texture(&mut self.file, block_id) + } + pub fn into_complete_map(mut self) -> Result { + let mut block_ids = Vec::new(); + self.bvh + .into_visitor(&mut |block_id| block_ids.push(block_id)); + //count on reading the file in sequential order being fastest + block_ids.sort_unstable(); + //load all regions + let mut model_pairs = HashMap::new(); + for block_id in block_ids { + model_pairs.extend(read_region(&mut self.file, block_id)?); + } + let mut models = Vec::with_capacity(model_pairs.len()); + for model_id in 0..model_pairs.len() as u32 { + let model_id = model::ModelId::new(model_id); + models.push( + model_pairs + .remove(&model_id) + .ok_or(Error::InvalidModelId(model_id))?, + ); + } + //load all meshes + let mut meshes = Vec::with_capacity(self.resource_blocks.meshes.len()); + for mesh_id in 0..self.resource_blocks.meshes.len() as u32 { + let mesh_id = model::MeshId::new(mesh_id); + let block_id = self.resource_blocks.meshes[&mesh_id]; + meshes.push(read_mesh(&mut self.file, block_id)?); + } + //load all textures + let mut textures = Vec::with_capacity(self.resource_blocks.textures.len()); + for texture_id in 0..self.resource_blocks.textures.len() as u32 { + let texture_id = model::TextureId::new(texture_id); + let block_id = self.resource_blocks.textures[&texture_id]; + textures.push(read_texture(&mut self.file, block_id)?); + } + Ok(strafesnet_common::map::CompleteMap { + modes: self.modes, + attributes: self.attributes, + meshes, + models, + textures, + render_configs: self.render_configs, + }) + } } -const BVH_NODE_MAX_WEIGHT:usize=64*1024;//64 kB +const BVH_NODE_MAX_WEIGHT: usize = 64 * 1024; //64 kB fn collect_spacial_blocks( - block_location:&mut Vec, - block_headers:&mut Vec, - sequential_block_data:&mut std::io::Cursor<&mut Vec>, - bvh_node:strafesnet_common::bvh::BvhWeightNode -)->Result<(),Error>{ - //inspect the node weights top-down. - //When a node weighs less than the limit, - //serialize its entire contents into a region block - if *bvh_node.weight(){ - for bvh_node in bvh_node_list{ - collect_spacial_blocks(block_location,block_headers,sequential_block_data,bvh_node)?; - } - }, - strafesnet_common::bvh::RecursiveContent::Leaf(_)=>panic!(),//bvh branches are 20 leaves minimum - } - } - Ok(()) + block_location: &mut Vec, + block_headers: &mut Vec, + sequential_block_data: &mut std::io::Cursor<&mut Vec>, + bvh_node: strafesnet_common::bvh::BvhWeightNode< + usize, + (model::ModelId, newtypes::model::Model), + >, +) -> Result<(), Error> { + //inspect the node weights top-down. + //When a node weighs less than the limit, + //serialize its entire contents into a region block + if *bvh_node.weight() < BVH_NODE_MAX_WEIGHT { + let mut models = Vec::new(); + let mut model_count = 0; + let extents = bvh_node.aabb().clone().into(); + bvh_node.into_visitor(&mut |(model_id, model)| { + model_count += 1; + models.push((model_id.get(), model)); + }); + let id = BlockId::new(block_headers.len() as u32 + 1); + block_headers.push(SpacialBlockHeader { id, extents }); + let region = Region { + model_count, + models, + }; + binrw::BinWrite::write_le(®ion, sequential_block_data).map_err(Error::InvalidData)?; + block_location.push(sequential_block_data.position()); + } else { + match bvh_node.into_content() { + strafesnet_common::bvh::RecursiveContent::Branch(bvh_node_list) => { + for bvh_node in bvh_node_list { + collect_spacial_blocks( + block_location, + block_headers, + sequential_block_data, + bvh_node, + )?; + } + } + strafesnet_common::bvh::RecursiveContent::Leaf(_) => panic!(), //bvh branches are 20 leaves minimum + } + } + Ok(()) } /// TODO: Optionally provide a bot that describes the path through the map /// otherwise sort by distance to start zone -pub fn write_map(mut writer:W,map:strafesnet_common::map::CompleteMap)->Result<(),Error>{ - //serialize models and make a bvh that knows the file size of the branch - let boxen=map.models.into_iter().enumerate().map(|(model_id,model)|{ - //grow your own aabb - let mesh=map.meshes.get(model.mesh.get() as usize).ok_or(Error::InvalidMeshId(model.mesh))?; - let mut aabb=strafesnet_common::aabb::Aabb::default(); - for &pos in &mesh.unique_pos{ - aabb.grow(model.transform.transform_point3(pos).fix_1()); - } - Ok(((model::ModelId::new(model_id as u32),model.into()),aabb)) - }).collect::,_>>()?; - let bvh=strafesnet_common::bvh::generate_bvh(boxen).weigh_contents(&|_|std::mem::size_of::()); - //build blocks - //block location is initialized with two values - //the first value represents the location of the first byte after the file header - //the second value represents the first byte of the second data block - //the first data block is always the map header, so the difference is the map header size. - //this information is filled in later after the sizes are known. - let mut block_location=vec![0,0];//for file header - let mut spacial_blocks=Vec::new();//for map header - let mut sequential_block_data=Vec::new(); - let mut cursor_to_data=std::io::Cursor::new(&mut sequential_block_data); - collect_spacial_blocks(&mut block_location,&mut spacial_blocks,&mut cursor_to_data,bvh)?; - let mut block_count=spacial_blocks.len() as u32+1;//continue block id - let mut resource_blocks=Vec::new();//for map header - //meshes - for mesh in map.meshes.into_iter(){ - resource_blocks.push(ResourceBlockHeader{ - resource:ResourceType::Mesh, - id:BlockId::new(block_count), - }); - block_count+=1; - let serializable_mesh:newtypes::model::Mesh=mesh.into(); - binrw::BinWrite::write_le(&serializable_mesh,&mut cursor_to_data).map_err(Error::InvalidData)?; - block_location.push(cursor_to_data.position()); - } - //textures - for mut texture in map.textures.into_iter(){ - resource_blocks.push(ResourceBlockHeader{ - resource:ResourceType::Texture, - id:BlockId::new(block_count), - }); - block_count+=1; - sequential_block_data.append(&mut texture); - block_location.push(sequential_block_data.len() as u64); - } - //build header - let map_header=MapHeader{ - num_spacial_blocks:spacial_blocks.len() as u32, - num_resource_blocks:resource_blocks.len() as u32, - //num_resources_external:0, - num_modes:map.modes.modes.len() as u32, - num_attributes:map.attributes.len() as u32, - num_render_configs:map.render_configs.len() as u32, - spacial_blocks, - resource_blocks, - //external_resources:Vec::new(), - modes:map.modes.modes.into_iter().map(Into::into).collect(), - attributes:map.attributes.into_iter().map(Into::into).collect(), - render_configs:map.render_configs.into_iter().map(Into::into).collect(), - }; - let mut file_header=crate::file::Header{ - fourcc:crate::file::FourCC::Map, - version:0, - priming:0, - resource:0, - block_count, - block_location, - }; - //probe header length - let mut file_header_data=Vec::new(); - binrw::BinWrite::write_le(&file_header,&mut std::io::Cursor::new(&mut file_header_data)).map_err(Error::InvalidData)?; - let mut map_header_data=Vec::new(); - binrw::BinWrite::write_le(&map_header,&mut std::io::Cursor::new(&mut map_header_data)).map_err(Error::InvalidData)?; +pub fn write_map( + mut writer: W, + map: strafesnet_common::map::CompleteMap, +) -> Result<(), Error> { + //serialize models and make a bvh that knows the file size of the branch + let boxen = map + .models + .into_iter() + .enumerate() + .map(|(model_id, model)| { + //grow your own aabb + let mesh = map + .meshes + .get(model.mesh.get() as usize) + .ok_or(Error::InvalidMeshId(model.mesh))?; + let mut aabb = strafesnet_common::aabb::Aabb::default(); + for &pos in &mesh.unique_pos { + aabb.grow(model.transform.transform_point3(pos).fix_1()); + } + Ok(((model::ModelId::new(model_id as u32), model.into()), aabb)) + }) + .collect::, _>>()?; + let bvh = strafesnet_common::bvh::generate_bvh(boxen) + .weigh_contents(&|_| std::mem::size_of::()); + //build blocks + //block location is initialized with two values + //the first value represents the location of the first byte after the file header + //the second value represents the first byte of the second data block + //the first data block is always the map header, so the difference is the map header size. + //this information is filled in later after the sizes are known. + let mut block_location = vec![0, 0]; //for file header + let mut spacial_blocks = Vec::new(); //for map header + let mut sequential_block_data = Vec::new(); + let mut cursor_to_data = std::io::Cursor::new(&mut sequential_block_data); + collect_spacial_blocks( + &mut block_location, + &mut spacial_blocks, + &mut cursor_to_data, + bvh, + )?; + let mut block_count = spacial_blocks.len() as u32 + 1; //continue block id + let mut resource_blocks = Vec::new(); //for map header + //meshes + for mesh in map.meshes.into_iter() { + resource_blocks.push(ResourceBlockHeader { + resource: ResourceType::Mesh, + id: BlockId::new(block_count), + }); + block_count += 1; + let serializable_mesh: newtypes::model::Mesh = mesh.into(); + binrw::BinWrite::write_le(&serializable_mesh, &mut cursor_to_data) + .map_err(Error::InvalidData)?; + block_location.push(cursor_to_data.position()); + } + //textures + for mut texture in map.textures.into_iter() { + resource_blocks.push(ResourceBlockHeader { + resource: ResourceType::Texture, + id: BlockId::new(block_count), + }); + block_count += 1; + sequential_block_data.append(&mut texture); + block_location.push(sequential_block_data.len() as u64); + } + //build header + let map_header = MapHeader { + num_spacial_blocks: spacial_blocks.len() as u32, + num_resource_blocks: resource_blocks.len() as u32, + //num_resources_external:0, + num_modes: map.modes.modes.len() as u32, + num_attributes: map.attributes.len() as u32, + num_render_configs: map.render_configs.len() as u32, + spacial_blocks, + resource_blocks, + //external_resources:Vec::new(), + modes: map.modes.modes.into_iter().map(Into::into).collect(), + attributes: map.attributes.into_iter().map(Into::into).collect(), + render_configs: map.render_configs.into_iter().map(Into::into).collect(), + }; + let mut file_header = crate::file::Header { + fourcc: crate::file::FourCC::Map, + version: 0, + priming: 0, + resource: 0, + block_count, + block_location, + }; + //probe header length + let mut file_header_data = Vec::new(); + binrw::BinWrite::write_le( + &file_header, + &mut std::io::Cursor::new(&mut file_header_data), + ) + .map_err(Error::InvalidData)?; + let mut map_header_data = Vec::new(); + binrw::BinWrite::write_le(&map_header, &mut std::io::Cursor::new(&mut map_header_data)) + .map_err(Error::InvalidData)?; - //update file header according to probe data - let mut offset=file_header_data.len() as u64; - file_header.priming=offset; - file_header.block_location[0]=offset; - offset+=map_header_data.len() as u64; - for position in &mut file_header.block_location[1..]{ - *position+=offset; - } + //update file header according to probe data + let mut offset = file_header_data.len() as u64; + file_header.priming = offset; + file_header.block_location[0] = offset; + offset += map_header_data.len() as u64; + for position in &mut file_header.block_location[1..] { + *position += offset; + } - //write (updated) file header - writer.write_le(&file_header).map_err(Error::InvalidData)?; - //write map header - writer.write(&map_header_data).map_err(Error::IO)?; - //write blocks - writer.write(&sequential_block_data).map_err(Error::IO)?; - Ok(()) + //write (updated) file header + writer.write_le(&file_header).map_err(Error::InvalidData)?; + //write map header + writer.write(&map_header_data).map_err(Error::IO)?; + //write blocks + writer.write(&sequential_block_data).map_err(Error::IO)?; + Ok(()) } diff --git a/lib/snf/src/newtypes.rs b/lib/snf/src/newtypes.rs index 19e2e59..90a2351 100644 --- a/lib/snf/src/newtypes.rs +++ b/lib/snf/src/newtypes.rs @@ -1,7 +1,7 @@ -mod common; pub mod aabb; -pub mod model; -pub mod integer; +mod common; +pub mod gameplay_attributes; pub mod gameplay_modes; pub mod gameplay_style; -pub mod gameplay_attributes; +pub mod integer; +pub mod model; diff --git a/lib/snf/src/newtypes/aabb.rs b/lib/snf/src/newtypes/aabb.rs index 4443e55..f4267ce 100644 --- a/lib/snf/src/newtypes/aabb.rs +++ b/lib/snf/src/newtypes/aabb.rs @@ -1,23 +1,23 @@ use super::integer::Planar64Vec3; #[binrw::binrw] #[brw(little)] -pub struct Aabb{ - pub min:Planar64Vec3, - pub max:Planar64Vec3, +pub struct Aabb { + pub min: Planar64Vec3, + pub max: Planar64Vec3, } -impl Into for Aabb{ - fn into(self)->strafesnet_common::aabb::Aabb{ - strafesnet_common::aabb::Aabb::new( - strafesnet_common::integer::vec3::raw_array(self.min), - strafesnet_common::integer::vec3::raw_array(self.max), - ) - } +impl Into for Aabb { + fn into(self) -> strafesnet_common::aabb::Aabb { + strafesnet_common::aabb::Aabb::new( + strafesnet_common::integer::vec3::raw_array(self.min), + strafesnet_common::integer::vec3::raw_array(self.max), + ) + } } -impl From for Aabb{ - fn from(value:strafesnet_common::aabb::Aabb)->Self{ - Self{ - max:value.max().map(|t|t.to_raw()).to_array(), - min:value.min().map(|t|t.to_raw()).to_array(), - } - } +impl From for Aabb { + fn from(value: strafesnet_common::aabb::Aabb) -> Self { + Self { + max: value.max().map(|t| t.to_raw()).to_array(), + min: value.min().map(|t| t.to_raw()).to_array(), + } + } } diff --git a/lib/snf/src/newtypes/common.rs b/lib/snf/src/newtypes/common.rs index cec7e09..f10af72 100644 --- a/lib/snf/src/newtypes/common.rs +++ b/lib/snf/src/newtypes/common.rs @@ -1,3 +1,3 @@ -pub const fn flag(b:bool,mask:u8)->u8{ - (-(b as i8) as u8)&mask +pub const fn flag(b: bool, mask: u8) -> u8 { + (-(b as i8) as u8) & mask } diff --git a/lib/snf/src/newtypes/gameplay_attributes.rs b/lib/snf/src/newtypes/gameplay_attributes.rs index 3dcfb81..9656506 100644 --- a/lib/snf/src/newtypes/gameplay_attributes.rs +++ b/lib/snf/src/newtypes/gameplay_attributes.rs @@ -1,493 +1,541 @@ use super::common::flag; -use super::integer::{Time,Planar64,Planar64Vec3}; +use super::integer::{Planar64, Planar64Vec3, Time}; #[binrw::binrw] #[brw(little)] -pub struct ContactingLadder{ - #[br(map=|paused:u8|paused!=0)] - #[bw(map=|paused:&bool|*paused as u8)] - pub sticky:bool, +pub struct ContactingLadder { + #[br(map=|paused:u8|paused!=0)] + #[bw(map=|paused:&bool|*paused as u8)] + pub sticky: bool, } -impl Into for ContactingLadder{ - fn into(self)->strafesnet_common::gameplay_attributes::ContactingLadder{ - strafesnet_common::gameplay_attributes::ContactingLadder{ - sticky:self.sticky.into(), - } - } +impl Into for ContactingLadder { + fn into(self) -> strafesnet_common::gameplay_attributes::ContactingLadder { + strafesnet_common::gameplay_attributes::ContactingLadder { + sticky: self.sticky.into(), + } + } } -impl From for ContactingLadder{ - fn from(value:strafesnet_common::gameplay_attributes::ContactingLadder)->Self{ - Self{ - sticky:value.sticky.into(), - } - } +impl From for ContactingLadder { + fn from(value: strafesnet_common::gameplay_attributes::ContactingLadder) -> Self { + Self { + sticky: value.sticky.into(), + } + } } #[binrw::binrw] #[brw(little)] -pub enum ContactingBehaviour{ - #[brw(magic=0u8)] - Surf, - #[brw(magic=1u8)] - Ladder(ContactingLadder), - #[brw(magic=2u8)] - NoJump, - #[brw(magic=3u8)] - Cling, - #[brw(magic=4u8)] - Elastic(u32), +pub enum ContactingBehaviour { + #[brw(magic = 0u8)] + Surf, + #[brw(magic = 1u8)] + Ladder(ContactingLadder), + #[brw(magic = 2u8)] + NoJump, + #[brw(magic = 3u8)] + Cling, + #[brw(magic = 4u8)] + Elastic(u32), } -impl Into for ContactingBehaviour{ - fn into(self)->strafesnet_common::gameplay_attributes::ContactingBehaviour{ - match self{ - ContactingBehaviour::Surf=> - strafesnet_common::gameplay_attributes::ContactingBehaviour::Surf, - ContactingBehaviour::Ladder(contacting_ladder)=> - strafesnet_common::gameplay_attributes::ContactingBehaviour::Ladder( - contacting_ladder.into(), - ), - ContactingBehaviour::NoJump=> - strafesnet_common::gameplay_attributes::ContactingBehaviour::NoJump, - ContactingBehaviour::Cling=> - strafesnet_common::gameplay_attributes::ContactingBehaviour::Cling, - ContactingBehaviour::Elastic(elasticity)=> - strafesnet_common::gameplay_attributes::ContactingBehaviour::Elastic(elasticity), - } - } +impl Into for ContactingBehaviour { + fn into(self) -> strafesnet_common::gameplay_attributes::ContactingBehaviour { + match self { + ContactingBehaviour::Surf => { + strafesnet_common::gameplay_attributes::ContactingBehaviour::Surf + } + ContactingBehaviour::Ladder(contacting_ladder) => { + strafesnet_common::gameplay_attributes::ContactingBehaviour::Ladder( + contacting_ladder.into(), + ) + } + ContactingBehaviour::NoJump => { + strafesnet_common::gameplay_attributes::ContactingBehaviour::NoJump + } + ContactingBehaviour::Cling => { + strafesnet_common::gameplay_attributes::ContactingBehaviour::Cling + } + ContactingBehaviour::Elastic(elasticity) => { + strafesnet_common::gameplay_attributes::ContactingBehaviour::Elastic(elasticity) + } + } + } } -impl From for ContactingBehaviour{ - fn from(value:strafesnet_common::gameplay_attributes::ContactingBehaviour)->Self{ - match value{ - strafesnet_common::gameplay_attributes::ContactingBehaviour::Surf=> - ContactingBehaviour::Surf, - strafesnet_common::gameplay_attributes::ContactingBehaviour::Ladder(contacting_ladder)=> - ContactingBehaviour::Ladder( - contacting_ladder.into() - ), - strafesnet_common::gameplay_attributes::ContactingBehaviour::NoJump=> - ContactingBehaviour::NoJump, - strafesnet_common::gameplay_attributes::ContactingBehaviour::Cling=> - ContactingBehaviour::Cling, - strafesnet_common::gameplay_attributes::ContactingBehaviour::Elastic(elasticity)=> - ContactingBehaviour::Elastic(elasticity), - } - } +impl From for ContactingBehaviour { + fn from(value: strafesnet_common::gameplay_attributes::ContactingBehaviour) -> Self { + match value { + strafesnet_common::gameplay_attributes::ContactingBehaviour::Surf => { + ContactingBehaviour::Surf + } + strafesnet_common::gameplay_attributes::ContactingBehaviour::Ladder( + contacting_ladder, + ) => ContactingBehaviour::Ladder(contacting_ladder.into()), + strafesnet_common::gameplay_attributes::ContactingBehaviour::NoJump => { + ContactingBehaviour::NoJump + } + strafesnet_common::gameplay_attributes::ContactingBehaviour::Cling => { + ContactingBehaviour::Cling + } + strafesnet_common::gameplay_attributes::ContactingBehaviour::Elastic(elasticity) => { + ContactingBehaviour::Elastic(elasticity) + } + } + } } #[binrw::binrw] #[brw(little)] -pub struct IntersectingWater{ - pub viscosity:Planar64, - pub density:Planar64, - pub velocity:Planar64Vec3, +pub struct IntersectingWater { + pub viscosity: Planar64, + pub density: Planar64, + pub velocity: Planar64Vec3, } -impl Into for IntersectingWater{ - fn into(self)->strafesnet_common::gameplay_attributes::IntersectingWater{ - strafesnet_common::gameplay_attributes::IntersectingWater{ - viscosity:strafesnet_common::integer::Planar64::raw(self.viscosity), - density:strafesnet_common::integer::Planar64::raw(self.density), - velocity:strafesnet_common::integer::vec3::raw_array(self.velocity), - } - } +impl Into for IntersectingWater { + fn into(self) -> strafesnet_common::gameplay_attributes::IntersectingWater { + strafesnet_common::gameplay_attributes::IntersectingWater { + viscosity: strafesnet_common::integer::Planar64::raw(self.viscosity), + density: strafesnet_common::integer::Planar64::raw(self.density), + velocity: strafesnet_common::integer::vec3::raw_array(self.velocity), + } + } } -impl From for IntersectingWater{ - fn from(value:strafesnet_common::gameplay_attributes::IntersectingWater)->Self{ - Self{ - viscosity:value.viscosity.to_raw(), - density:value.density.to_raw(), - velocity:value.velocity.map(|t|t.to_raw()).to_array(), - } - } +impl From for IntersectingWater { + fn from(value: strafesnet_common::gameplay_attributes::IntersectingWater) -> Self { + Self { + viscosity: value.viscosity.to_raw(), + density: value.density.to_raw(), + velocity: value.velocity.map(|t| t.to_raw()).to_array(), + } + } } #[binrw::binrw] #[brw(little)] -pub struct Accelerator{ - pub acceleration:Planar64Vec3 +pub struct Accelerator { + pub acceleration: Planar64Vec3, } -impl Into for Accelerator{ - fn into(self)->strafesnet_common::gameplay_attributes::Accelerator{ - strafesnet_common::gameplay_attributes::Accelerator{ - acceleration:strafesnet_common::integer::vec3::raw_array(self.acceleration) - } - } +impl Into for Accelerator { + fn into(self) -> strafesnet_common::gameplay_attributes::Accelerator { + strafesnet_common::gameplay_attributes::Accelerator { + acceleration: strafesnet_common::integer::vec3::raw_array(self.acceleration), + } + } } -impl From for Accelerator{ - fn from(value:strafesnet_common::gameplay_attributes::Accelerator)->Self{ - Self{ - acceleration:value.acceleration.map(|t|t.to_raw()).to_array(), - } - } +impl From for Accelerator { + fn from(value: strafesnet_common::gameplay_attributes::Accelerator) -> Self { + Self { + acceleration: value.acceleration.map(|t| t.to_raw()).to_array(), + } + } } #[binrw::binrw] #[brw(little)] -pub enum Booster{ - #[brw(magic=0u8)] - Velocity(Planar64Vec3), - #[brw(magic=1u8)] - Energy{direction:Planar64Vec3,energy:Planar64}, - #[brw(magic=2u8)] - AirTime(Time), - #[brw(magic=3u8)] - Height(Planar64), - +pub enum Booster { + #[brw(magic = 0u8)] + Velocity(Planar64Vec3), + #[brw(magic = 1u8)] + Energy { + direction: Planar64Vec3, + energy: Planar64, + }, + #[brw(magic = 2u8)] + AirTime(Time), + #[brw(magic = 3u8)] + Height(Planar64), } -impl Into for Booster{ - fn into(self)->strafesnet_common::gameplay_attributes::Booster{ - match self{ - Booster::Velocity(velocity)=> - strafesnet_common::gameplay_attributes::Booster::Velocity( - strafesnet_common::integer::vec3::raw_array(velocity) - ), - Booster::Energy{direction,energy}=> - strafesnet_common::gameplay_attributes::Booster::Energy{ - direction:strafesnet_common::integer::vec3::raw_array(direction), - energy:strafesnet_common::integer::Planar64::raw(energy) - }, - Booster::AirTime(time)=> - strafesnet_common::gameplay_attributes::Booster::AirTime( - strafesnet_common::integer::Time::raw(time) - ), - Booster::Height(height)=> - strafesnet_common::gameplay_attributes::Booster::Height( - strafesnet_common::integer::Planar64::raw(height) - ), - } - } +impl Into for Booster { + fn into(self) -> strafesnet_common::gameplay_attributes::Booster { + match self { + Booster::Velocity(velocity) => { + strafesnet_common::gameplay_attributes::Booster::Velocity( + strafesnet_common::integer::vec3::raw_array(velocity), + ) + } + Booster::Energy { direction, energy } => { + strafesnet_common::gameplay_attributes::Booster::Energy { + direction: strafesnet_common::integer::vec3::raw_array(direction), + energy: strafesnet_common::integer::Planar64::raw(energy), + } + } + Booster::AirTime(time) => strafesnet_common::gameplay_attributes::Booster::AirTime( + strafesnet_common::integer::Time::raw(time), + ), + Booster::Height(height) => strafesnet_common::gameplay_attributes::Booster::Height( + strafesnet_common::integer::Planar64::raw(height), + ), + } + } } -impl From for Booster{ - fn from(value:strafesnet_common::gameplay_attributes::Booster)->Self{ - match value{ - strafesnet_common::gameplay_attributes::Booster::Velocity(velocity)=> - Booster::Velocity(velocity.map(|t|t.to_raw()).to_array()), - strafesnet_common::gameplay_attributes::Booster::Energy{direction,energy}=> - Booster::Energy{ - direction:direction.map(|t|t.to_raw()).to_array(), - energy:energy.to_raw(), - }, - strafesnet_common::gameplay_attributes::Booster::AirTime(time)=> - Booster::AirTime(time.get()), - strafesnet_common::gameplay_attributes::Booster::Height(height)=> - Booster::Height(height.to_raw()), - } - } +impl From for Booster { + fn from(value: strafesnet_common::gameplay_attributes::Booster) -> Self { + match value { + strafesnet_common::gameplay_attributes::Booster::Velocity(velocity) => { + Booster::Velocity(velocity.map(|t| t.to_raw()).to_array()) + } + strafesnet_common::gameplay_attributes::Booster::Energy { direction, energy } => { + Booster::Energy { + direction: direction.map(|t| t.to_raw()).to_array(), + energy: energy.to_raw(), + } + } + strafesnet_common::gameplay_attributes::Booster::AirTime(time) => { + Booster::AirTime(time.get()) + } + strafesnet_common::gameplay_attributes::Booster::Height(height) => { + Booster::Height(height.to_raw()) + } + } + } } #[binrw::binrw] #[brw(little,repr=u8)] -pub enum TrajectoryChoice{ - HighArcLongDuration, - LowArcShortDuration, +pub enum TrajectoryChoice { + HighArcLongDuration, + LowArcShortDuration, } -impl Into for TrajectoryChoice{ - fn into(self)->strafesnet_common::gameplay_attributes::TrajectoryChoice{ - match self{ - TrajectoryChoice::HighArcLongDuration=> - strafesnet_common::gameplay_attributes::TrajectoryChoice::HighArcLongDuration, - TrajectoryChoice::LowArcShortDuration=> - strafesnet_common::gameplay_attributes::TrajectoryChoice::LowArcShortDuration, - } - } +impl Into for TrajectoryChoice { + fn into(self) -> strafesnet_common::gameplay_attributes::TrajectoryChoice { + match self { + TrajectoryChoice::HighArcLongDuration => { + strafesnet_common::gameplay_attributes::TrajectoryChoice::HighArcLongDuration + } + TrajectoryChoice::LowArcShortDuration => { + strafesnet_common::gameplay_attributes::TrajectoryChoice::LowArcShortDuration + } + } + } } -impl From for TrajectoryChoice{ - fn from(value:strafesnet_common::gameplay_attributes::TrajectoryChoice)->Self{ - match value{ - strafesnet_common::gameplay_attributes::TrajectoryChoice::HighArcLongDuration=> - TrajectoryChoice::HighArcLongDuration, - strafesnet_common::gameplay_attributes::TrajectoryChoice::LowArcShortDuration=> - TrajectoryChoice::LowArcShortDuration, - } - } +impl From for TrajectoryChoice { + fn from(value: strafesnet_common::gameplay_attributes::TrajectoryChoice) -> Self { + match value { + strafesnet_common::gameplay_attributes::TrajectoryChoice::HighArcLongDuration => { + TrajectoryChoice::HighArcLongDuration + } + strafesnet_common::gameplay_attributes::TrajectoryChoice::LowArcShortDuration => { + TrajectoryChoice::LowArcShortDuration + } + } + } } #[binrw::binrw] #[brw(little)] -pub enum SetTrajectory{ - #[brw(magic=0u8)] - AirTime(Time), - #[brw(magic=1u8)] - Height(Planar64), - #[brw(magic=2u8)] - DotVelocity{direction:Planar64Vec3,dot:Planar64}, - #[brw(magic=3u8)] - TargetPointTime{ - target_point:Planar64Vec3, - time:Time, - }, - #[brw(magic=4u8)] - TargetPointSpeed{ - target_point:Planar64Vec3, - speed:Planar64, - trajectory_choice:TrajectoryChoice, - }, - #[brw(magic=5u8)] - Velocity(Planar64Vec3), +pub enum SetTrajectory { + #[brw(magic = 0u8)] + AirTime(Time), + #[brw(magic = 1u8)] + Height(Planar64), + #[brw(magic = 2u8)] + DotVelocity { + direction: Planar64Vec3, + dot: Planar64, + }, + #[brw(magic = 3u8)] + TargetPointTime { + target_point: Planar64Vec3, + time: Time, + }, + #[brw(magic = 4u8)] + TargetPointSpeed { + target_point: Planar64Vec3, + speed: Planar64, + trajectory_choice: TrajectoryChoice, + }, + #[brw(magic = 5u8)] + Velocity(Planar64Vec3), } -impl Into for SetTrajectory{ - fn into(self)->strafesnet_common::gameplay_attributes::SetTrajectory{ - match self{ - SetTrajectory::AirTime(time)=> - strafesnet_common::gameplay_attributes::SetTrajectory::AirTime( - strafesnet_common::integer::Time::raw(time) - ), - SetTrajectory::Height(height)=> - strafesnet_common::gameplay_attributes::SetTrajectory::Height( - strafesnet_common::integer::Planar64::raw(height) - ), - SetTrajectory::DotVelocity{direction,dot}=> - strafesnet_common::gameplay_attributes::SetTrajectory::DotVelocity{ - direction:strafesnet_common::integer::vec3::raw_array(direction), - dot:strafesnet_common::integer::Planar64::raw(dot), - }, - SetTrajectory::TargetPointTime{target_point,time}=> - strafesnet_common::gameplay_attributes::SetTrajectory::TargetPointTime{ - target_point:strafesnet_common::integer::vec3::raw_array(target_point), - time:strafesnet_common::integer::Time::raw(time), - }, - SetTrajectory::TargetPointSpeed{target_point,speed,trajectory_choice}=> - strafesnet_common::gameplay_attributes::SetTrajectory::TargetPointSpeed{ - target_point:strafesnet_common::integer::vec3::raw_array(target_point), - speed:strafesnet_common::integer::Planar64::raw(speed), - trajectory_choice:trajectory_choice.into(), - }, - SetTrajectory::Velocity(velocity)=> - strafesnet_common::gameplay_attributes::SetTrajectory::Velocity( - strafesnet_common::integer::vec3::raw_array(velocity) - ), - } - } +impl Into for SetTrajectory { + fn into(self) -> strafesnet_common::gameplay_attributes::SetTrajectory { + match self { + SetTrajectory::AirTime(time) => { + strafesnet_common::gameplay_attributes::SetTrajectory::AirTime( + strafesnet_common::integer::Time::raw(time), + ) + } + SetTrajectory::Height(height) => { + strafesnet_common::gameplay_attributes::SetTrajectory::Height( + strafesnet_common::integer::Planar64::raw(height), + ) + } + SetTrajectory::DotVelocity { direction, dot } => { + strafesnet_common::gameplay_attributes::SetTrajectory::DotVelocity { + direction: strafesnet_common::integer::vec3::raw_array(direction), + dot: strafesnet_common::integer::Planar64::raw(dot), + } + } + SetTrajectory::TargetPointTime { target_point, time } => { + strafesnet_common::gameplay_attributes::SetTrajectory::TargetPointTime { + target_point: strafesnet_common::integer::vec3::raw_array(target_point), + time: strafesnet_common::integer::Time::raw(time), + } + } + SetTrajectory::TargetPointSpeed { + target_point, + speed, + trajectory_choice, + } => strafesnet_common::gameplay_attributes::SetTrajectory::TargetPointSpeed { + target_point: strafesnet_common::integer::vec3::raw_array(target_point), + speed: strafesnet_common::integer::Planar64::raw(speed), + trajectory_choice: trajectory_choice.into(), + }, + SetTrajectory::Velocity(velocity) => { + strafesnet_common::gameplay_attributes::SetTrajectory::Velocity( + strafesnet_common::integer::vec3::raw_array(velocity), + ) + } + } + } } -impl From for SetTrajectory{ - fn from(value:strafesnet_common::gameplay_attributes::SetTrajectory)->Self{ - match value{ - strafesnet_common::gameplay_attributes::SetTrajectory::AirTime(time)=> - SetTrajectory::AirTime( - time.get() - ), - strafesnet_common::gameplay_attributes::SetTrajectory::Height(height)=> - SetTrajectory::Height( - height.to_raw() - ), - strafesnet_common::gameplay_attributes::SetTrajectory::DotVelocity{direction,dot}=> - SetTrajectory::DotVelocity{ - direction:direction.map(|t|t.to_raw()).to_array(), - dot:dot.to_raw(), - }, - strafesnet_common::gameplay_attributes::SetTrajectory::TargetPointTime{target_point,time}=> - SetTrajectory::TargetPointTime{ - target_point:target_point.map(|t|t.to_raw()).to_array(), - time:time.get(), - }, - strafesnet_common::gameplay_attributes::SetTrajectory::TargetPointSpeed{target_point,speed,trajectory_choice}=> - SetTrajectory::TargetPointSpeed{ - target_point:target_point.map(|t|t.to_raw()).to_array(), - speed:speed.to_raw(), - trajectory_choice:trajectory_choice.into(), - }, - strafesnet_common::gameplay_attributes::SetTrajectory::Velocity(velocity)=> - SetTrajectory::Velocity( - velocity.map(|t|t.to_raw()).to_array() - ), - } - } +impl From for SetTrajectory { + fn from(value: strafesnet_common::gameplay_attributes::SetTrajectory) -> Self { + match value { + strafesnet_common::gameplay_attributes::SetTrajectory::AirTime(time) => { + SetTrajectory::AirTime(time.get()) + } + strafesnet_common::gameplay_attributes::SetTrajectory::Height(height) => { + SetTrajectory::Height(height.to_raw()) + } + strafesnet_common::gameplay_attributes::SetTrajectory::DotVelocity { + direction, + dot, + } => SetTrajectory::DotVelocity { + direction: direction.map(|t| t.to_raw()).to_array(), + dot: dot.to_raw(), + }, + strafesnet_common::gameplay_attributes::SetTrajectory::TargetPointTime { + target_point, + time, + } => SetTrajectory::TargetPointTime { + target_point: target_point.map(|t| t.to_raw()).to_array(), + time: time.get(), + }, + strafesnet_common::gameplay_attributes::SetTrajectory::TargetPointSpeed { + target_point, + speed, + trajectory_choice, + } => SetTrajectory::TargetPointSpeed { + target_point: target_point.map(|t| t.to_raw()).to_array(), + speed: speed.to_raw(), + trajectory_choice: trajectory_choice.into(), + }, + strafesnet_common::gameplay_attributes::SetTrajectory::Velocity(velocity) => { + SetTrajectory::Velocity(velocity.map(|t| t.to_raw()).to_array()) + } + } + } } #[binrw::binrw] #[brw(little)] -pub struct Wormhole{ - pub destination_model:u32, +pub struct Wormhole { + pub destination_model: u32, } -impl Into for Wormhole{ - fn into(self)->strafesnet_common::gameplay_attributes::Wormhole{ - strafesnet_common::gameplay_attributes::Wormhole{ - destination_model:strafesnet_common::model::ModelId::new(self.destination_model), - } - } +impl Into for Wormhole { + fn into(self) -> strafesnet_common::gameplay_attributes::Wormhole { + strafesnet_common::gameplay_attributes::Wormhole { + destination_model: strafesnet_common::model::ModelId::new(self.destination_model), + } + } } -impl From for Wormhole{ - fn from(value:strafesnet_common::gameplay_attributes::Wormhole)->Self{ - Self{ - destination_model:value.destination_model.get(), - } - } +impl From for Wormhole { + fn from(value: strafesnet_common::gameplay_attributes::Wormhole) -> Self { + Self { + destination_model: value.destination_model.get(), + } + } } #[binrw::binrw] #[brw(little)] -pub struct GeneralAttributes{ - pub header:u8, - #[br(if(header&Self::BOOSTER!=0))] - pub booster:Option, - #[br(if(header&Self::TRAJECTORY!=0))] - pub trajectory:Option, - #[br(if(header&Self::WORMHOLE!=0))] - pub wormhole:Option, - #[br(if(header&Self::ACCELERATOR!=0))] - pub accelerator:Option, +pub struct GeneralAttributes { + pub header: u8, + #[br(if(header&Self::BOOSTER!=0))] + pub booster: Option, + #[br(if(header&Self::TRAJECTORY!=0))] + pub trajectory: Option, + #[br(if(header&Self::WORMHOLE!=0))] + pub wormhole: Option, + #[br(if(header&Self::ACCELERATOR!=0))] + pub accelerator: Option, } -impl GeneralAttributes{ - const BOOSTER:u8=1<<0; - const TRAJECTORY:u8=1<<1; - const WORMHOLE:u8=1<<2; - const ACCELERATOR:u8=1<<3; +impl GeneralAttributes { + const BOOSTER: u8 = 1 << 0; + const TRAJECTORY: u8 = 1 << 1; + const WORMHOLE: u8 = 1 << 2; + const ACCELERATOR: u8 = 1 << 3; } -impl Into for GeneralAttributes{ - fn into(self)->strafesnet_common::gameplay_attributes::GeneralAttributes{ - strafesnet_common::gameplay_attributes::GeneralAttributes{ - booster:self.booster.map(Into::into), - trajectory:self.trajectory.map(Into::into), - wormhole:self.wormhole.map(Into::into), - accelerator:self.accelerator.map(Into::into), - } - } +impl Into for GeneralAttributes { + fn into(self) -> strafesnet_common::gameplay_attributes::GeneralAttributes { + strafesnet_common::gameplay_attributes::GeneralAttributes { + booster: self.booster.map(Into::into), + trajectory: self.trajectory.map(Into::into), + wormhole: self.wormhole.map(Into::into), + accelerator: self.accelerator.map(Into::into), + } + } } -impl From for GeneralAttributes{ - fn from(value:strafesnet_common::gameplay_attributes::GeneralAttributes)->Self{ - let header= - flag(value.booster.is_some(),GeneralAttributes::BOOSTER) - |flag(value.trajectory.is_some(),GeneralAttributes::TRAJECTORY) - |flag(value.wormhole.is_some(),GeneralAttributes::WORMHOLE) - |flag(value.accelerator.is_some(),GeneralAttributes::ACCELERATOR); - Self{ - header, - booster:value.booster.map(Into::into), - trajectory:value.trajectory.map(Into::into), - wormhole:value.wormhole.map(Into::into), - accelerator:value.accelerator.map(Into::into), - } - } +impl From for GeneralAttributes { + fn from(value: strafesnet_common::gameplay_attributes::GeneralAttributes) -> Self { + let header = flag(value.booster.is_some(), GeneralAttributes::BOOSTER) + | flag(value.trajectory.is_some(), GeneralAttributes::TRAJECTORY) + | flag(value.wormhole.is_some(), GeneralAttributes::WORMHOLE) + | flag(value.accelerator.is_some(), GeneralAttributes::ACCELERATOR); + Self { + header, + booster: value.booster.map(Into::into), + trajectory: value.trajectory.map(Into::into), + wormhole: value.wormhole.map(Into::into), + accelerator: value.accelerator.map(Into::into), + } + } } #[binrw::binrw] #[brw(little)] -pub struct ContactingAttributes{ - pub header:u8, - #[br(if(header&Self::CONTACTING_BEHAVIOUR!=0))] - pub contact_behaviour:Option, +pub struct ContactingAttributes { + pub header: u8, + #[br(if(header&Self::CONTACTING_BEHAVIOUR!=0))] + pub contact_behaviour: Option, } -impl ContactingAttributes{ - const CONTACTING_BEHAVIOUR:u8=1<<0; +impl ContactingAttributes { + const CONTACTING_BEHAVIOUR: u8 = 1 << 0; } -impl Into for ContactingAttributes{ - fn into(self)->strafesnet_common::gameplay_attributes::ContactingAttributes{ - strafesnet_common::gameplay_attributes::ContactingAttributes{ - contact_behaviour:self.contact_behaviour.map(Into::into), - } - } +impl Into for ContactingAttributes { + fn into(self) -> strafesnet_common::gameplay_attributes::ContactingAttributes { + strafesnet_common::gameplay_attributes::ContactingAttributes { + contact_behaviour: self.contact_behaviour.map(Into::into), + } + } } -impl From for ContactingAttributes{ - fn from(value:strafesnet_common::gameplay_attributes::ContactingAttributes)->Self{ - Self{ - header:flag(value.contact_behaviour.is_some(),ContactingAttributes::CONTACTING_BEHAVIOUR), - contact_behaviour:value.contact_behaviour.map(Into::into), - } - } +impl From for ContactingAttributes { + fn from(value: strafesnet_common::gameplay_attributes::ContactingAttributes) -> Self { + Self { + header: flag( + value.contact_behaviour.is_some(), + ContactingAttributes::CONTACTING_BEHAVIOUR, + ), + contact_behaviour: value.contact_behaviour.map(Into::into), + } + } } #[binrw::binrw] #[brw(little)] -pub struct IntersectingAttributes{ - pub header:u8, - #[br(if(header&Self::INTERSECTING_WATER!=0))] - pub water:Option, +pub struct IntersectingAttributes { + pub header: u8, + #[br(if(header&Self::INTERSECTING_WATER!=0))] + pub water: Option, } -impl IntersectingAttributes{ - const INTERSECTING_WATER:u8=1<<0; +impl IntersectingAttributes { + const INTERSECTING_WATER: u8 = 1 << 0; } -impl Into for IntersectingAttributes{ - fn into(self)->strafesnet_common::gameplay_attributes::IntersectingAttributes{ - strafesnet_common::gameplay_attributes::IntersectingAttributes{ - water:self.water.map(Into::into), - } - } +impl Into + for IntersectingAttributes +{ + fn into(self) -> strafesnet_common::gameplay_attributes::IntersectingAttributes { + strafesnet_common::gameplay_attributes::IntersectingAttributes { + water: self.water.map(Into::into), + } + } } -impl From for IntersectingAttributes{ - fn from(value:strafesnet_common::gameplay_attributes::IntersectingAttributes)->Self{ - Self{ - header:flag(value.water.is_some(),IntersectingAttributes::INTERSECTING_WATER), - water:value.water.map(Into::into), - } - } +impl From + for IntersectingAttributes +{ + fn from(value: strafesnet_common::gameplay_attributes::IntersectingAttributes) -> Self { + Self { + header: flag( + value.water.is_some(), + IntersectingAttributes::INTERSECTING_WATER, + ), + water: value.water.map(Into::into), + } + } } #[binrw::binrw] #[brw(little)] -pub struct ContactAttributes{ - contacting:ContactingAttributes, - general:GeneralAttributes, +pub struct ContactAttributes { + contacting: ContactingAttributes, + general: GeneralAttributes, } -impl Into for ContactAttributes{ - fn into(self)->strafesnet_common::gameplay_attributes::ContactAttributes{ - strafesnet_common::gameplay_attributes::ContactAttributes{ - contacting:self.contacting.into(), - general:self.general.into(), - } - } +impl Into for ContactAttributes { + fn into(self) -> strafesnet_common::gameplay_attributes::ContactAttributes { + strafesnet_common::gameplay_attributes::ContactAttributes { + contacting: self.contacting.into(), + general: self.general.into(), + } + } } -impl From for ContactAttributes{ - fn from(value:strafesnet_common::gameplay_attributes::ContactAttributes)->Self{ - Self{ - contacting:value.contacting.into(), - general:value.general.into(), - } - } +impl From for ContactAttributes { + fn from(value: strafesnet_common::gameplay_attributes::ContactAttributes) -> Self { + Self { + contacting: value.contacting.into(), + general: value.general.into(), + } + } } #[binrw::binrw] #[brw(little)] -pub struct IntersectAttributes{ - intersecting:IntersectingAttributes, - general:GeneralAttributes, +pub struct IntersectAttributes { + intersecting: IntersectingAttributes, + general: GeneralAttributes, } -impl Into for IntersectAttributes{ - fn into(self)->strafesnet_common::gameplay_attributes::IntersectAttributes{ - strafesnet_common::gameplay_attributes::IntersectAttributes{ - intersecting:self.intersecting.into(), - general:self.general.into(), - } - } +impl Into for IntersectAttributes { + fn into(self) -> strafesnet_common::gameplay_attributes::IntersectAttributes { + strafesnet_common::gameplay_attributes::IntersectAttributes { + intersecting: self.intersecting.into(), + general: self.general.into(), + } + } } -impl From for IntersectAttributes{ - fn from(value:strafesnet_common::gameplay_attributes::IntersectAttributes)->Self{ - Self{ - intersecting:value.intersecting.into(), - general:value.general.into(), - } - } +impl From for IntersectAttributes { + fn from(value: strafesnet_common::gameplay_attributes::IntersectAttributes) -> Self { + Self { + intersecting: value.intersecting.into(), + general: value.general.into(), + } + } } #[binrw::binrw] #[brw(little)] -pub enum CollisionAttributes{ - #[brw(magic=0u8)] - Decoration, - #[brw(magic=1u8)] - Contact(ContactAttributes), - #[brw(magic=2u8)] - Intersect(IntersectAttributes), +pub enum CollisionAttributes { + #[brw(magic = 0u8)] + Decoration, + #[brw(magic = 1u8)] + Contact(ContactAttributes), + #[brw(magic = 2u8)] + Intersect(IntersectAttributes), } -impl Into for CollisionAttributes{ - fn into(self)->strafesnet_common::gameplay_attributes::CollisionAttributes{ - match self{ - CollisionAttributes::Decoration=> - strafesnet_common::gameplay_attributes::CollisionAttributes::Decoration, - CollisionAttributes::Contact(attr)=> - strafesnet_common::gameplay_attributes::CollisionAttributes::Contact(attr.into()), - CollisionAttributes::Intersect(attr)=> - strafesnet_common::gameplay_attributes::CollisionAttributes::Intersect(attr.into()), - } - } +impl Into for CollisionAttributes { + fn into(self) -> strafesnet_common::gameplay_attributes::CollisionAttributes { + match self { + CollisionAttributes::Decoration => { + strafesnet_common::gameplay_attributes::CollisionAttributes::Decoration + } + CollisionAttributes::Contact(attr) => { + strafesnet_common::gameplay_attributes::CollisionAttributes::Contact(attr.into()) + } + CollisionAttributes::Intersect(attr) => { + strafesnet_common::gameplay_attributes::CollisionAttributes::Intersect(attr.into()) + } + } + } } -impl From for CollisionAttributes{ - fn from(value:strafesnet_common::gameplay_attributes::CollisionAttributes)->Self{ - match value{ - strafesnet_common::gameplay_attributes::CollisionAttributes::Decoration=> - CollisionAttributes::Decoration, - strafesnet_common::gameplay_attributes::CollisionAttributes::Contact(attr)=> - CollisionAttributes::Contact(attr.into()), - strafesnet_common::gameplay_attributes::CollisionAttributes::Intersect(attr)=> - CollisionAttributes::Intersect(attr.into()), - } - } +impl From for CollisionAttributes { + fn from(value: strafesnet_common::gameplay_attributes::CollisionAttributes) -> Self { + match value { + strafesnet_common::gameplay_attributes::CollisionAttributes::Decoration => { + CollisionAttributes::Decoration + } + strafesnet_common::gameplay_attributes::CollisionAttributes::Contact(attr) => { + CollisionAttributes::Contact(attr.into()) + } + strafesnet_common::gameplay_attributes::CollisionAttributes::Intersect(attr) => { + CollisionAttributes::Intersect(attr.into()) + } + } + } } diff --git a/lib/snf/src/newtypes/gameplay_modes.rs b/lib/snf/src/newtypes/gameplay_modes.rs index 4363208..3177ae8 100644 --- a/lib/snf/src/newtypes/gameplay_modes.rs +++ b/lib/snf/src/newtypes/gameplay_modes.rs @@ -1,220 +1,241 @@ use super::common::flag; -pub type ModeId=u32; -pub type StageId=u32; +pub type ModeId = u32; +pub type StageId = u32; #[binrw::binrw] #[brw(little)] -pub struct StageElement{ - pub header:u8, - pub stage_id:StageId, - #[br(if(header&Self::JUMP_LIMIT!=0))] - pub jump_limit:Option, +pub struct StageElement { + pub header: u8, + pub stage_id: StageId, + #[br(if(header&Self::JUMP_LIMIT!=0))] + pub jump_limit: Option, } -impl StageElement{ - const BEHAVIOUR:u8=0b00111; - const JUMP_LIMIT:u8=1<<3; - const FORCE:u8=1<<4; - const fn behaviour(&self)->Option{ - match self.header&Self::BEHAVIOUR{ - 0=>Some(strafesnet_common::gameplay_modes::StageElementBehaviour::SpawnAt), - 1=>Some(strafesnet_common::gameplay_modes::StageElementBehaviour::Trigger), - 2=>Some(strafesnet_common::gameplay_modes::StageElementBehaviour::Teleport), - 3=>Some(strafesnet_common::gameplay_modes::StageElementBehaviour::Platform), - 4=>Some(strafesnet_common::gameplay_modes::StageElementBehaviour::Check), - 5=>Some(strafesnet_common::gameplay_modes::StageElementBehaviour::Checkpoint), - _=>None, - } - } - const fn force(&self)->bool{ - self.header&Self::FORCE!=0 - } +impl StageElement { + const BEHAVIOUR: u8 = 0b00111; + const JUMP_LIMIT: u8 = 1 << 3; + const FORCE: u8 = 1 << 4; + const fn behaviour(&self) -> Option { + match self.header & Self::BEHAVIOUR { + 0 => Some(strafesnet_common::gameplay_modes::StageElementBehaviour::SpawnAt), + 1 => Some(strafesnet_common::gameplay_modes::StageElementBehaviour::Trigger), + 2 => Some(strafesnet_common::gameplay_modes::StageElementBehaviour::Teleport), + 3 => Some(strafesnet_common::gameplay_modes::StageElementBehaviour::Platform), + 4 => Some(strafesnet_common::gameplay_modes::StageElementBehaviour::Check), + 5 => Some(strafesnet_common::gameplay_modes::StageElementBehaviour::Checkpoint), + _ => None, + } + } + const fn force(&self) -> bool { + self.header & Self::FORCE != 0 + } } #[derive(Debug)] -pub enum StageElementError{ - InvalidBehaviour, +pub enum StageElementError { + InvalidBehaviour, } -impl std::fmt::Display for StageElementError{ - fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ - write!(f,"{self:?}") - } +impl std::fmt::Display for StageElementError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{self:?}") + } } -impl std::error::Error for StageElementError{} -impl TryInto for StageElement{ - type Error=StageElementError; - fn try_into(self)->Result{ - Ok(strafesnet_common::gameplay_modes::StageElement::new( - strafesnet_common::gameplay_modes::StageId::new(self.stage_id), - self.force(), - self.behaviour().ok_or(StageElementError::InvalidBehaviour)?, - self.jump_limit, - )) - } +impl std::error::Error for StageElementError {} +impl TryInto for StageElement { + type Error = StageElementError; + fn try_into(self) -> Result { + Ok(strafesnet_common::gameplay_modes::StageElement::new( + strafesnet_common::gameplay_modes::StageId::new(self.stage_id), + self.force(), + self.behaviour() + .ok_or(StageElementError::InvalidBehaviour)?, + self.jump_limit, + )) + } } -impl From for StageElement{ - fn from(value:strafesnet_common::gameplay_modes::StageElement)->Self{ - let behaviour=match value.behaviour(){ - strafesnet_common::gameplay_modes::StageElementBehaviour::SpawnAt=>0, - strafesnet_common::gameplay_modes::StageElementBehaviour::Trigger=>1, - strafesnet_common::gameplay_modes::StageElementBehaviour::Teleport=>2, - strafesnet_common::gameplay_modes::StageElementBehaviour::Platform=>3, - strafesnet_common::gameplay_modes::StageElementBehaviour::Check=>4, - strafesnet_common::gameplay_modes::StageElementBehaviour::Checkpoint=>5, - }; - let header= - behaviour - |flag(value.jump_limit().is_some(),StageElement::JUMP_LIMIT) - |flag(value.force(),StageElement::FORCE); - Self{ - header, - stage_id:value.stage_id().get(), - jump_limit:value.jump_limit(), - } - } +impl From for StageElement { + fn from(value: strafesnet_common::gameplay_modes::StageElement) -> Self { + let behaviour = match value.behaviour() { + strafesnet_common::gameplay_modes::StageElementBehaviour::SpawnAt => 0, + strafesnet_common::gameplay_modes::StageElementBehaviour::Trigger => 1, + strafesnet_common::gameplay_modes::StageElementBehaviour::Teleport => 2, + strafesnet_common::gameplay_modes::StageElementBehaviour::Platform => 3, + strafesnet_common::gameplay_modes::StageElementBehaviour::Check => 4, + strafesnet_common::gameplay_modes::StageElementBehaviour::Checkpoint => 5, + }; + let header = behaviour + | flag(value.jump_limit().is_some(), StageElement::JUMP_LIMIT) + | flag(value.force(), StageElement::FORCE); + Self { + header, + stage_id: value.stage_id().get(), + jump_limit: value.jump_limit(), + } + } } #[binrw::binrw] #[brw(little)] -pub struct Stage{ - pub spawn:u32, - //open world support lol - pub ordered_checkpoints_count:u32, - pub unordered_checkpoints_count:u32, - //currently loaded checkpoint models - #[br(count=ordered_checkpoints_count)] - pub ordered_checkpoints:Vec<(u32,u32)>, - #[br(count=unordered_checkpoints_count)] - pub unordered_checkpoints:Vec, +pub struct Stage { + pub spawn: u32, + //open world support lol + pub ordered_checkpoints_count: u32, + pub unordered_checkpoints_count: u32, + //currently loaded checkpoint models + #[br(count=ordered_checkpoints_count)] + pub ordered_checkpoints: Vec<(u32, u32)>, + #[br(count=unordered_checkpoints_count)] + pub unordered_checkpoints: Vec, } -impl Into for Stage{ - fn into(self)->strafesnet_common::gameplay_modes::Stage{ - strafesnet_common::gameplay_modes::Stage::new( - strafesnet_common::model::ModelId::new(self.spawn), - self.ordered_checkpoints_count, - self.unordered_checkpoints_count, - self.ordered_checkpoints.into_iter().map(|(checkpoint_id,model_id)|( - strafesnet_common::gameplay_modes::CheckpointId::new(checkpoint_id), - strafesnet_common::model::ModelId::new(model_id), - )).collect(), - self.unordered_checkpoints.into_iter() - .map(strafesnet_common::model::ModelId::new) - .collect(), - ) - } +impl Into for Stage { + fn into(self) -> strafesnet_common::gameplay_modes::Stage { + strafesnet_common::gameplay_modes::Stage::new( + strafesnet_common::model::ModelId::new(self.spawn), + self.ordered_checkpoints_count, + self.unordered_checkpoints_count, + self.ordered_checkpoints + .into_iter() + .map(|(checkpoint_id, model_id)| { + ( + strafesnet_common::gameplay_modes::CheckpointId::new(checkpoint_id), + strafesnet_common::model::ModelId::new(model_id), + ) + }) + .collect(), + self.unordered_checkpoints + .into_iter() + .map(strafesnet_common::model::ModelId::new) + .collect(), + ) + } } -impl From for Stage{ - fn from(value:strafesnet_common::gameplay_modes::Stage)->Self{ - let spawn=value.spawn().get(); - let ordered_checkpoints_count=value.ordered_checkpoints_count(); - let unordered_checkpoints_count=value.unordered_checkpoints_count(); - let (ordered_checkpoints,unordered_checkpoints)=value.into_inner(); - Self{ - spawn, - ordered_checkpoints_count, - unordered_checkpoints_count, - ordered_checkpoints:ordered_checkpoints.into_iter() - .map(|(checkpoint_id,model_id)|(checkpoint_id.get(),model_id.get())) - .collect(), - unordered_checkpoints:unordered_checkpoints.into_iter() - .map(|model_id|model_id.get()) - .collect(), - } - } +impl From for Stage { + fn from(value: strafesnet_common::gameplay_modes::Stage) -> Self { + let spawn = value.spawn().get(); + let ordered_checkpoints_count = value.ordered_checkpoints_count(); + let unordered_checkpoints_count = value.unordered_checkpoints_count(); + let (ordered_checkpoints, unordered_checkpoints) = value.into_inner(); + Self { + spawn, + ordered_checkpoints_count, + unordered_checkpoints_count, + ordered_checkpoints: ordered_checkpoints + .into_iter() + .map(|(checkpoint_id, model_id)| (checkpoint_id.get(), model_id.get())) + .collect(), + unordered_checkpoints: unordered_checkpoints + .into_iter() + .map(|model_id| model_id.get()) + .collect(), + } + } } #[binrw::binrw] #[brw(little,repr=u8)] -pub enum Zone{ - Start, - Finish, - Anticheat, +pub enum Zone { + Start, + Finish, + Anticheat, } -impl Into for Zone{ - fn into(self)->strafesnet_common::gameplay_modes::Zone{ - match self{ - Zone::Start=>strafesnet_common::gameplay_modes::Zone::Start, - Zone::Finish=>strafesnet_common::gameplay_modes::Zone::Finish, - Zone::Anticheat=>strafesnet_common::gameplay_modes::Zone::Anticheat, - } - } +impl Into for Zone { + fn into(self) -> strafesnet_common::gameplay_modes::Zone { + match self { + Zone::Start => strafesnet_common::gameplay_modes::Zone::Start, + Zone::Finish => strafesnet_common::gameplay_modes::Zone::Finish, + Zone::Anticheat => strafesnet_common::gameplay_modes::Zone::Anticheat, + } + } } -impl From for Zone{ - fn from(value:strafesnet_common::gameplay_modes::Zone)->Self{ - match value{ - strafesnet_common::gameplay_modes::Zone::Start=>Zone::Start, - strafesnet_common::gameplay_modes::Zone::Finish=>Zone::Finish, - strafesnet_common::gameplay_modes::Zone::Anticheat=>Zone::Anticheat, - } - } +impl From for Zone { + fn from(value: strafesnet_common::gameplay_modes::Zone) -> Self { + match value { + strafesnet_common::gameplay_modes::Zone::Start => Zone::Start, + strafesnet_common::gameplay_modes::Zone::Finish => Zone::Finish, + strafesnet_common::gameplay_modes::Zone::Anticheat => Zone::Anticheat, + } + } } #[binrw::binrw] #[brw(little)] -pub struct ModeHeader{ - pub zones:u32, - pub stages:u32, - pub elements:u32, +pub struct ModeHeader { + pub zones: u32, + pub stages: u32, + pub elements: u32, } #[binrw::binrw] #[brw(little)] -pub struct Mode{ - pub header:ModeHeader, - pub style:super::gameplay_style::StyleModifiers, - pub start:u32, - #[br(count=header.zones)] - pub zones:Vec<(u32,Zone)>, - #[br(count=header.stages)] - pub stages:Vec, - #[br(count=header.elements)] - pub elements:Vec<(u32,StageElement)>, +pub struct Mode { + pub header: ModeHeader, + pub style: super::gameplay_style::StyleModifiers, + pub start: u32, + #[br(count=header.zones)] + pub zones: Vec<(u32, Zone)>, + #[br(count=header.stages)] + pub stages: Vec, + #[br(count=header.elements)] + pub elements: Vec<(u32, StageElement)>, } #[derive(Debug)] -pub enum ModeError{ - StyleModifier(super::gameplay_style::StyleModifierError), - StageElement(StageElementError), +pub enum ModeError { + StyleModifier(super::gameplay_style::StyleModifierError), + StageElement(StageElementError), } -impl std::fmt::Display for ModeError{ - fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ - write!(f,"{self:?}") - } +impl std::fmt::Display for ModeError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{self:?}") + } } -impl std::error::Error for ModeError{} -impl TryInto for Mode{ - type Error=ModeError; - fn try_into(self)->Result{ - Ok(strafesnet_common::gameplay_modes::Mode::new( - self.style.try_into().map_err(ModeError::StyleModifier)?, - strafesnet_common::model::ModelId::new(self.start), - self.zones.into_iter().map(|(model_id,zone)| - (strafesnet_common::model::ModelId::new(model_id),zone.into()) - ).collect(), - self.stages.into_iter().map(Into::into).collect(), - self.elements.into_iter().map(|(model_id,stage_element)| - Ok((strafesnet_common::model::ModelId::new(model_id),stage_element.try_into()?)) - ).collect::>().map_err(ModeError::StageElement)?, - )) - } +impl std::error::Error for ModeError {} +impl TryInto for Mode { + type Error = ModeError; + fn try_into(self) -> Result { + Ok(strafesnet_common::gameplay_modes::Mode::new( + self.style.try_into().map_err(ModeError::StyleModifier)?, + strafesnet_common::model::ModelId::new(self.start), + self.zones + .into_iter() + .map(|(model_id, zone)| { + ( + strafesnet_common::model::ModelId::new(model_id), + zone.into(), + ) + }) + .collect(), + self.stages.into_iter().map(Into::into).collect(), + self.elements + .into_iter() + .map(|(model_id, stage_element)| { + Ok(( + strafesnet_common::model::ModelId::new(model_id), + stage_element.try_into()?, + )) + }) + .collect::>() + .map_err(ModeError::StageElement)?, + )) + } } -impl From for Mode{ - fn from(value:strafesnet_common::gameplay_modes::Mode)->Self{ - let (style,start,zones,stages,elements)=value.into_inner(); - Self{ - header:ModeHeader{ - zones:zones.len() as u32, - stages:stages.len() as u32, - elements:elements.len() as u32, - }, - style:style.into(), - start:start.get(), - zones:zones.into_iter() - .map(|(model_id,zone)|(model_id.get(),zone.into())) - .collect(), - stages:stages.into_iter() - .map(Into::into) - .collect(), - elements:elements.into_iter() - .map(|(model_id,stage_element)|(model_id.get(),stage_element.into())) - .collect(), - } - } +impl From for Mode { + fn from(value: strafesnet_common::gameplay_modes::Mode) -> Self { + let (style, start, zones, stages, elements) = value.into_inner(); + Self { + header: ModeHeader { + zones: zones.len() as u32, + stages: stages.len() as u32, + elements: elements.len() as u32, + }, + style: style.into(), + start: start.get(), + zones: zones + .into_iter() + .map(|(model_id, zone)| (model_id.get(), zone.into())) + .collect(), + stages: stages.into_iter().map(Into::into).collect(), + elements: elements + .into_iter() + .map(|(model_id, stage_element)| (model_id.get(), stage_element.into())) + .collect(), + } + } } diff --git a/lib/snf/src/newtypes/gameplay_style.rs b/lib/snf/src/newtypes/gameplay_style.rs index 9033700..276b963 100644 --- a/lib/snf/src/newtypes/gameplay_style.rs +++ b/lib/snf/src/newtypes/gameplay_style.rs @@ -1,442 +1,514 @@ use super::common::flag; -use super::integer::{Time,Ratio64,Planar64,Planar64Vec3}; +use super::integer::{Planar64, Planar64Vec3, Ratio64, Time}; -pub type Controls=u32; +pub type Controls = u32; #[derive(Debug)] -pub enum ControlsError{ - UnknownBits, +pub enum ControlsError { + UnknownBits, } -impl std::fmt::Display for ControlsError{ - fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ - write!(f,"{self:?}") - } +impl std::fmt::Display for ControlsError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{self:?}") + } } -impl std::error::Error for ControlsError{} +impl std::error::Error for ControlsError {} #[binrw::binrw] #[brw(little)] -pub struct StyleModifiers{ - pub header:u8, - pub controls_mask:Controls, - pub controls_mask_state:Controls, - #[br(if(header&Self::STRAFE!=0))] - pub strafe:Option, - #[br(if(header&Self::ROCKET!=0))] - pub rocket:Option, - #[br(if(header&Self::JUMP!=0))] - pub jump:Option, - #[br(if(header&Self::WALK!=0))] - pub walk:Option, - #[br(if(header&Self::LADDER!=0))] - pub ladder:Option, - #[br(if(header&Self::SWIM!=0))] - pub swim:Option, - pub gravity:Planar64Vec3, - pub hitbox:Hitbox, - pub camera_offset:Planar64Vec3, - pub mass:Planar64, +pub struct StyleModifiers { + pub header: u8, + pub controls_mask: Controls, + pub controls_mask_state: Controls, + #[br(if(header&Self::STRAFE!=0))] + pub strafe: Option, + #[br(if(header&Self::ROCKET!=0))] + pub rocket: Option, + #[br(if(header&Self::JUMP!=0))] + pub jump: Option, + #[br(if(header&Self::WALK!=0))] + pub walk: Option, + #[br(if(header&Self::LADDER!=0))] + pub ladder: Option, + #[br(if(header&Self::SWIM!=0))] + pub swim: Option, + pub gravity: Planar64Vec3, + pub hitbox: Hitbox, + pub camera_offset: Planar64Vec3, + pub mass: Planar64, } -impl StyleModifiers{ - const STRAFE:u8=1<<0; - const ROCKET:u8=1<<1; - const JUMP:u8=1<<2; - const WALK:u8=1<<3; - const LADDER:u8=1<<4; - const SWIM:u8=1<<5; +impl StyleModifiers { + const STRAFE: u8 = 1 << 0; + const ROCKET: u8 = 1 << 1; + const JUMP: u8 = 1 << 2; + const WALK: u8 = 1 << 3; + const LADDER: u8 = 1 << 4; + const SWIM: u8 = 1 << 5; } #[derive(Debug)] -pub enum StyleModifierError{ - Controls(ControlsError), - JumpSettings(JumpSettingsError), - StrafeSettings(StrafeSettingsError), +pub enum StyleModifierError { + Controls(ControlsError), + JumpSettings(JumpSettingsError), + StrafeSettings(StrafeSettingsError), } -impl std::fmt::Display for StyleModifierError{ - fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ - write!(f,"{self:?}") - } +impl std::fmt::Display for StyleModifierError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{self:?}") + } } -impl std::error::Error for StyleModifierError{} -impl TryInto for StyleModifiers{ - type Error=StyleModifierError; - fn try_into(self)->Result{ - Ok(strafesnet_common::gameplay_style::StyleModifiers{ - controls_mask:strafesnet_common::controls_bitflag::Controls::from_bits(self.controls_mask).ok_or(StyleModifierError::Controls(ControlsError::UnknownBits))?, - controls_mask_state:strafesnet_common::controls_bitflag::Controls::from_bits(self.controls_mask_state).ok_or(StyleModifierError::Controls(ControlsError::UnknownBits))?, - strafe:self.strafe.map(TryInto::try_into).transpose().map_err(StyleModifierError::StrafeSettings)?, - rocket:self.rocket.map(Into::into), - jump:self.jump.map(TryInto::try_into).transpose().map_err(StyleModifierError::JumpSettings)?, - walk:self.walk.map(Into::into), - ladder:self.ladder.map(Into::into), - swim:self.swim.map(Into::into), - gravity:strafesnet_common::integer::vec3::raw_array(self.gravity), - hitbox:self.hitbox.into(), - camera_offset:strafesnet_common::integer::vec3::raw_array(self.camera_offset), - mass:strafesnet_common::integer::Planar64::raw(self.mass), - }) - } +impl std::error::Error for StyleModifierError {} +impl TryInto for StyleModifiers { + type Error = StyleModifierError; + fn try_into(self) -> Result { + Ok(strafesnet_common::gameplay_style::StyleModifiers { + controls_mask: strafesnet_common::controls_bitflag::Controls::from_bits( + self.controls_mask, + ) + .ok_or(StyleModifierError::Controls(ControlsError::UnknownBits))?, + controls_mask_state: strafesnet_common::controls_bitflag::Controls::from_bits( + self.controls_mask_state, + ) + .ok_or(StyleModifierError::Controls(ControlsError::UnknownBits))?, + strafe: self + .strafe + .map(TryInto::try_into) + .transpose() + .map_err(StyleModifierError::StrafeSettings)?, + rocket: self.rocket.map(Into::into), + jump: self + .jump + .map(TryInto::try_into) + .transpose() + .map_err(StyleModifierError::JumpSettings)?, + walk: self.walk.map(Into::into), + ladder: self.ladder.map(Into::into), + swim: self.swim.map(Into::into), + gravity: strafesnet_common::integer::vec3::raw_array(self.gravity), + hitbox: self.hitbox.into(), + camera_offset: strafesnet_common::integer::vec3::raw_array(self.camera_offset), + mass: strafesnet_common::integer::Planar64::raw(self.mass), + }) + } } -impl From for StyleModifiers{ - fn from(value:strafesnet_common::gameplay_style::StyleModifiers)->Self{ - let header= - flag(value.strafe.is_some(),StyleModifiers::STRAFE) - |flag(value.rocket.is_some(),StyleModifiers::ROCKET) - |flag(value.jump.is_some(),StyleModifiers::JUMP) - |flag(value.walk.is_some(),StyleModifiers::WALK) - |flag(value.ladder.is_some(),StyleModifiers::LADDER) - |flag(value.swim.is_some(),StyleModifiers::SWIM); - Self{ - header, - controls_mask:value.controls_mask.bits(), - controls_mask_state:value.controls_mask_state.bits(), - strafe:value.strafe.map(Into::into), - rocket:value.rocket.map(Into::into), - jump:value.jump.map(Into::into), - walk:value.walk.map(Into::into), - ladder:value.ladder.map(Into::into), - swim:value.swim.map(Into::into), - gravity:value.gravity.map(|t|t.to_raw()).to_array(), - hitbox:value.hitbox.into(), - camera_offset:value.camera_offset.map(|t|t.to_raw()).to_array(), - mass:value.mass.to_raw(), - } - } +impl From for StyleModifiers { + fn from(value: strafesnet_common::gameplay_style::StyleModifiers) -> Self { + let header = flag(value.strafe.is_some(), StyleModifiers::STRAFE) + | flag(value.rocket.is_some(), StyleModifiers::ROCKET) + | flag(value.jump.is_some(), StyleModifiers::JUMP) + | flag(value.walk.is_some(), StyleModifiers::WALK) + | flag(value.ladder.is_some(), StyleModifiers::LADDER) + | flag(value.swim.is_some(), StyleModifiers::SWIM); + Self { + header, + controls_mask: value.controls_mask.bits(), + controls_mask_state: value.controls_mask_state.bits(), + strafe: value.strafe.map(Into::into), + rocket: value.rocket.map(Into::into), + jump: value.jump.map(Into::into), + walk: value.walk.map(Into::into), + ladder: value.ladder.map(Into::into), + swim: value.swim.map(Into::into), + gravity: value.gravity.map(|t| t.to_raw()).to_array(), + hitbox: value.hitbox.into(), + camera_offset: value.camera_offset.map(|t| t.to_raw()).to_array(), + mass: value.mass.to_raw(), + } + } } #[binrw::binrw] #[brw(little,repr=u8)] -pub enum JumpCalculation{ - Max, - BoostThenJump, - JumpThenBoost, +pub enum JumpCalculation { + Max, + BoostThenJump, + JumpThenBoost, } -impl Into for JumpCalculation{ - fn into(self)->strafesnet_common::gameplay_style::JumpCalculation{ - match self{ - JumpCalculation::Max=>strafesnet_common::gameplay_style::JumpCalculation::Max, - JumpCalculation::BoostThenJump=>strafesnet_common::gameplay_style::JumpCalculation::BoostThenJump, - JumpCalculation::JumpThenBoost=>strafesnet_common::gameplay_style::JumpCalculation::JumpThenBoost, - } - } +impl Into for JumpCalculation { + fn into(self) -> strafesnet_common::gameplay_style::JumpCalculation { + match self { + JumpCalculation::Max => strafesnet_common::gameplay_style::JumpCalculation::Max, + JumpCalculation::BoostThenJump => { + strafesnet_common::gameplay_style::JumpCalculation::BoostThenJump + } + JumpCalculation::JumpThenBoost => { + strafesnet_common::gameplay_style::JumpCalculation::JumpThenBoost + } + } + } } -impl From for JumpCalculation{ - fn from(value:strafesnet_common::gameplay_style::JumpCalculation)->Self{ - match value{ - strafesnet_common::gameplay_style::JumpCalculation::Max=>JumpCalculation::Max, - strafesnet_common::gameplay_style::JumpCalculation::BoostThenJump=>JumpCalculation::BoostThenJump, - strafesnet_common::gameplay_style::JumpCalculation::JumpThenBoost=>JumpCalculation::JumpThenBoost, - } - } +impl From for JumpCalculation { + fn from(value: strafesnet_common::gameplay_style::JumpCalculation) -> Self { + match value { + strafesnet_common::gameplay_style::JumpCalculation::Max => JumpCalculation::Max, + strafesnet_common::gameplay_style::JumpCalculation::BoostThenJump => { + JumpCalculation::BoostThenJump + } + strafesnet_common::gameplay_style::JumpCalculation::JumpThenBoost => { + JumpCalculation::JumpThenBoost + } + } + } } -pub enum JumpImpulse{ - Time(Time), - Height(Planar64), - Linear(Planar64), - Energy(Planar64), +pub enum JumpImpulse { + Time(Time), + Height(Planar64), + Linear(Planar64), + Energy(Planar64), } -impl Into for JumpImpulse{ - fn into(self)->strafesnet_common::gameplay_style::JumpImpulse{ - match self{ - JumpImpulse::Time(time)=>strafesnet_common::gameplay_style::JumpImpulse::Time(strafesnet_common::integer::Time::raw(time)), - JumpImpulse::Height(height)=>strafesnet_common::gameplay_style::JumpImpulse::Height(strafesnet_common::integer::Planar64::raw(height)), - JumpImpulse::Linear(deltav)=>strafesnet_common::gameplay_style::JumpImpulse::Linear(strafesnet_common::integer::Planar64::raw(deltav)), - JumpImpulse::Energy(energy)=>strafesnet_common::gameplay_style::JumpImpulse::Energy(strafesnet_common::integer::Planar64::raw(energy)), - } - } +impl Into for JumpImpulse { + fn into(self) -> strafesnet_common::gameplay_style::JumpImpulse { + match self { + JumpImpulse::Time(time) => strafesnet_common::gameplay_style::JumpImpulse::Time( + strafesnet_common::integer::Time::raw(time), + ), + JumpImpulse::Height(height) => strafesnet_common::gameplay_style::JumpImpulse::Height( + strafesnet_common::integer::Planar64::raw(height), + ), + JumpImpulse::Linear(deltav) => strafesnet_common::gameplay_style::JumpImpulse::Linear( + strafesnet_common::integer::Planar64::raw(deltav), + ), + JumpImpulse::Energy(energy) => strafesnet_common::gameplay_style::JumpImpulse::Energy( + strafesnet_common::integer::Planar64::raw(energy), + ), + } + } } -impl From for JumpImpulse{ - fn from(value:strafesnet_common::gameplay_style::JumpImpulse)->Self{ - match value{ - strafesnet_common::gameplay_style::JumpImpulse::Time(time)=>JumpImpulse::Time(time.get()), - strafesnet_common::gameplay_style::JumpImpulse::Height(height)=>JumpImpulse::Height(height.to_raw()), - strafesnet_common::gameplay_style::JumpImpulse::Linear(deltav)=>JumpImpulse::Linear(deltav.to_raw()), - strafesnet_common::gameplay_style::JumpImpulse::Energy(energy)=>JumpImpulse::Energy(energy.to_raw()), - } - } +impl From for JumpImpulse { + fn from(value: strafesnet_common::gameplay_style::JumpImpulse) -> Self { + match value { + strafesnet_common::gameplay_style::JumpImpulse::Time(time) => { + JumpImpulse::Time(time.get()) + } + strafesnet_common::gameplay_style::JumpImpulse::Height(height) => { + JumpImpulse::Height(height.to_raw()) + } + strafesnet_common::gameplay_style::JumpImpulse::Linear(deltav) => { + JumpImpulse::Linear(deltav.to_raw()) + } + strafesnet_common::gameplay_style::JumpImpulse::Energy(energy) => { + JumpImpulse::Energy(energy.to_raw()) + } + } + } } #[binrw::binrw] #[brw(little)] -pub struct ControlsActivation{ - controls_mask:Controls, - controls_intersects:Controls, - controls_contains:Controls, +pub struct ControlsActivation { + controls_mask: Controls, + controls_intersects: Controls, + controls_contains: Controls, } -impl TryInto for ControlsActivation{ - type Error=ControlsError; - fn try_into(self)->Result{ - Ok(strafesnet_common::gameplay_style::ControlsActivation{ - controls_mask:strafesnet_common::controls_bitflag::Controls::from_bits(self.controls_mask).ok_or(ControlsError::UnknownBits)?, - controls_intersects:strafesnet_common::controls_bitflag::Controls::from_bits(self.controls_intersects).ok_or(ControlsError::UnknownBits)?, - controls_contains:strafesnet_common::controls_bitflag::Controls::from_bits(self.controls_contains).ok_or(ControlsError::UnknownBits)?, - }) - } +impl TryInto for ControlsActivation { + type Error = ControlsError; + fn try_into( + self, + ) -> Result { + Ok(strafesnet_common::gameplay_style::ControlsActivation { + controls_mask: strafesnet_common::controls_bitflag::Controls::from_bits( + self.controls_mask, + ) + .ok_or(ControlsError::UnknownBits)?, + controls_intersects: strafesnet_common::controls_bitflag::Controls::from_bits( + self.controls_intersects, + ) + .ok_or(ControlsError::UnknownBits)?, + controls_contains: strafesnet_common::controls_bitflag::Controls::from_bits( + self.controls_contains, + ) + .ok_or(ControlsError::UnknownBits)?, + }) + } } -impl From for ControlsActivation{ - fn from(value:strafesnet_common::gameplay_style::ControlsActivation)->Self{ - Self{ - controls_mask:value.controls_mask.bits(), - controls_intersects:value.controls_intersects.bits(), - controls_contains:value.controls_contains.bits(), - } - } +impl From for ControlsActivation { + fn from(value: strafesnet_common::gameplay_style::ControlsActivation) -> Self { + Self { + controls_mask: value.controls_mask.bits(), + controls_intersects: value.controls_intersects.bits(), + controls_contains: value.controls_contains.bits(), + } + } } #[binrw::binrw] #[brw(little)] -pub struct StrafeSettings{ - header:u8, - enable:ControlsActivation, - mv:Planar64, - #[br(if(header&Self::AIR_ACCEL_LIMIT!=0))] - air_accel_limit:Option, - tick_rate:Ratio64, +pub struct StrafeSettings { + header: u8, + enable: ControlsActivation, + mv: Planar64, + #[br(if(header&Self::AIR_ACCEL_LIMIT!=0))] + air_accel_limit: Option, + tick_rate: Ratio64, } -impl StrafeSettings{ - const AIR_ACCEL_LIMIT:u8=1<<0; +impl StrafeSettings { + const AIR_ACCEL_LIMIT: u8 = 1 << 0; } #[derive(Debug)] -pub enum StrafeSettingsError{ - Ratio(super::integer::RatioError), - Controls(ControlsError), +pub enum StrafeSettingsError { + Ratio(super::integer::RatioError), + Controls(ControlsError), } -impl std::fmt::Display for StrafeSettingsError{ - fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ - write!(f,"{self:?}") - } +impl std::fmt::Display for StrafeSettingsError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{self:?}") + } } -impl std::error::Error for StrafeSettingsError{} -impl TryInto for StrafeSettings{ - type Error=StrafeSettingsError; - fn try_into(self)->Result{ - Ok(strafesnet_common::gameplay_style::StrafeSettings{ - enable:self.enable.try_into().map_err(StrafeSettingsError::Controls)?, - mv:strafesnet_common::integer::Planar64::raw(self.mv), - air_accel_limit:self.air_accel_limit.map(strafesnet_common::integer::Planar64::raw), - tick_rate:self.tick_rate.try_into().map_err(StrafeSettingsError::Ratio)?, - }) - } +impl std::error::Error for StrafeSettingsError {} +impl TryInto for StrafeSettings { + type Error = StrafeSettingsError; + fn try_into(self) -> Result { + Ok(strafesnet_common::gameplay_style::StrafeSettings { + enable: self + .enable + .try_into() + .map_err(StrafeSettingsError::Controls)?, + mv: strafesnet_common::integer::Planar64::raw(self.mv), + air_accel_limit: self + .air_accel_limit + .map(strafesnet_common::integer::Planar64::raw), + tick_rate: self + .tick_rate + .try_into() + .map_err(StrafeSettingsError::Ratio)?, + }) + } } -impl From for StrafeSettings{ - fn from(value:strafesnet_common::gameplay_style::StrafeSettings)->Self{ - let header=flag(value.air_accel_limit.is_some(),StrafeSettings::AIR_ACCEL_LIMIT); - Self{ - header, - enable:value.enable.into(), - mv:value.mv.to_raw(), - air_accel_limit:value.air_accel_limit.map(|a|a.to_raw()), - tick_rate:value.tick_rate.into(), - } - } +impl From for StrafeSettings { + fn from(value: strafesnet_common::gameplay_style::StrafeSettings) -> Self { + let header = flag( + value.air_accel_limit.is_some(), + StrafeSettings::AIR_ACCEL_LIMIT, + ); + Self { + header, + enable: value.enable.into(), + mv: value.mv.to_raw(), + air_accel_limit: value.air_accel_limit.map(|a| a.to_raw()), + tick_rate: value.tick_rate.into(), + } + } } #[binrw::binrw] #[brw(little)] -pub struct PropulsionSettings{ - magnitude:Planar64, +pub struct PropulsionSettings { + magnitude: Planar64, } -impl Into for PropulsionSettings{ - fn into(self)->strafesnet_common::gameplay_style::PropulsionSettings{ - strafesnet_common::gameplay_style::PropulsionSettings{ - magnitude:strafesnet_common::integer::Planar64::raw(self.magnitude) - } - } +impl Into for PropulsionSettings { + fn into(self) -> strafesnet_common::gameplay_style::PropulsionSettings { + strafesnet_common::gameplay_style::PropulsionSettings { + magnitude: strafesnet_common::integer::Planar64::raw(self.magnitude), + } + } } -impl From for PropulsionSettings{ - fn from(value:strafesnet_common::gameplay_style::PropulsionSettings)->Self{ - Self{ - magnitude:value.magnitude.to_raw(), - } - } +impl From for PropulsionSettings { + fn from(value: strafesnet_common::gameplay_style::PropulsionSettings) -> Self { + Self { + magnitude: value.magnitude.to_raw(), + } + } } #[binrw::binrw] #[brw(little)] -pub struct JumpSettings{ - header:u8, - impulse:i64, +pub struct JumpSettings { + header: u8, + impulse: i64, } -impl JumpSettings{ - const IMPULSE:u8=0b00011; - const CALCULATION:u8=0b01100; - const LIMIT_MINIMUM:u8=0b10000; - const fn impulse(&self)->Option{ - match self.header&Self::IMPULSE{ - 0=>Some(strafesnet_common::gameplay_style::JumpImpulse::Time(strafesnet_common::integer::Time::raw(self.impulse))), - 1=>Some(strafesnet_common::gameplay_style::JumpImpulse::Height(strafesnet_common::integer::Planar64::raw(self.impulse))), - 2=>Some(strafesnet_common::gameplay_style::JumpImpulse::Linear(strafesnet_common::integer::Planar64::raw(self.impulse))), - 3=>Some(strafesnet_common::gameplay_style::JumpImpulse::Energy(strafesnet_common::integer::Planar64::raw(self.impulse))), - _=>None, - } - } - const fn calculation(&self)->Option{ - match (self.header&Self::CALCULATION)>>2{ - 0=>Some(strafesnet_common::gameplay_style::JumpCalculation::Max), - 1=>Some(strafesnet_common::gameplay_style::JumpCalculation::JumpThenBoost), - 2=>Some(strafesnet_common::gameplay_style::JumpCalculation::BoostThenJump), - _=>None, - } - } - const fn limit_minimum(&self)->bool{ - self.header&Self::LIMIT_MINIMUM!=0 - } +impl JumpSettings { + const IMPULSE: u8 = 0b00011; + const CALCULATION: u8 = 0b01100; + const LIMIT_MINIMUM: u8 = 0b10000; + const fn impulse(&self) -> Option { + match self.header & Self::IMPULSE { + 0 => Some(strafesnet_common::gameplay_style::JumpImpulse::Time( + strafesnet_common::integer::Time::raw(self.impulse), + )), + 1 => Some(strafesnet_common::gameplay_style::JumpImpulse::Height( + strafesnet_common::integer::Planar64::raw(self.impulse), + )), + 2 => Some(strafesnet_common::gameplay_style::JumpImpulse::Linear( + strafesnet_common::integer::Planar64::raw(self.impulse), + )), + 3 => Some(strafesnet_common::gameplay_style::JumpImpulse::Energy( + strafesnet_common::integer::Planar64::raw(self.impulse), + )), + _ => None, + } + } + const fn calculation(&self) -> Option { + match (self.header & Self::CALCULATION) >> 2 { + 0 => Some(strafesnet_common::gameplay_style::JumpCalculation::Max), + 1 => Some(strafesnet_common::gameplay_style::JumpCalculation::JumpThenBoost), + 2 => Some(strafesnet_common::gameplay_style::JumpCalculation::BoostThenJump), + _ => None, + } + } + const fn limit_minimum(&self) -> bool { + self.header & Self::LIMIT_MINIMUM != 0 + } } #[derive(Debug)] -pub enum JumpSettingsError{ - InvalidImpulseDiscriminant, - InvalidCalculationDiscriminant, +pub enum JumpSettingsError { + InvalidImpulseDiscriminant, + InvalidCalculationDiscriminant, } -impl TryInto for JumpSettings{ - type Error=JumpSettingsError; - fn try_into(self)->Result{ - Ok(strafesnet_common::gameplay_style::JumpSettings{ - impulse:self.impulse().ok_or(JumpSettingsError::InvalidImpulseDiscriminant)?, - calculation:self.calculation().ok_or(JumpSettingsError::InvalidCalculationDiscriminant)?, - limit_minimum:self.limit_minimum(), - }) - } +impl TryInto for JumpSettings { + type Error = JumpSettingsError; + fn try_into(self) -> Result { + Ok(strafesnet_common::gameplay_style::JumpSettings { + impulse: self + .impulse() + .ok_or(JumpSettingsError::InvalidImpulseDiscriminant)?, + calculation: self + .calculation() + .ok_or(JumpSettingsError::InvalidCalculationDiscriminant)?, + limit_minimum: self.limit_minimum(), + }) + } } -impl From for JumpSettings{ - fn from(value:strafesnet_common::gameplay_style::JumpSettings)->Self{ - let (impulse,impulse_header)=match value.impulse{ - strafesnet_common::gameplay_style::JumpImpulse::Time(impulse)=>(impulse.get(),0), - strafesnet_common::gameplay_style::JumpImpulse::Height(impulse)=>(impulse.to_raw(),1), - strafesnet_common::gameplay_style::JumpImpulse::Linear(impulse)=>(impulse.to_raw(),2), - strafesnet_common::gameplay_style::JumpImpulse::Energy(impulse)=>(impulse.to_raw(),3), - }; - let calculation_header=match value.calculation{ - strafesnet_common::gameplay_style::JumpCalculation::Max=>0, - strafesnet_common::gameplay_style::JumpCalculation::JumpThenBoost=>1, - strafesnet_common::gameplay_style::JumpCalculation::BoostThenJump=>2, - }; - let header= - impulse_header - |(calculation_header<<2) - |((value.limit_minimum as u8)<<4); - Self{ - header, - impulse, - } - } +impl From for JumpSettings { + fn from(value: strafesnet_common::gameplay_style::JumpSettings) -> Self { + let (impulse, impulse_header) = match value.impulse { + strafesnet_common::gameplay_style::JumpImpulse::Time(impulse) => (impulse.get(), 0), + strafesnet_common::gameplay_style::JumpImpulse::Height(impulse) => { + (impulse.to_raw(), 1) + } + strafesnet_common::gameplay_style::JumpImpulse::Linear(impulse) => { + (impulse.to_raw(), 2) + } + strafesnet_common::gameplay_style::JumpImpulse::Energy(impulse) => { + (impulse.to_raw(), 3) + } + }; + let calculation_header = match value.calculation { + strafesnet_common::gameplay_style::JumpCalculation::Max => 0, + strafesnet_common::gameplay_style::JumpCalculation::JumpThenBoost => 1, + strafesnet_common::gameplay_style::JumpCalculation::BoostThenJump => 2, + }; + let header = + impulse_header | (calculation_header << 2) | ((value.limit_minimum as u8) << 4); + Self { header, impulse } + } } #[binrw::binrw] #[brw(little)] -pub struct AccelerateSettings{ - accel:Planar64, - topspeed:Planar64, +pub struct AccelerateSettings { + accel: Planar64, + topspeed: Planar64, } -impl Into for AccelerateSettings{ - fn into(self)->strafesnet_common::gameplay_style::AccelerateSettings{ - strafesnet_common::gameplay_style::AccelerateSettings{ - accel:strafesnet_common::integer::Planar64::raw(self.accel), - topspeed:strafesnet_common::integer::Planar64::raw(self.topspeed), - } - } +impl Into for AccelerateSettings { + fn into(self) -> strafesnet_common::gameplay_style::AccelerateSettings { + strafesnet_common::gameplay_style::AccelerateSettings { + accel: strafesnet_common::integer::Planar64::raw(self.accel), + topspeed: strafesnet_common::integer::Planar64::raw(self.topspeed), + } + } } -impl From for AccelerateSettings{ - fn from(value:strafesnet_common::gameplay_style::AccelerateSettings)->Self{ - Self{ - accel:value.accel.to_raw(), - topspeed:value.topspeed.to_raw(), - } - } +impl From for AccelerateSettings { + fn from(value: strafesnet_common::gameplay_style::AccelerateSettings) -> Self { + Self { + accel: value.accel.to_raw(), + topspeed: value.topspeed.to_raw(), + } + } } #[binrw::binrw] #[brw(little)] -pub struct WalkSettings{ - accelerate:AccelerateSettings, - static_friction:Planar64, - kinetic_friction:Planar64, - surf_dot:Planar64, +pub struct WalkSettings { + accelerate: AccelerateSettings, + static_friction: Planar64, + kinetic_friction: Planar64, + surf_dot: Planar64, } -impl Into for WalkSettings{ - fn into(self)->strafesnet_common::gameplay_style::WalkSettings{ - strafesnet_common::gameplay_style::WalkSettings{ - accelerate:self.accelerate.into(), - static_friction:strafesnet_common::integer::Planar64::raw(self.static_friction), - kinetic_friction:strafesnet_common::integer::Planar64::raw(self.kinetic_friction), - surf_dot:strafesnet_common::integer::Planar64::raw(self.surf_dot), - } - } +impl Into for WalkSettings { + fn into(self) -> strafesnet_common::gameplay_style::WalkSettings { + strafesnet_common::gameplay_style::WalkSettings { + accelerate: self.accelerate.into(), + static_friction: strafesnet_common::integer::Planar64::raw(self.static_friction), + kinetic_friction: strafesnet_common::integer::Planar64::raw(self.kinetic_friction), + surf_dot: strafesnet_common::integer::Planar64::raw(self.surf_dot), + } + } } -impl From for WalkSettings{ - fn from(value:strafesnet_common::gameplay_style::WalkSettings)->Self{ - Self{ - accelerate:value.accelerate.into(), - static_friction:value.static_friction.to_raw(), - kinetic_friction:value.kinetic_friction.to_raw(), - surf_dot:value.surf_dot.to_raw(), - } - } +impl From for WalkSettings { + fn from(value: strafesnet_common::gameplay_style::WalkSettings) -> Self { + Self { + accelerate: value.accelerate.into(), + static_friction: value.static_friction.to_raw(), + kinetic_friction: value.kinetic_friction.to_raw(), + surf_dot: value.surf_dot.to_raw(), + } + } } #[binrw::binrw] #[brw(little)] -pub struct LadderSettings{ - accelerate:AccelerateSettings, - dot:Planar64, +pub struct LadderSettings { + accelerate: AccelerateSettings, + dot: Planar64, } -impl Into for LadderSettings{ - fn into(self)->strafesnet_common::gameplay_style::LadderSettings{ - strafesnet_common::gameplay_style::LadderSettings{ - accelerate:self.accelerate.into(), - dot:strafesnet_common::integer::Planar64::raw(self.dot), - } - } +impl Into for LadderSettings { + fn into(self) -> strafesnet_common::gameplay_style::LadderSettings { + strafesnet_common::gameplay_style::LadderSettings { + accelerate: self.accelerate.into(), + dot: strafesnet_common::integer::Planar64::raw(self.dot), + } + } } -impl From for LadderSettings{ - fn from(value:strafesnet_common::gameplay_style::LadderSettings)->Self{ - Self{ - accelerate:value.accelerate.into(), - dot:value.dot.to_raw(), - } - } +impl From for LadderSettings { + fn from(value: strafesnet_common::gameplay_style::LadderSettings) -> Self { + Self { + accelerate: value.accelerate.into(), + dot: value.dot.to_raw(), + } + } } #[binrw::binrw] #[brw(little,repr=u8)] -pub enum HitboxMesh{ - Box, - Cylinder, +pub enum HitboxMesh { + Box, + Cylinder, } -impl Into for HitboxMesh{ - fn into(self)->strafesnet_common::gameplay_style::HitboxMesh{ - match self{ - HitboxMesh::Box=>strafesnet_common::gameplay_style::HitboxMesh::Box, - HitboxMesh::Cylinder=>strafesnet_common::gameplay_style::HitboxMesh::Cylinder, - } - } +impl Into for HitboxMesh { + fn into(self) -> strafesnet_common::gameplay_style::HitboxMesh { + match self { + HitboxMesh::Box => strafesnet_common::gameplay_style::HitboxMesh::Box, + HitboxMesh::Cylinder => strafesnet_common::gameplay_style::HitboxMesh::Cylinder, + } + } } -impl From for HitboxMesh{ - fn from(value:strafesnet_common::gameplay_style::HitboxMesh)->Self{ - match value{ - strafesnet_common::gameplay_style::HitboxMesh::Box=>HitboxMesh::Box, - strafesnet_common::gameplay_style::HitboxMesh::Cylinder=>HitboxMesh::Cylinder, - } - } +impl From for HitboxMesh { + fn from(value: strafesnet_common::gameplay_style::HitboxMesh) -> Self { + match value { + strafesnet_common::gameplay_style::HitboxMesh::Box => HitboxMesh::Box, + strafesnet_common::gameplay_style::HitboxMesh::Cylinder => HitboxMesh::Cylinder, + } + } } #[binrw::binrw] #[brw(little)] -pub struct Hitbox{ - pub halfsize:Planar64Vec3, - pub mesh:HitboxMesh, +pub struct Hitbox { + pub halfsize: Planar64Vec3, + pub mesh: HitboxMesh, } -impl Into for Hitbox{ - fn into(self)->strafesnet_common::gameplay_style::Hitbox{ - strafesnet_common::gameplay_style::Hitbox{ - halfsize:strafesnet_common::integer::vec3::raw_array(self.halfsize), - mesh:self.mesh.into(), - } - } +impl Into for Hitbox { + fn into(self) -> strafesnet_common::gameplay_style::Hitbox { + strafesnet_common::gameplay_style::Hitbox { + halfsize: strafesnet_common::integer::vec3::raw_array(self.halfsize), + mesh: self.mesh.into(), + } + } } -impl From for Hitbox{ - fn from(value:strafesnet_common::gameplay_style::Hitbox)->Self{ - Self{ - halfsize:value.halfsize.map(|t|t.to_raw()).to_array(), - mesh:value.mesh.into(), - } - } +impl From for Hitbox { + fn from(value: strafesnet_common::gameplay_style::Hitbox) -> Self { + Self { + halfsize: value.halfsize.map(|t| t.to_raw()).to_array(), + mesh: value.mesh.into(), + } + } } diff --git a/lib/snf/src/newtypes/integer.rs b/lib/snf/src/newtypes/integer.rs index 0eb7711..9fce71b 100644 --- a/lib/snf/src/newtypes/integer.rs +++ b/lib/snf/src/newtypes/integer.rs @@ -1,46 +1,46 @@ -pub type Time=i64; +pub type Time = i64; #[binrw::binrw] #[brw(little)] -pub struct Ratio64{ - num:i64, - den:u64, +pub struct Ratio64 { + num: i64, + den: u64, } #[derive(Debug)] -pub enum RatioError{ - ZeroDenominator, +pub enum RatioError { + ZeroDenominator, } -impl std::fmt::Display for RatioError{ - fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ - write!(f,"{self:?}") - } +impl std::fmt::Display for RatioError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{self:?}") + } } -impl std::error::Error for RatioError{} -impl TryInto for Ratio64{ - type Error=RatioError; - fn try_into(self)->Result{ - strafesnet_common::integer::Ratio64::new(self.num,self.den) - .ok_or(RatioError::ZeroDenominator) - } +impl std::error::Error for RatioError {} +impl TryInto for Ratio64 { + type Error = RatioError; + fn try_into(self) -> Result { + strafesnet_common::integer::Ratio64::new(self.num, self.den) + .ok_or(RatioError::ZeroDenominator) + } } -impl From for Ratio64{ - fn from(value:strafesnet_common::integer::Ratio64)->Self{ - Self{ - num:value.num(), - den:value.den(), - } - } +impl From for Ratio64 { + fn from(value: strafesnet_common::integer::Ratio64) -> Self { + Self { + num: value.num(), + den: value.den(), + } + } } #[binrw::binrw] #[brw(little)] -pub struct Ratio64Vec2{ - pub x:Ratio64, - pub y:Ratio64, +pub struct Ratio64Vec2 { + pub x: Ratio64, + pub y: Ratio64, } -pub type Angle32=i32; -pub type Planar64=i64; -pub type Planar64Vec3=[i64;3]; -pub type Planar64Mat3=[i64;9]; -pub type Planar64Affine3=[i64;12]; +pub type Angle32 = i32; +pub type Planar64 = i64; +pub type Planar64Vec3 = [i64; 3]; +pub type Planar64Mat3 = [i64; 9]; +pub type Planar64Affine3 = [i64; 12]; diff --git a/lib/snf/src/newtypes/model.rs b/lib/snf/src/newtypes/model.rs index 6d3ce51..ea6cb4d 100644 --- a/lib/snf/src/newtypes/model.rs +++ b/lib/snf/src/newtypes/model.rs @@ -1,276 +1,342 @@ use super::common::flag; use strafesnet_common::model::PolygonIter; -use super::integer::{Planar64Vec3,Planar64Affine3}; +use super::integer::{Planar64Affine3, Planar64Vec3}; -pub type TextureCoordinate=[f32;2]; -pub type Color4=[f32;4]; +pub type TextureCoordinate = [f32; 2]; +pub type Color4 = [f32; 4]; #[binrw::binrw] #[brw(little)] -pub struct IndexedVertex{ - pub pos:u32, - pub tex:u32, - pub normal:u32, - pub color:u32, +pub struct IndexedVertex { + pub pos: u32, + pub tex: u32, + pub normal: u32, + pub color: u32, } -impl From for IndexedVertex{ - fn from(value:strafesnet_common::model::IndexedVertex)->Self{ - Self{ - pos:value.pos.get(), - tex:value.tex.get(), - normal:value.normal.get(), - color:value.color.get(), - } - } +impl From for IndexedVertex { + fn from(value: strafesnet_common::model::IndexedVertex) -> Self { + Self { + pos: value.pos.get(), + tex: value.tex.get(), + normal: value.normal.get(), + color: value.color.get(), + } + } } #[binrw::binrw] #[brw(little)] -pub struct Polygon{ - pub count:u32, - #[br(count=count)] - pub vertices:Vec, +pub struct Polygon { + pub count: u32, + #[br(count=count)] + pub vertices: Vec, } #[binrw::binrw] #[brw(little)] -pub struct PolygonGroup{ - pub count:u32, - #[br(count=count)] - pub polys:Vec, +pub struct PolygonGroup { + pub count: u32, + #[br(count=count)] + pub polys: Vec, } -impl From for PolygonGroup{ - fn from(value:strafesnet_common::model::PolygonGroup)->Self{ - match value{ - strafesnet_common::model::PolygonGroup::PolygonList(polygon_list)=>{ - let polys:Vec=polygon_list.polys().map(|poly|{ - let vertices:Vec=poly.iter().map(|vert|vert.get()).collect(); - Polygon{ - count:vertices.len() as u32, - vertices, - } - }).collect(); - Self{ - count:polys.len() as u32, - polys, - } - } - } - } +impl From for PolygonGroup { + fn from(value: strafesnet_common::model::PolygonGroup) -> Self { + match value { + strafesnet_common::model::PolygonGroup::PolygonList(polygon_list) => { + let polys: Vec = polygon_list + .polys() + .map(|poly| { + let vertices: Vec = poly.iter().map(|vert| vert.get()).collect(); + Polygon { + count: vertices.len() as u32, + vertices, + } + }) + .collect(); + Self { + count: polys.len() as u32, + polys, + } + } + } + } } #[binrw::binrw] #[brw(little)] -pub struct RenderConfig{ - pub header:u8, - #[br(if(header&Self::TEXTURE!=0))] - pub texture:Option, +pub struct RenderConfig { + pub header: u8, + #[br(if(header&Self::TEXTURE!=0))] + pub texture: Option, } -impl RenderConfig{ - const TEXTURE:u8=1<<0; +impl RenderConfig { + const TEXTURE: u8 = 1 << 0; } -impl Into for RenderConfig{ - fn into(self)->strafesnet_common::model::RenderConfig{ - strafesnet_common::model::RenderConfig{ - texture:self.texture.map(strafesnet_common::model::TextureId::new), - } - } +impl Into for RenderConfig { + fn into(self) -> strafesnet_common::model::RenderConfig { + strafesnet_common::model::RenderConfig { + texture: self.texture.map(strafesnet_common::model::TextureId::new), + } + } } -impl From for RenderConfig{ - fn from(value:strafesnet_common::model::RenderConfig)->Self{ - let header=flag(value.texture.is_some(),RenderConfig::TEXTURE); - Self{ - header, - texture:value.texture.map(|texture_id|texture_id.get()), - } - } +impl From for RenderConfig { + fn from(value: strafesnet_common::model::RenderConfig) -> Self { + let header = flag(value.texture.is_some(), RenderConfig::TEXTURE); + Self { + header, + texture: value.texture.map(|texture_id| texture_id.get()), + } + } } #[binrw::binrw] #[brw(little)] -pub struct IndexedGraphicsGroup{ - pub count:u32, - pub render:u32, - #[br(count=count)] - pub groups:Vec, +pub struct IndexedGraphicsGroup { + pub count: u32, + pub render: u32, + #[br(count=count)] + pub groups: Vec, } -impl From for IndexedGraphicsGroup{ - fn from(value:strafesnet_common::model::IndexedGraphicsGroup)->Self{ - Self{ - count:value.groups.len() as u32, - render:value.render.get(), - groups:value.groups.into_iter().map(|group_id|group_id.get()).collect(), - } - } +impl From for IndexedGraphicsGroup { + fn from(value: strafesnet_common::model::IndexedGraphicsGroup) -> Self { + Self { + count: value.groups.len() as u32, + render: value.render.get(), + groups: value + .groups + .into_iter() + .map(|group_id| group_id.get()) + .collect(), + } + } } #[binrw::binrw] #[brw(little)] -pub struct IndexedPhysicsGroup{ - pub count:u32, - #[br(count=count)] - pub groups:Vec, +pub struct IndexedPhysicsGroup { + pub count: u32, + #[br(count=count)] + pub groups: Vec, } -impl From for IndexedPhysicsGroup{ - fn from(value:strafesnet_common::model::IndexedPhysicsGroup)->Self{ - Self{ - count:value.groups.len() as u32, - groups:value.groups.into_iter().map(|group_id|group_id.get()).collect(), - } - } +impl From for IndexedPhysicsGroup { + fn from(value: strafesnet_common::model::IndexedPhysicsGroup) -> Self { + Self { + count: value.groups.len() as u32, + groups: value + .groups + .into_iter() + .map(|group_id| group_id.get()) + .collect(), + } + } } #[binrw::binrw] #[brw(little)] -pub struct MeshHeader{ - pub unique_pos:u32, - pub unique_normal:u32, - pub unique_tex:u32, - pub unique_color:u32, - pub unique_vertices:u32, - pub polygon_groups:u32, - pub graphics_groups:u32, - pub physics_groups:u32, +pub struct MeshHeader { + pub unique_pos: u32, + pub unique_normal: u32, + pub unique_tex: u32, + pub unique_color: u32, + pub unique_vertices: u32, + pub polygon_groups: u32, + pub graphics_groups: u32, + pub physics_groups: u32, } #[binrw::binrw] #[brw(little)] -pub struct Mesh{ - pub header:MeshHeader, - #[br(count=header.unique_pos)] - pub unique_pos:Vec, - #[br(count=header.unique_normal)] - pub unique_normal:Vec, - #[br(count=header.unique_tex)] - pub unique_tex:Vec, - #[br(count=header.unique_color)] - pub unique_color:Vec, - #[br(count=header.unique_vertices)] - pub unique_vertices:Vec, - #[br(count=header.polygon_groups)] - pub polygon_groups:Vec, - #[br(count=header.graphics_groups)] - pub graphics_groups:Vec, - #[br(count=header.physics_groups)] - pub physics_groups:Vec, +pub struct Mesh { + pub header: MeshHeader, + #[br(count=header.unique_pos)] + pub unique_pos: Vec, + #[br(count=header.unique_normal)] + pub unique_normal: Vec, + #[br(count=header.unique_tex)] + pub unique_tex: Vec, + #[br(count=header.unique_color)] + pub unique_color: Vec, + #[br(count=header.unique_vertices)] + pub unique_vertices: Vec, + #[br(count=header.polygon_groups)] + pub polygon_groups: Vec, + #[br(count=header.graphics_groups)] + pub graphics_groups: Vec, + #[br(count=header.physics_groups)] + pub physics_groups: Vec, } -impl Into for Mesh{ - fn into(self)->strafesnet_common::model::Mesh{ - strafesnet_common::model::Mesh{ - unique_pos:self.unique_pos.into_iter().map(strafesnet_common::integer::vec3::raw_array).collect(), - unique_normal:self.unique_normal.into_iter().map(strafesnet_common::integer::vec3::raw_array).collect(), - unique_tex:self.unique_tex.into_iter().map(strafesnet_common::model::TextureCoordinate::from_array).collect(), - unique_color:self.unique_color.into_iter().map(strafesnet_common::model::Color4::from_array).collect(), - unique_vertices:self.unique_vertices.into_iter().map(|vert|strafesnet_common::model::IndexedVertex{ - pos:strafesnet_common::model::PositionId::new(vert.pos), - tex:strafesnet_common::model::TextureCoordinateId::new(vert.tex), - normal:strafesnet_common::model::NormalId::new(vert.normal), - color:strafesnet_common::model::ColorId::new(vert.color), - }).collect(), - polygon_groups:self.polygon_groups.into_iter().map(|group| - strafesnet_common::model::PolygonGroup::PolygonList( - strafesnet_common::model::PolygonList::new( - group.polys.into_iter().map(|vert| - vert.vertices.into_iter().map(strafesnet_common::model::VertexId::new).collect() - ).collect() - ) - ) - ).collect(), - graphics_groups:self.graphics_groups.into_iter().map(|group| - strafesnet_common::model::IndexedGraphicsGroup{ - render:strafesnet_common::model::RenderConfigId::new(group.render), - groups:group.groups.into_iter().map(strafesnet_common::model::PolygonGroupId::new).collect(), - } - ).collect(), - physics_groups:self.physics_groups.into_iter().map(|group| - strafesnet_common::model::IndexedPhysicsGroup{ - groups:group.groups.into_iter().map(strafesnet_common::model::PolygonGroupId::new).collect(), - } - ).collect(), - } - } +impl Into for Mesh { + fn into(self) -> strafesnet_common::model::Mesh { + strafesnet_common::model::Mesh { + unique_pos: self + .unique_pos + .into_iter() + .map(strafesnet_common::integer::vec3::raw_array) + .collect(), + unique_normal: self + .unique_normal + .into_iter() + .map(strafesnet_common::integer::vec3::raw_array) + .collect(), + unique_tex: self + .unique_tex + .into_iter() + .map(strafesnet_common::model::TextureCoordinate::from_array) + .collect(), + unique_color: self + .unique_color + .into_iter() + .map(strafesnet_common::model::Color4::from_array) + .collect(), + unique_vertices: self + .unique_vertices + .into_iter() + .map(|vert| strafesnet_common::model::IndexedVertex { + pos: strafesnet_common::model::PositionId::new(vert.pos), + tex: strafesnet_common::model::TextureCoordinateId::new(vert.tex), + normal: strafesnet_common::model::NormalId::new(vert.normal), + color: strafesnet_common::model::ColorId::new(vert.color), + }) + .collect(), + polygon_groups: self + .polygon_groups + .into_iter() + .map(|group| { + strafesnet_common::model::PolygonGroup::PolygonList( + strafesnet_common::model::PolygonList::new( + group + .polys + .into_iter() + .map(|vert| { + vert.vertices + .into_iter() + .map(strafesnet_common::model::VertexId::new) + .collect() + }) + .collect(), + ), + ) + }) + .collect(), + graphics_groups: self + .graphics_groups + .into_iter() + .map(|group| strafesnet_common::model::IndexedGraphicsGroup { + render: strafesnet_common::model::RenderConfigId::new(group.render), + groups: group + .groups + .into_iter() + .map(strafesnet_common::model::PolygonGroupId::new) + .collect(), + }) + .collect(), + physics_groups: self + .physics_groups + .into_iter() + .map(|group| strafesnet_common::model::IndexedPhysicsGroup { + groups: group + .groups + .into_iter() + .map(strafesnet_common::model::PolygonGroupId::new) + .collect(), + }) + .collect(), + } + } } -impl From for Mesh{ - fn from(value:strafesnet_common::model::Mesh)->Self{ - Self{ - header:MeshHeader{ - unique_pos:value.unique_pos.len() as u32, - unique_normal:value.unique_normal.len() as u32, - unique_tex:value.unique_tex.len() as u32, - unique_color:value.unique_color.len() as u32, - unique_vertices:value.unique_vertices.len() as u32, - polygon_groups:value.polygon_groups.len() as u32, - graphics_groups:value.graphics_groups.len() as u32, - physics_groups:value.physics_groups.len() as u32, - }, - unique_pos:value.unique_pos.into_iter() - .map(|pos|pos.map(|t|t.to_raw()).to_array()) - .collect(), - unique_normal:value.unique_normal.into_iter() - .map(|normal|normal.map(|t|t.to_raw()).to_array()) - .collect(), - unique_tex:value.unique_tex.into_iter() - .map(|tex|tex.to_array()) - .collect(), - unique_color:value.unique_color.into_iter() - .map(|color|color.to_array()) - .collect(), - unique_vertices:value.unique_vertices.into_iter() - .map(Into::into) - .collect(), - polygon_groups:value.polygon_groups.into_iter() - .map(Into::into) - .collect(), - graphics_groups:value.graphics_groups.into_iter() - .map(Into::into) - .collect(), - physics_groups:value.physics_groups.into_iter() - .map(Into::into) - .collect(), - } - } +impl From for Mesh { + fn from(value: strafesnet_common::model::Mesh) -> Self { + Self { + header: MeshHeader { + unique_pos: value.unique_pos.len() as u32, + unique_normal: value.unique_normal.len() as u32, + unique_tex: value.unique_tex.len() as u32, + unique_color: value.unique_color.len() as u32, + unique_vertices: value.unique_vertices.len() as u32, + polygon_groups: value.polygon_groups.len() as u32, + graphics_groups: value.graphics_groups.len() as u32, + physics_groups: value.physics_groups.len() as u32, + }, + unique_pos: value + .unique_pos + .into_iter() + .map(|pos| pos.map(|t| t.to_raw()).to_array()) + .collect(), + unique_normal: value + .unique_normal + .into_iter() + .map(|normal| normal.map(|t| t.to_raw()).to_array()) + .collect(), + unique_tex: value + .unique_tex + .into_iter() + .map(|tex| tex.to_array()) + .collect(), + unique_color: value + .unique_color + .into_iter() + .map(|color| color.to_array()) + .collect(), + unique_vertices: value.unique_vertices.into_iter().map(Into::into).collect(), + polygon_groups: value.polygon_groups.into_iter().map(Into::into).collect(), + graphics_groups: value.graphics_groups.into_iter().map(Into::into).collect(), + physics_groups: value.physics_groups.into_iter().map(Into::into).collect(), + } + } } #[binrw::binrw] #[brw(little)] -pub struct Model{ - pub mesh:u32, - pub attributes:u32, - pub color:Color4, - pub transform:Planar64Affine3, +pub struct Model { + pub mesh: u32, + pub attributes: u32, + pub color: Color4, + pub transform: Planar64Affine3, } -impl Into for Model{ - fn into(self)->strafesnet_common::model::Model{ - let [_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_a,_b]=self.transform; - strafesnet_common::model::Model{ - mesh:strafesnet_common::model::MeshId::new(self.mesh), - attributes:strafesnet_common::gameplay_attributes::CollisionAttributesId::new(self.attributes), - color:strafesnet_common::model::Color4::from_array(self.color), - transform:strafesnet_common::integer::Planar64Affine3::new( - strafesnet_common::integer::Planar64Mat3::from_cols([ - strafesnet_common::integer::vec3::raw_xyz(_0,_1,_2), - strafesnet_common::integer::vec3::raw_xyz(_3,_4,_5), - strafesnet_common::integer::vec3::raw_xyz(_6,_7,_8) - ]), - strafesnet_common::integer::vec3::raw_xyz(_9,_a,_b) - ), - } - } +impl Into for Model { + fn into(self) -> strafesnet_common::model::Model { + let [_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b] = self.transform; + strafesnet_common::model::Model { + mesh: strafesnet_common::model::MeshId::new(self.mesh), + attributes: strafesnet_common::gameplay_attributes::CollisionAttributesId::new( + self.attributes, + ), + color: strafesnet_common::model::Color4::from_array(self.color), + transform: strafesnet_common::integer::Planar64Affine3::new( + strafesnet_common::integer::Planar64Mat3::from_cols([ + strafesnet_common::integer::vec3::raw_xyz(_0, _1, _2), + strafesnet_common::integer::vec3::raw_xyz(_3, _4, _5), + strafesnet_common::integer::vec3::raw_xyz(_6, _7, _8), + ]), + strafesnet_common::integer::vec3::raw_xyz(_9, _a, _b), + ), + } + } } -impl From for Model{ - fn from(value:strafesnet_common::model::Model)->Self{ - let ( - [_0,_1,_2], - [_3,_4,_5], - [_6,_7,_8], - [_9,_a,_b] - )=( - value.transform.matrix3.x_axis.map(|t|t.to_raw()).to_array(), - value.transform.matrix3.y_axis.map(|t|t.to_raw()).to_array(), - value.transform.matrix3.z_axis.map(|t|t.to_raw()).to_array(), - value.transform.translation.map(|t|t.to_raw()).to_array() - ); - Self{ - mesh:value.mesh.get(), - attributes:value.attributes.get(), - color:value.color.to_array(), - transform:[_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_a,_b], - } - } +impl From for Model { + fn from(value: strafesnet_common::model::Model) -> Self { + let ([_0, _1, _2], [_3, _4, _5], [_6, _7, _8], [_9, _a, _b]) = ( + value + .transform + .matrix3 + .x_axis + .map(|t| t.to_raw()) + .to_array(), + value + .transform + .matrix3 + .y_axis + .map(|t| t.to_raw()) + .to_array(), + value + .transform + .matrix3 + .z_axis + .map(|t| t.to_raw()) + .to_array(), + value.transform.translation.map(|t| t.to_raw()).to_array(), + ); + Self { + mesh: value.mesh.get(), + attributes: value.attributes.get(), + color: value.color.to_array(), + transform: [_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b], + } + } } diff --git a/strafe-client/src/body.rs b/strafe-client/src/body.rs index 6f7f769..a48c453 100644 --- a/strafe-client/src/body.rs +++ b/strafe-client/src/body.rs @@ -1,160 +1,182 @@ use strafesnet_common::aabb; -use strafesnet_common::integer::{self,vec3,Time,Planar64,Planar64Vec3}; -#[derive(Clone,Copy,Debug,Hash)] -pub struct Body{ - pub position:Planar64Vec3,//I64 where 2^32 = 1 u - pub velocity:Planar64Vec3,//I64 where 2^32 = 1 u/s - pub acceleration:Planar64Vec3,//I64 where 2^32 = 1 u/s/s - pub time:Time,//nanoseconds x xxxxD! +use strafesnet_common::integer::{self, vec3, Planar64, Planar64Vec3, Time}; +#[derive(Clone, Copy, Debug, Hash)] +pub struct Body { + pub position: Planar64Vec3, //I64 where 2^32 = 1 u + pub velocity: Planar64Vec3, //I64 where 2^32 = 1 u/s + pub acceleration: Planar64Vec3, //I64 where 2^32 = 1 u/s/s + pub time: Time, //nanoseconds x xxxxD! } -impl std::ops::Neg for Body{ - type Output=Self; - fn neg(self)->Self::Output{ - Self{ - position:self.position, - velocity:-self.velocity, - acceleration:self.acceleration, - time:-self.time, - } - } +impl std::ops::Neg for Body { + type Output = Self; + fn neg(self) -> Self::Output { + Self { + position: self.position, + velocity: -self.velocity, + acceleration: self.acceleration, + time: -self.time, + } + } } impl Body - where Time:Copy, +where + Time: Copy, { - pub const ZERO:Self=Self::new(vec3::ZERO,vec3::ZERO,vec3::ZERO,Time::ZERO); - pub const fn new(position:Planar64Vec3,velocity:Planar64Vec3,acceleration:Planar64Vec3,time:Time)->Self{ - Self{ - position, - velocity, - acceleration, - time, - } - } - pub fn extrapolated_position(&self,time:Time)->Planar64Vec3{ - let dt=time-self.time; - self.position - +(self.velocity*dt).map(|elem|elem.divide().fix_1()) - +self.acceleration.map(|elem|(dt*dt*elem/2).divide().fix_1()) - } - pub fn extrapolated_velocity(&self,time:Time)->Planar64Vec3{ - let dt=time-self.time; - self.velocity+(self.acceleration*dt).map(|elem|elem.divide().fix_1()) - } - pub fn advance_time(&mut self,time:Time){ - self.position=self.extrapolated_position(time); - self.velocity=self.extrapolated_velocity(time); - self.time=time; - } - pub fn extrapolated_position_ratio_dt(&self,dt:integer::Ratio)->Planar64Vec3 - where - // Why? - // All of this can be removed with const generics because the type can be specified as - // Ratio,Fixed> - // which is known to implement all the necessary traits - Num:Copy, - Den:Copy+core::ops::Mul, - D1:Copy, - Num:core::ops::Mul, - Planar64:core::ops::Mul, - N1:core::ops::Add, - Num:core::ops::Mul, - Den:core::ops::Mul, - D2:Copy, - Planar64:core::ops::Mul, - N4:integer::Divide, - T1:integer::Fix, - { - // a*dt^2/2 + v*dt + p - // (a*dt/2+v)*dt+p - (self.acceleration.map(|elem|dt*elem/2)+self.velocity).map(|elem|dt.mul_ratio(elem)) - .map(|elem|elem.divide().fix())+self.position - } - pub fn extrapolated_velocity_ratio_dt(&self,dt:integer::Ratio)->Planar64Vec3 - where - Num:Copy, - Den:Copy, - Num:core::ops::Mul, - Planar64:core::ops::Mul, - N1:integer::Divide, - T1:integer::Fix, - { - // a*dt + v - self.acceleration.map(|elem|(dt*elem).divide().fix())+self.velocity - } - pub fn advance_time_ratio_dt(&mut self,dt:crate::model_physics::GigaTime){ - self.position=self.extrapolated_position_ratio_dt(dt); - self.velocity=self.extrapolated_velocity_ratio_dt(dt); - self.time+=dt.into(); - } - pub fn infinity_dir(&self)->Option{ - if self.velocity==vec3::ZERO{ - if self.acceleration==vec3::ZERO{ - None - }else{ - Some(self.acceleration) - } - }else{ - Some(self.velocity) - } - } - pub fn grow_aabb(&self,aabb:&mut aabb::Aabb,t0:Time,t1:Time){ - aabb.grow(self.extrapolated_position(t0)); - aabb.grow(self.extrapolated_position(t1)); - //v+a*t==0 - //goober code - if !self.acceleration.x.is_zero(){ - let t=-self.velocity.x/self.acceleration.x; - if t0.to_ratio().lt_ratio(t)&&t.lt_ratio(t1.to_ratio()){ - aabb.grow(self.extrapolated_position_ratio_dt(t)); - } - } - if !self.acceleration.y.is_zero(){ - let t=-self.velocity.y/self.acceleration.y; - if t0.to_ratio().lt_ratio(t)&&t.lt_ratio(t1.to_ratio()){ - aabb.grow(self.extrapolated_position_ratio_dt(t)); - } - } - if !self.acceleration.z.is_zero(){ - let t=-self.velocity.z/self.acceleration.z; - if t0.to_ratio().lt_ratio(t)&&t.lt_ratio(t1.to_ratio()){ - aabb.grow(self.extrapolated_position_ratio_dt(t)); - } - } - } - + pub const ZERO: Self = Self::new(vec3::ZERO, vec3::ZERO, vec3::ZERO, Time::ZERO); + pub const fn new( + position: Planar64Vec3, + velocity: Planar64Vec3, + acceleration: Planar64Vec3, + time: Time, + ) -> Self { + Self { + position, + velocity, + acceleration, + time, + } + } + pub fn extrapolated_position(&self, time: Time) -> Planar64Vec3 { + let dt = time - self.time; + self.position + + (self.velocity * dt).map(|elem| elem.divide().fix_1()) + + self + .acceleration + .map(|elem| (dt * dt * elem / 2).divide().fix_1()) + } + pub fn extrapolated_velocity(&self, time: Time) -> Planar64Vec3 { + let dt = time - self.time; + self.velocity + (self.acceleration * dt).map(|elem| elem.divide().fix_1()) + } + pub fn advance_time(&mut self, time: Time) { + self.position = self.extrapolated_position(time); + self.velocity = self.extrapolated_velocity(time); + self.time = time; + } + pub fn extrapolated_position_ratio_dt( + &self, + dt: integer::Ratio, + ) -> Planar64Vec3 + where + // Why? + // All of this can be removed with const generics because the type can be specified as + // Ratio,Fixed> + // which is known to implement all the necessary traits + Num: Copy, + Den: Copy + core::ops::Mul, + D1: Copy, + Num: core::ops::Mul, + Planar64: core::ops::Mul, + N1: core::ops::Add, + Num: core::ops::Mul, + Den: core::ops::Mul, + D2: Copy, + Planar64: core::ops::Mul, + N4: integer::Divide, + T1: integer::Fix, + { + // a*dt^2/2 + v*dt + p + // (a*dt/2+v)*dt+p + (self.acceleration.map(|elem| dt * elem / 2) + self.velocity) + .map(|elem| dt.mul_ratio(elem)) + .map(|elem| elem.divide().fix()) + + self.position + } + pub fn extrapolated_velocity_ratio_dt( + &self, + dt: integer::Ratio, + ) -> Planar64Vec3 + where + Num: Copy, + Den: Copy, + Num: core::ops::Mul, + Planar64: core::ops::Mul, + N1: integer::Divide, + T1: integer::Fix, + { + // a*dt + v + self.acceleration.map(|elem| (dt * elem).divide().fix()) + self.velocity + } + pub fn advance_time_ratio_dt(&mut self, dt: crate::model_physics::GigaTime) { + self.position = self.extrapolated_position_ratio_dt(dt); + self.velocity = self.extrapolated_velocity_ratio_dt(dt); + self.time += dt.into(); + } + pub fn infinity_dir(&self) -> Option { + if self.velocity == vec3::ZERO { + if self.acceleration == vec3::ZERO { + None + } else { + Some(self.acceleration) + } + } else { + Some(self.velocity) + } + } + pub fn grow_aabb(&self, aabb: &mut aabb::Aabb, t0: Time, t1: Time) { + aabb.grow(self.extrapolated_position(t0)); + aabb.grow(self.extrapolated_position(t1)); + //v+a*t==0 + //goober code + if !self.acceleration.x.is_zero() { + let t = -self.velocity.x / self.acceleration.x; + if t0.to_ratio().lt_ratio(t) && t.lt_ratio(t1.to_ratio()) { + aabb.grow(self.extrapolated_position_ratio_dt(t)); + } + } + if !self.acceleration.y.is_zero() { + let t = -self.velocity.y / self.acceleration.y; + if t0.to_ratio().lt_ratio(t) && t.lt_ratio(t1.to_ratio()) { + aabb.grow(self.extrapolated_position_ratio_dt(t)); + } + } + if !self.acceleration.z.is_zero() { + let t = -self.velocity.z / self.acceleration.z; + if t0.to_ratio().lt_ratio(t) && t.lt_ratio(t1.to_ratio()) { + aabb.grow(self.extrapolated_position_ratio_dt(t)); + } + } + } } -impl std::fmt::Display for Body{ - fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ - write!(f,"p({}) v({}) a({}) t({})",self.position,self.velocity,self.acceleration,self.time) - } +impl std::fmt::Display for Body { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "p({}) v({}) a({}) t({})", + self.position, self.velocity, self.acceleration, self.time + ) + } } -pub struct VirtualBody<'a,T>{ - body0:&'a Body, - body1:&'a Body, +pub struct VirtualBody<'a, T> { + body0: &'a Body, + body1: &'a Body, } -impl VirtualBody<'_,T> - where Time:Copy, +impl VirtualBody<'_, T> +where + Time: Copy, { - pub const fn relative<'a>(body0:&'a Body,body1:&'a Body)->VirtualBody<'a,T>{ - //(p0,v0,a0,t0) - //(p1,v1,a1,t1) - VirtualBody{ - body0, - body1, - } - } - pub fn extrapolated_position(&self,time:Time)->Planar64Vec3{ - self.body1.extrapolated_position(time)-self.body0.extrapolated_position(time) - } - pub fn extrapolated_velocity(&self,time:Time)->Planar64Vec3{ - self.body1.extrapolated_velocity(time)-self.body0.extrapolated_velocity(time) - } - pub fn acceleration(&self)->Planar64Vec3{ - self.body1.acceleration-self.body0.acceleration - } - pub fn body(&self,time:Time)->Body{ - Body::new(self.extrapolated_position(time),self.extrapolated_velocity(time),self.acceleration(),time) - } + pub const fn relative<'a>(body0: &'a Body, body1: &'a Body) -> VirtualBody<'a, T> { + //(p0,v0,a0,t0) + //(p1,v1,a1,t1) + VirtualBody { body0, body1 } + } + pub fn extrapolated_position(&self, time: Time) -> Planar64Vec3 { + self.body1.extrapolated_position(time) - self.body0.extrapolated_position(time) + } + pub fn extrapolated_velocity(&self, time: Time) -> Planar64Vec3 { + self.body1.extrapolated_velocity(time) - self.body0.extrapolated_velocity(time) + } + pub fn acceleration(&self) -> Planar64Vec3 { + self.body1.acceleration - self.body0.acceleration + } + pub fn body(&self, time: Time) -> Body { + Body::new( + self.extrapolated_position(time), + self.extrapolated_velocity(time), + self.acceleration(), + time, + ) + } } diff --git a/strafe-client/src/compat_worker.rs b/strafe-client/src/compat_worker.rs index aec9367..5f1cc4b 100644 --- a/strafe-client/src/compat_worker.rs +++ b/strafe-client/src/compat_worker.rs @@ -1,21 +1,21 @@ -pub type QNWorker<'a,Task>=CompatNWorker<'a,Task>; -pub type INWorker<'a,Task>=CompatNWorker<'a,Task>; +pub type QNWorker<'a, Task> = CompatNWorker<'a, Task>; +pub type INWorker<'a, Task> = CompatNWorker<'a, Task>; -pub struct CompatNWorker<'a,Task>{ - data:std::marker::PhantomData, - f:Box, +pub struct CompatNWorker<'a, Task> { + data: std::marker::PhantomData, + f: Box, } -impl<'a,Task> CompatNWorker<'a,Task>{ - pub fn new(f:impl FnMut(Task)+Send+'a)->CompatNWorker<'a,Task>{ - Self{ - data:std::marker::PhantomData, - f:Box::new(f), - } - } +impl<'a, Task> CompatNWorker<'a, Task> { + pub fn new(f: impl FnMut(Task) + Send + 'a) -> CompatNWorker<'a, Task> { + Self { + data: std::marker::PhantomData, + f: Box::new(f), + } + } - pub fn send(&mut self,task:Task)->Result<(),()>{ - (self.f)(task); - Ok(()) - } + pub fn send(&mut self, task: Task) -> Result<(), ()> { + (self.f)(task); + Ok(()) + } } diff --git a/strafe-client/src/face_crawler.rs b/strafe-client/src/face_crawler.rs index 0640575..fe2bfcb 100644 --- a/strafe-client/src/face_crawler.rs +++ b/strafe-client/src/face_crawler.rs @@ -1,134 +1,185 @@ -use crate::model_physics::{GigaTime,FEV,MeshQuery,DirectedEdge}; -use strafesnet_common::integer::{Fixed,Ratio,vec3::Vector3}; -use crate::physics::{Time,Body}; +use crate::model_physics::{DirectedEdge, GigaTime, MeshQuery, FEV}; +use crate::physics::{Body, Time}; +use strafesnet_common::integer::{vec3::Vector3, Fixed, Ratio}; -enum Transition{ - Miss, - Next(FEV,GigaTime), - Hit(M::Face,GigaTime), +enum Transition { + Miss, + Next(FEV, GigaTime), + Hit(M::Face, GigaTime), } -pub enum CrawlResult{ - Miss(FEV), - Hit(M::Face,GigaTime), +pub enum CrawlResult { + Miss(FEV), + Hit(M::Face, GigaTime), } -impl,Offset=Fixed<4,128>>> FEV - where - // This is hardcoded for MinkowskiMesh lol - M::Face:Copy, - M::Edge:Copy, - M::Vert:Copy, - F:core::ops::Mul,Output=Fixed<4,128>>, - >>::Output:core::iter::Sum, - ::Offset:core::ops::Sub<>>::Output>, +impl, Offset = Fixed<4, 128>>> FEV +where + // This is hardcoded for MinkowskiMesh lol + M::Face: Copy, + M::Edge: Copy, + M::Vert: Copy, + F: core::ops::Mul, Output = Fixed<4, 128>>, + >>::Output: core::iter::Sum, + ::Offset: core::ops::Sub<>>::Output>, { - fn next_transition(&self,body_time:GigaTime,mesh:&M,body:&Body,mut best_time:GigaTime)->Transition{ - //conflicting derivative means it crosses in the wrong direction. - //if the transition time is equal to an already tested transition, do not replace the current best. - let mut best_transition=Transition::Miss; - match self{ - &FEV::Face(face_id)=>{ - //test own face collision time, ignoring roots with zero or conflicting derivative - //n=face.normal d=face.dot - //n.a t^2+n.v t+n.p-d==0 - let (n,d)=mesh.face_nd(face_id); - //TODO: use higher precision d value? - //use the mesh transform translation instead of baking it into the d value. - for dt in Fixed::<4,128>::zeroes2((n.dot(body.position)-d)*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){ - if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){ - best_time=dt; - best_transition=Transition::Hit(face_id,dt); - break; - } - } - //test each edge collision time, ignoring roots with zero or conflicting derivative - for &directed_edge_id in mesh.face_edges(face_id).iter(){ - let edge_n=mesh.directed_edge_n(directed_edge_id); - let n=n.cross(edge_n); - let verts=mesh.edge_verts(directed_edge_id.as_undirected()); - //WARNING: d is moved out of the *2 block because of adding two vertices! - //WARNING: precision is swept under the rug! - for dt in Fixed::<4,128>::zeroes2(n.dot(body.position*2-(mesh.vert(verts[0])+mesh.vert(verts[1]))).fix_4(),n.dot(body.velocity).fix_4()*2,n.dot(body.acceleration).fix_4()){ - if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){ - best_time=dt; - best_transition=Transition::Next(FEV::Edge(directed_edge_id.as_undirected()),dt); - break; - } - } - } - //if none: - }, - &FEV::Edge(edge_id)=>{ - //test each face collision time, ignoring roots with zero or conflicting derivative - let edge_n=mesh.edge_n(edge_id); - let edge_verts=mesh.edge_verts(edge_id); - let delta_pos=body.position*2-(mesh.vert(edge_verts[0])+mesh.vert(edge_verts[1])); - for (i,&edge_face_id) in mesh.edge_faces(edge_id).iter().enumerate(){ - let face_n=mesh.face_nd(edge_face_id).0; - //edge_n gets parity from the order of edge_faces - let n=face_n.cross(edge_n)*((i as i64)*2-1); - //WARNING yada yada d *2 - for dt in Fixed::<4,128>::zeroes2(n.dot(delta_pos).fix_4(),n.dot(body.velocity).fix_4()*2,n.dot(body.acceleration).fix_4()){ - if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){ - best_time=dt; - best_transition=Transition::Next(FEV::Face(edge_face_id),dt); - break; - } - } - } - //test each vertex collision time, ignoring roots with zero or conflicting derivative - for (i,&vert_id) in edge_verts.iter().enumerate(){ - //vertex normal gets parity from vert index - let n=edge_n*(1-2*(i as i64)); - for dt in Fixed::<2,64>::zeroes2((n.dot(body.position-mesh.vert(vert_id)))*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){ - if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){ - let dt=Ratio::new(dt.num.fix_4(),dt.den.fix_4()); - best_time=dt; - best_transition=Transition::Next(FEV::Vert(vert_id),dt); - break; - } - } - } - //if none: - }, - &FEV::Vert(vert_id)=>{ - //test each edge collision time, ignoring roots with zero or conflicting derivative - for &directed_edge_id in mesh.vert_edges(vert_id).iter(){ - //edge is directed away from vertex, but we want the dot product to turn out negative - let n=-mesh.directed_edge_n(directed_edge_id); - for dt in Fixed::<2,64>::zeroes2((n.dot(body.position-mesh.vert(vert_id)))*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){ - if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){ - let dt=Ratio::new(dt.num.fix_4(),dt.den.fix_4()); - best_time=dt; - best_transition=Transition::Next(FEV::Edge(directed_edge_id.as_undirected()),dt); - break; - } - } - } - //if none: - }, - } - best_transition - } - pub fn crawl(mut self,mesh:&M,relative_body:&Body,start_time:Time,time_limit:Time)->CrawlResult{ - let mut body_time={ - let r=(start_time-relative_body.time).to_ratio(); - Ratio::new(r.num.fix_4(),r.den.fix_4()) - }; - let time_limit={ - let r=(time_limit-relative_body.time).to_ratio(); - Ratio::new(r.num.fix_4(),r.den.fix_4()) - }; - for _ in 0..20{ - match self.next_transition(body_time,mesh,relative_body,time_limit){ - Transition::Miss=>return CrawlResult::Miss(self), - Transition::Next(next_fev,next_time)=>(self,body_time)=(next_fev,next_time), - Transition::Hit(face,time)=>return CrawlResult::Hit(face,time), - } - } - //TODO: fix all bugs - //println!("Too many iterations! Using default behaviour instead of crashing..."); - CrawlResult::Miss(self) - } + fn next_transition( + &self, + body_time: GigaTime, + mesh: &M, + body: &Body, + mut best_time: GigaTime, + ) -> Transition { + //conflicting derivative means it crosses in the wrong direction. + //if the transition time is equal to an already tested transition, do not replace the current best. + let mut best_transition = Transition::Miss; + match self { + &FEV::Face(face_id) => { + //test own face collision time, ignoring roots with zero or conflicting derivative + //n=face.normal d=face.dot + //n.a t^2+n.v t+n.p-d==0 + let (n, d) = mesh.face_nd(face_id); + //TODO: use higher precision d value? + //use the mesh transform translation instead of baking it into the d value. + for dt in Fixed::<4, 128>::zeroes2( + (n.dot(body.position) - d) * 2, + n.dot(body.velocity) * 2, + n.dot(body.acceleration), + ) { + if body_time.le_ratio(dt) + && dt.lt_ratio(best_time) + && n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative() + { + best_time = dt; + best_transition = Transition::Hit(face_id, dt); + break; + } + } + //test each edge collision time, ignoring roots with zero or conflicting derivative + for &directed_edge_id in mesh.face_edges(face_id).iter() { + let edge_n = mesh.directed_edge_n(directed_edge_id); + let n = n.cross(edge_n); + let verts = mesh.edge_verts(directed_edge_id.as_undirected()); + //WARNING: d is moved out of the *2 block because of adding two vertices! + //WARNING: precision is swept under the rug! + for dt in Fixed::<4, 128>::zeroes2( + n.dot(body.position * 2 - (mesh.vert(verts[0]) + mesh.vert(verts[1]))) + .fix_4(), + n.dot(body.velocity).fix_4() * 2, + n.dot(body.acceleration).fix_4(), + ) { + if body_time.le_ratio(dt) + && dt.lt_ratio(best_time) + && n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative() + { + best_time = dt; + best_transition = + Transition::Next(FEV::Edge(directed_edge_id.as_undirected()), dt); + break; + } + } + } + //if none: + } + &FEV::Edge(edge_id) => { + //test each face collision time, ignoring roots with zero or conflicting derivative + let edge_n = mesh.edge_n(edge_id); + let edge_verts = mesh.edge_verts(edge_id); + let delta_pos = + body.position * 2 - (mesh.vert(edge_verts[0]) + mesh.vert(edge_verts[1])); + for (i, &edge_face_id) in mesh.edge_faces(edge_id).iter().enumerate() { + let face_n = mesh.face_nd(edge_face_id).0; + //edge_n gets parity from the order of edge_faces + let n = face_n.cross(edge_n) * ((i as i64) * 2 - 1); + //WARNING yada yada d *2 + for dt in Fixed::<4, 128>::zeroes2( + n.dot(delta_pos).fix_4(), + n.dot(body.velocity).fix_4() * 2, + n.dot(body.acceleration).fix_4(), + ) { + if body_time.le_ratio(dt) + && dt.lt_ratio(best_time) + && n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative() + { + best_time = dt; + best_transition = Transition::Next(FEV::Face(edge_face_id), dt); + break; + } + } + } + //test each vertex collision time, ignoring roots with zero or conflicting derivative + for (i, &vert_id) in edge_verts.iter().enumerate() { + //vertex normal gets parity from vert index + let n = edge_n * (1 - 2 * (i as i64)); + for dt in Fixed::<2, 64>::zeroes2( + (n.dot(body.position - mesh.vert(vert_id))) * 2, + n.dot(body.velocity) * 2, + n.dot(body.acceleration), + ) { + if body_time.le_ratio(dt) + && dt.lt_ratio(best_time) + && n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative() + { + let dt = Ratio::new(dt.num.fix_4(), dt.den.fix_4()); + best_time = dt; + best_transition = Transition::Next(FEV::Vert(vert_id), dt); + break; + } + } + } + //if none: + } + &FEV::Vert(vert_id) => { + //test each edge collision time, ignoring roots with zero or conflicting derivative + for &directed_edge_id in mesh.vert_edges(vert_id).iter() { + //edge is directed away from vertex, but we want the dot product to turn out negative + let n = -mesh.directed_edge_n(directed_edge_id); + for dt in Fixed::<2, 64>::zeroes2( + (n.dot(body.position - mesh.vert(vert_id))) * 2, + n.dot(body.velocity) * 2, + n.dot(body.acceleration), + ) { + if body_time.le_ratio(dt) + && dt.lt_ratio(best_time) + && n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative() + { + let dt = Ratio::new(dt.num.fix_4(), dt.den.fix_4()); + best_time = dt; + best_transition = + Transition::Next(FEV::Edge(directed_edge_id.as_undirected()), dt); + break; + } + } + } + //if none: + } + } + best_transition + } + pub fn crawl( + mut self, + mesh: &M, + relative_body: &Body, + start_time: Time, + time_limit: Time, + ) -> CrawlResult { + let mut body_time = { + let r = (start_time - relative_body.time).to_ratio(); + Ratio::new(r.num.fix_4(), r.den.fix_4()) + }; + let time_limit = { + let r = (time_limit - relative_body.time).to_ratio(); + Ratio::new(r.num.fix_4(), r.den.fix_4()) + }; + for _ in 0..20 { + match self.next_transition(body_time, mesh, relative_body, time_limit) { + Transition::Miss => return CrawlResult::Miss(self), + Transition::Next(next_fev, next_time) => (self, body_time) = (next_fev, next_time), + Transition::Hit(face, time) => return CrawlResult::Hit(face, time), + } + } + //TODO: fix all bugs + //println!("Too many iterations! Using default behaviour instead of crashing..."); + CrawlResult::Miss(self) + } } diff --git a/strafe-client/src/file.rs b/strafe-client/src/file.rs index 3adc397..aee4531 100644 --- a/strafe-client/src/file.rs +++ b/strafe-client/src/file.rs @@ -1,144 +1,170 @@ use std::io::Read; #[derive(Debug)] -pub enum ReadError{ - #[cfg(feature="roblox")] - Roblox(strafesnet_rbx_loader::ReadError), - #[cfg(feature="source")] - Source(strafesnet_bsp_loader::ReadError), - #[cfg(feature="snf")] - StrafesNET(strafesnet_snf::Error), - #[cfg(feature="snf")] - StrafesNETMap(strafesnet_snf::map::Error), - Io(std::io::Error), - UnknownFileFormat, +pub enum ReadError { + #[cfg(feature = "roblox")] + Roblox(strafesnet_rbx_loader::ReadError), + #[cfg(feature = "source")] + Source(strafesnet_bsp_loader::ReadError), + #[cfg(feature = "snf")] + StrafesNET(strafesnet_snf::Error), + #[cfg(feature = "snf")] + StrafesNETMap(strafesnet_snf::map::Error), + Io(std::io::Error), + UnknownFileFormat, } -impl std::fmt::Display for ReadError{ - fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ - write!(f,"{self:?}") - } +impl std::fmt::Display for ReadError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{self:?}") + } } -impl std::error::Error for ReadError{} +impl std::error::Error for ReadError {} -pub enum DataStructure{ - #[cfg(feature="roblox")] - Roblox(strafesnet_rbx_loader::Model), - #[cfg(feature="source")] - Source(strafesnet_bsp_loader::Bsp), - #[cfg(feature="snf")] - StrafesNET(strafesnet_common::map::CompleteMap), +pub enum DataStructure { + #[cfg(feature = "roblox")] + Roblox(strafesnet_rbx_loader::Model), + #[cfg(feature = "source")] + Source(strafesnet_bsp_loader::Bsp), + #[cfg(feature = "snf")] + StrafesNET(strafesnet_common::map::CompleteMap), } -pub fn read(input:R)->Result{ - let mut buf=std::io::BufReader::new(input); - let peek=std::io::BufRead::fill_buf(&mut buf).map_err(ReadError::Io)?; - match &peek[0..4]{ - #[cfg(feature="roblox")] - b"Ok(DataStructure::Roblox(strafesnet_rbx_loader::read(buf).map_err(ReadError::Roblox)?)), - #[cfg(feature="source")] - b"VBSP"=>Ok(DataStructure::Source(strafesnet_bsp_loader::read(buf).map_err(ReadError::Source)?)), - #[cfg(feature="snf")] - b"SNFM"=>Ok(DataStructure::StrafesNET( - strafesnet_snf::read_map(buf).map_err(ReadError::StrafesNET)? - .into_complete_map().map_err(ReadError::StrafesNETMap)? - )), - _=>Err(ReadError::UnknownFileFormat), - } +pub fn read(input: R) -> Result { + let mut buf = std::io::BufReader::new(input); + let peek = std::io::BufRead::fill_buf(&mut buf).map_err(ReadError::Io)?; + match &peek[0..4] { + #[cfg(feature = "roblox")] + b" Ok(DataStructure::Roblox( + strafesnet_rbx_loader::read(buf).map_err(ReadError::Roblox)?, + )), + #[cfg(feature = "source")] + b"VBSP" => Ok(DataStructure::Source( + strafesnet_bsp_loader::read(buf).map_err(ReadError::Source)?, + )), + #[cfg(feature = "snf")] + b"SNFM" => Ok(DataStructure::StrafesNET( + strafesnet_snf::read_map(buf) + .map_err(ReadError::StrafesNET)? + .into_complete_map() + .map_err(ReadError::StrafesNETMap)?, + )), + _ => Err(ReadError::UnknownFileFormat), + } } #[derive(Debug)] -pub enum LoadError{ - ReadError(ReadError), - File(std::io::Error), - Io(std::io::Error), +pub enum LoadError { + ReadError(ReadError), + File(std::io::Error), + Io(std::io::Error), } -impl std::fmt::Display for LoadError{ - fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ - write!(f,"{self:?}") - } +impl std::fmt::Display for LoadError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{self:?}") + } } -impl std::error::Error for LoadError{} +impl std::error::Error for LoadError {} -pub fn load>(path:P)->Result{ - //blocking because it's simpler... - let file=std::fs::File::open(path).map_err(LoadError::File)?; - match read(file).map_err(LoadError::ReadError)?{ - #[cfg(feature="snf")] - DataStructure::StrafesNET(map)=>Ok(map), - #[cfg(feature="roblox")] - DataStructure::Roblox(model)=>{ - let mut place=model.into_place(); - place.run_scripts(); +pub fn load>( + path: P, +) -> Result { + //blocking because it's simpler... + let file = std::fs::File::open(path).map_err(LoadError::File)?; + match read(file).map_err(LoadError::ReadError)? { + #[cfg(feature = "snf")] + DataStructure::StrafesNET(map) => Ok(map), + #[cfg(feature = "roblox")] + DataStructure::Roblox(model) => { + let mut place = model.into_place(); + place.run_scripts(); - let mut loader=strafesnet_deferred_loader::roblox_legacy(); + let mut loader = strafesnet_deferred_loader::roblox_legacy(); - let (texture_loader,mesh_loader)=loader.get_inner_mut(); + let (texture_loader, mesh_loader) = loader.get_inner_mut(); - let map_step1=strafesnet_rbx_loader::convert( - &place, - |name|texture_loader.acquire_render_config_id(name), - |name|mesh_loader.acquire_mesh_id(name), - ); + let map_step1 = strafesnet_rbx_loader::convert( + &place, + |name| texture_loader.acquire_render_config_id(name), + |name| mesh_loader.acquire_mesh_id(name), + ); - let meshpart_meshes=mesh_loader.load_meshes().map_err(LoadError::Io)?; + let meshpart_meshes = mesh_loader.load_meshes().map_err(LoadError::Io)?; - let map_step2=map_step1.add_meshpart_meshes_and_calculate_attributes( - meshpart_meshes.into_iter().map(|(mesh_id,loader_model)| - (mesh_id,strafesnet_rbx_loader::data::RobloxMeshBytes::new(loader_model.get())) - ) - ); + let map_step2 = map_step1.add_meshpart_meshes_and_calculate_attributes( + meshpart_meshes.into_iter().map(|(mesh_id, loader_model)| { + ( + mesh_id, + strafesnet_rbx_loader::data::RobloxMeshBytes::new(loader_model.get()), + ) + }), + ); - let (textures,render_configs)=loader.into_render_configs().map_err(LoadError::Io)?.consume(); + let (textures, render_configs) = loader + .into_render_configs() + .map_err(LoadError::Io)? + .consume(); - let map=map_step2.add_render_configs_and_textures( - render_configs.into_iter(), - textures.into_iter().map(|(texture_id,texture)| - (texture_id,match texture{ - strafesnet_deferred_loader::texture::Texture::ImageDDS(data)=>data, - }) - ) - ); + let map = map_step2.add_render_configs_and_textures( + render_configs.into_iter(), + textures.into_iter().map(|(texture_id, texture)| { + ( + texture_id, + match texture { + strafesnet_deferred_loader::texture::Texture::ImageDDS(data) => data, + }, + ) + }), + ); - Ok(map) - }, - #[cfg(feature="source")] - DataStructure::Source(bsp)=>{ - let mut loader=strafesnet_deferred_loader::source_legacy(); + Ok(map) + } + #[cfg(feature = "source")] + DataStructure::Source(bsp) => { + let mut loader = strafesnet_deferred_loader::source_legacy(); - let (texture_loader,mesh_loader)=loader.get_inner_mut(); + let (texture_loader, mesh_loader) = loader.get_inner_mut(); - let map_step1=strafesnet_bsp_loader::convert( - &bsp, - |name|texture_loader.acquire_render_config_id(name), - |name|mesh_loader.acquire_mesh_id(name), - ); + let map_step1 = strafesnet_bsp_loader::convert( + &bsp, + |name| texture_loader.acquire_render_config_id(name), + |name| mesh_loader.acquire_mesh_id(name), + ); - let prop_meshes=mesh_loader.load_meshes(bsp.as_ref()); + let prop_meshes = mesh_loader.load_meshes(bsp.as_ref()); - let map_step2=map_step1.add_prop_meshes( - //the type conflagulator 9000 - prop_meshes.into_iter().map(|(mesh_id,loader_model)| - (mesh_id,strafesnet_bsp_loader::data::ModelData{ - mdl:strafesnet_bsp_loader::data::MdlData::new(loader_model.mdl.get()), - vtx:strafesnet_bsp_loader::data::VtxData::new(loader_model.vtx.get()), - vvd:strafesnet_bsp_loader::data::VvdData::new(loader_model.vvd.get()), - }) - ), - |name|texture_loader.acquire_render_config_id(name), - ); + let map_step2 = map_step1.add_prop_meshes( + //the type conflagulator 9000 + prop_meshes.into_iter().map(|(mesh_id, loader_model)| { + ( + mesh_id, + strafesnet_bsp_loader::data::ModelData { + mdl: strafesnet_bsp_loader::data::MdlData::new(loader_model.mdl.get()), + vtx: strafesnet_bsp_loader::data::VtxData::new(loader_model.vtx.get()), + vvd: strafesnet_bsp_loader::data::VvdData::new(loader_model.vvd.get()), + }, + ) + }), + |name| texture_loader.acquire_render_config_id(name), + ); - let (textures,render_configs)=loader.into_render_configs().map_err(LoadError::Io)?.consume(); + let (textures, render_configs) = loader + .into_render_configs() + .map_err(LoadError::Io)? + .consume(); - let map=map_step2.add_render_configs_and_textures( - render_configs.into_iter(), - textures.into_iter().map(|(texture_id,texture)| - (texture_id,match texture{ - strafesnet_deferred_loader::texture::Texture::ImageDDS(data)=>data, - }) - ), - ); + let map = map_step2.add_render_configs_and_textures( + render_configs.into_iter(), + textures.into_iter().map(|(texture_id, texture)| { + ( + texture_id, + match texture { + strafesnet_deferred_loader::texture::Texture::ImageDDS(data) => data, + }, + ) + }), + ); - Ok(map) - }, - } + Ok(map) + } + } } diff --git a/strafe-client/src/graphics.rs b/strafe-client/src/graphics.rs index b870ad2..40ce5c3 100644 --- a/strafe-client/src/graphics.rs +++ b/strafe-client/src/graphics.rs @@ -1,777 +1,896 @@ +use crate::model_graphics::{ + self, GraphicsMeshOwnedRenderConfig, GraphicsModelColor4, GraphicsModelOwned, GraphicsVertex, + IndexedGraphicsMeshOwnedRenderConfig, IndexedGraphicsMeshOwnedRenderConfigId, +}; use std::borrow::Cow; -use std::collections::{HashSet,HashMap}; -use strafesnet_common::map; +use std::collections::{HashMap, HashSet}; use strafesnet_common::integer; -use strafesnet_common::model::{self, ColorId, NormalId, PolygonIter, PositionId, RenderConfigId, TextureCoordinateId, VertexId}; -use wgpu::{util::DeviceExt,AstcBlock,AstcChannel}; -use crate::model_graphics::{self,IndexedGraphicsMeshOwnedRenderConfig,IndexedGraphicsMeshOwnedRenderConfigId,GraphicsMeshOwnedRenderConfig,GraphicsModelColor4,GraphicsModelOwned,GraphicsVertex}; +use strafesnet_common::map; +use strafesnet_common::model::{ + self, ColorId, NormalId, PolygonIter, PositionId, RenderConfigId, TextureCoordinateId, VertexId, +}; +use wgpu::{util::DeviceExt, AstcBlock, AstcChannel}; -struct Indices{ - count:u32, - buf:wgpu::Buffer, - format:wgpu::IndexFormat, +struct Indices { + count: u32, + buf: wgpu::Buffer, + format: wgpu::IndexFormat, } -impl Indices{ - fn new(device:&wgpu::Device,indices:&Vec,format:wgpu::IndexFormat)->Self{ - Self{ - buf:device.create_buffer_init(&wgpu::util::BufferInitDescriptor{ - label:Some("Index"), - contents:bytemuck::cast_slice(indices), - usage:wgpu::BufferUsages::INDEX, - }), - count:indices.len() as u32, - format, - } - } +impl Indices { + fn new( + device: &wgpu::Device, + indices: &Vec, + format: wgpu::IndexFormat, + ) -> Self { + Self { + buf: device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Index"), + contents: bytemuck::cast_slice(indices), + usage: wgpu::BufferUsages::INDEX, + }), + count: indices.len() as u32, + format, + } + } } -struct GraphicsModel{ - indices:Indices, - vertex_buf:wgpu::Buffer, - bind_group:wgpu::BindGroup, - instance_count:u32, +struct GraphicsModel { + indices: Indices, + vertex_buf: wgpu::Buffer, + bind_group: wgpu::BindGroup, + instance_count: u32, } -struct GraphicsSamplers{ - repeat:wgpu::Sampler, +struct GraphicsSamplers { + repeat: wgpu::Sampler, } -struct GraphicsBindGroupLayouts{ - model:wgpu::BindGroupLayout, +struct GraphicsBindGroupLayouts { + model: wgpu::BindGroupLayout, } -struct GraphicsBindGroups{ - camera:wgpu::BindGroup, - skybox_texture:wgpu::BindGroup, +struct GraphicsBindGroups { + camera: wgpu::BindGroup, + skybox_texture: wgpu::BindGroup, } -struct GraphicsPipelines{ - skybox:wgpu::RenderPipeline, - model:wgpu::RenderPipeline, +struct GraphicsPipelines { + skybox: wgpu::RenderPipeline, + model: wgpu::RenderPipeline, } -struct GraphicsCamera{ - screen_size:glam::UVec2, - fov:glam::Vec2,//slope - //camera angles and such are extrapolated and passed in every time +struct GraphicsCamera { + screen_size: glam::UVec2, + fov: glam::Vec2, //slope + //camera angles and such are extrapolated and passed in every time } #[inline] -fn perspective_rh(fov_x_slope:f32,fov_y_slope:f32,z_near:f32,z_far:f32)->glam::Mat4{ - //glam_assert!(z_near > 0.0 && z_far > 0.0); - let r=z_far/(z_near-z_far); - glam::Mat4::from_cols( - glam::Vec4::new(1.0/fov_x_slope,0.0,0.0,0.0), - glam::Vec4::new(0.0,1.0/fov_y_slope,0.0,0.0), - glam::Vec4::new(0.0,0.0,r,-1.0), - glam::Vec4::new(0.0,0.0,r*z_near,0.0), - ) +fn perspective_rh(fov_x_slope: f32, fov_y_slope: f32, z_near: f32, z_far: f32) -> glam::Mat4 { + //glam_assert!(z_near > 0.0 && z_far > 0.0); + let r = z_far / (z_near - z_far); + glam::Mat4::from_cols( + glam::Vec4::new(1.0 / fov_x_slope, 0.0, 0.0, 0.0), + glam::Vec4::new(0.0, 1.0 / fov_y_slope, 0.0, 0.0), + glam::Vec4::new(0.0, 0.0, r, -1.0), + glam::Vec4::new(0.0, 0.0, r * z_near, 0.0), + ) } -impl GraphicsCamera{ - pub fn proj(&self)->glam::Mat4{ - perspective_rh(self.fov.x,self.fov.y,0.4,4000.0) - } - pub fn world(&self,pos:glam::Vec3,angles:glam::Vec2)->glam::Mat4{ - //f32 good enough for view matrix - glam::Mat4::from_translation(pos)*glam::Mat4::from_euler(glam::EulerRot::YXZ,angles.x,angles.y,0f32) - } +impl GraphicsCamera { + pub fn proj(&self) -> glam::Mat4 { + perspective_rh(self.fov.x, self.fov.y, 0.4, 4000.0) + } + pub fn world(&self, pos: glam::Vec3, angles: glam::Vec2) -> glam::Mat4 { + //f32 good enough for view matrix + glam::Mat4::from_translation(pos) + * glam::Mat4::from_euler(glam::EulerRot::YXZ, angles.x, angles.y, 0f32) + } - pub fn to_uniform_data(&self,pos:glam::Vec3,angles:glam::Vec2)->[f32;16*4]{ - let proj=self.proj(); - let proj_inv=proj.inverse(); - let view_inv=self.world(pos,angles); - let view=view_inv.inverse(); + pub fn to_uniform_data(&self, pos: glam::Vec3, angles: glam::Vec2) -> [f32; 16 * 4] { + let proj = self.proj(); + let proj_inv = proj.inverse(); + let view_inv = self.world(pos, angles); + let view = view_inv.inverse(); - let mut raw=[0f32; 16 * 4]; - raw[..16].copy_from_slice(&AsRef::<[f32; 16]>::as_ref(&proj)[..]); - raw[16..32].copy_from_slice(&AsRef::<[f32; 16]>::as_ref(&proj_inv)[..]); - raw[32..48].copy_from_slice(&AsRef::<[f32; 16]>::as_ref(&view)[..]); - raw[48..64].copy_from_slice(&AsRef::<[f32; 16]>::as_ref(&view_inv)[..]); - raw - } + let mut raw = [0f32; 16 * 4]; + raw[..16].copy_from_slice(&AsRef::<[f32; 16]>::as_ref(&proj)[..]); + raw[16..32].copy_from_slice(&AsRef::<[f32; 16]>::as_ref(&proj_inv)[..]); + raw[32..48].copy_from_slice(&AsRef::<[f32; 16]>::as_ref(&view)[..]); + raw[48..64].copy_from_slice(&AsRef::<[f32; 16]>::as_ref(&view_inv)[..]); + raw + } } -impl std::default::Default for GraphicsCamera{ - fn default()->Self{ - Self{ - screen_size:glam::UVec2::ONE, - fov:glam::Vec2::ONE, - } - } +impl std::default::Default for GraphicsCamera { + fn default() -> Self { + Self { + screen_size: glam::UVec2::ONE, + fov: glam::Vec2::ONE, + } + } } -pub struct GraphicsState{ - pipelines:GraphicsPipelines, - bind_groups:GraphicsBindGroups, - bind_group_layouts:GraphicsBindGroupLayouts, - samplers:GraphicsSamplers, - camera:GraphicsCamera, - camera_buf:wgpu::Buffer, - temp_squid_texture_view:wgpu::TextureView, - models:Vec, - depth_view:wgpu::TextureView, - staging_belt:wgpu::util::StagingBelt, +pub struct GraphicsState { + pipelines: GraphicsPipelines, + bind_groups: GraphicsBindGroups, + bind_group_layouts: GraphicsBindGroupLayouts, + samplers: GraphicsSamplers, + camera: GraphicsCamera, + camera_buf: wgpu::Buffer, + temp_squid_texture_view: wgpu::TextureView, + models: Vec, + depth_view: wgpu::TextureView, + staging_belt: wgpu::util::StagingBelt, } -impl GraphicsState{ - const DEPTH_FORMAT:wgpu::TextureFormat=wgpu::TextureFormat::Depth24Plus; - fn create_depth_texture( - config:&wgpu::SurfaceConfiguration, - device:&wgpu::Device, - )->wgpu::TextureView{ - let depth_texture=device.create_texture(&wgpu::TextureDescriptor{ - size:wgpu::Extent3d{ - width:config.width, - height:config.height, - depth_or_array_layers:1, - }, - mip_level_count:1, - sample_count:1, - dimension:wgpu::TextureDimension::D2, - format:Self::DEPTH_FORMAT, - usage:wgpu::TextureUsages::RENDER_ATTACHMENT, - label:None, - view_formats:&[], - }); +impl GraphicsState { + const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth24Plus; + fn create_depth_texture( + config: &wgpu::SurfaceConfiguration, + device: &wgpu::Device, + ) -> wgpu::TextureView { + let depth_texture = device.create_texture(&wgpu::TextureDescriptor { + size: wgpu::Extent3d { + width: config.width, + height: config.height, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: Self::DEPTH_FORMAT, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + label: None, + view_formats: &[], + }); - depth_texture.create_view(&wgpu::TextureViewDescriptor::default()) - } - pub fn clear(&mut self){ - self.models.clear(); - } - pub fn load_user_settings(&mut self,user_settings:&crate::settings::UserSettings){ - self.camera.fov=user_settings.calculate_fov(1.0,&self.camera.screen_size).as_vec2(); - } - pub fn generate_models(&mut self,device:&wgpu::Device,queue:&wgpu::Queue,map:&map::CompleteMap){ - //generate texture view per texture - let texture_views:HashMap=map.textures.iter().enumerate().filter_map(|(texture_id,texture_data)|{ - let texture_id=model::TextureId::new(texture_id as u32); - let image=match ddsfile::Dds::read(std::io::Cursor::new(texture_data)){ - Ok(image)=>image, - Err(e)=>{ - println!("Error loading texture: {e}"); - return None; - }, - }; + depth_texture.create_view(&wgpu::TextureViewDescriptor::default()) + } + pub fn clear(&mut self) { + self.models.clear(); + } + pub fn load_user_settings(&mut self, user_settings: &crate::settings::UserSettings) { + self.camera.fov = user_settings + .calculate_fov(1.0, &self.camera.screen_size) + .as_vec2(); + } + pub fn generate_models( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + map: &map::CompleteMap, + ) { + //generate texture view per texture + let texture_views: HashMap = map + .textures + .iter() + .enumerate() + .filter_map(|(texture_id, texture_data)| { + let texture_id = model::TextureId::new(texture_id as u32); + let image = match ddsfile::Dds::read(std::io::Cursor::new(texture_data)) { + Ok(image) => image, + Err(e) => { + println!("Error loading texture: {e}"); + return None; + } + }; - let (mut width,mut height)=(image.get_width(),image.get_height()); + let (mut width, mut height) = (image.get_width(), image.get_height()); - let format=match image.header10.unwrap().dxgi_format{ - ddsfile::DxgiFormat::R8G8B8A8_UNorm_sRGB=>wgpu::TextureFormat::Rgba8UnormSrgb, - ddsfile::DxgiFormat::BC7_UNorm_sRGB =>{ - //floor(w,4),should be ceil(w,4) - width=width/4*4; - height=height/4*4; - wgpu::TextureFormat::Bc7RgbaUnormSrgb - }, - other=>{ - println!("unsupported texture format{:?}",other); - return None; - }, - }; + let format = match image.header10.unwrap().dxgi_format { + ddsfile::DxgiFormat::R8G8B8A8_UNorm_sRGB => wgpu::TextureFormat::Rgba8UnormSrgb, + ddsfile::DxgiFormat::BC7_UNorm_sRGB => { + //floor(w,4),should be ceil(w,4) + width = width / 4 * 4; + height = height / 4 * 4; + wgpu::TextureFormat::Bc7RgbaUnormSrgb + } + other => { + println!("unsupported texture format{:?}", other); + return None; + } + }; - let size=wgpu::Extent3d{ - width, - height, - depth_or_array_layers:1, - }; + let size = wgpu::Extent3d { + width, + height, + depth_or_array_layers: 1, + }; - let layer_size=wgpu::Extent3d{ - depth_or_array_layers:1, - ..size - }; - let max_mips=layer_size.max_mips(wgpu::TextureDimension::D2); + let layer_size = wgpu::Extent3d { + depth_or_array_layers: 1, + ..size + }; + let max_mips = layer_size.max_mips(wgpu::TextureDimension::D2); - let texture=device.create_texture_with_data( - queue, - &wgpu::TextureDescriptor{ - size, - mip_level_count:max_mips, - sample_count:1, - dimension:wgpu::TextureDimension::D2, - format, - usage:wgpu::TextureUsages::TEXTURE_BINDING|wgpu::TextureUsages::COPY_DST, - label:Some(format!("Texture{}",texture_id.get()).as_str()), - view_formats:&[], - }, - wgpu::util::TextureDataOrder::LayerMajor, - &image.data, - ); - Some((texture_id,texture.create_view(&wgpu::TextureViewDescriptor{ - label:Some(format!("Texture{} View",texture_id.get()).as_str()), - dimension:Some(wgpu::TextureViewDimension::D2), - ..wgpu::TextureViewDescriptor::default() - }))) - }).collect(); - let num_textures=texture_views.len(); + let texture = device.create_texture_with_data( + queue, + &wgpu::TextureDescriptor { + size, + mip_level_count: max_mips, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format, + usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + label: Some(format!("Texture{}", texture_id.get()).as_str()), + view_formats: &[], + }, + wgpu::util::TextureDataOrder::LayerMajor, + &image.data, + ); + Some(( + texture_id, + texture.create_view(&wgpu::TextureViewDescriptor { + label: Some(format!("Texture{} View", texture_id.get()).as_str()), + dimension: Some(wgpu::TextureViewDimension::D2), + ..wgpu::TextureViewDescriptor::default() + }), + )) + }) + .collect(); + let num_textures = texture_views.len(); - //split groups with different textures into separate models - //the models received here are supposed to be tightly packed,i.e. no code needs to check if two models are using the same groups. - let indexed_models_len=map.models.len(); - //models split into graphics_group.RenderConfigId - let mut owned_mesh_id_from_mesh_id_render_config_id:HashMap>=HashMap::new(); - let mut unique_render_config_models:Vec=Vec::with_capacity(indexed_models_len); - for model in &map.models{ - //wow - let instance=GraphicsModelOwned{ - transform:model.transform.into(), - normal_transform:glam::Mat3::from_cols_array_2d(&model.transform.matrix3.to_array().map(|row|row.map(Into::into))).inverse().transpose(), - color:GraphicsModelColor4::new(model.color), - }; - //get or create owned mesh map - let owned_mesh_map=owned_mesh_id_from_mesh_id_render_config_id - .entry(model.mesh).or_insert_with(||{ - let mut owned_mesh_map=HashMap::new(); - //add mesh if renderid never before seen for this model - //add instance - //convert Model into GraphicsModelOwned - //check each group, if it's using a new render config then make a new clone of the model - if let Some(mesh)=map.meshes.get(model.mesh.get() as usize){ - for graphics_group in mesh.graphics_groups.iter(){ - //get or create owned mesh - let owned_mesh_id=owned_mesh_map - .entry(graphics_group.render).or_insert_with(||{ - //create - let owned_mesh_id=IndexedGraphicsMeshOwnedRenderConfigId::new(unique_render_config_models.len() as u32); - unique_render_config_models.push(IndexedGraphicsMeshOwnedRenderConfig{ - unique_pos:mesh.unique_pos.iter().map(|v|v.to_array().map(Into::into)).collect(), - unique_tex:mesh.unique_tex.iter().map(|v|*v.as_ref()).collect(), - unique_normal:mesh.unique_normal.iter().map(|v|v.to_array().map(Into::into)).collect(), - unique_color:mesh.unique_color.iter().map(|v|*v.as_ref()).collect(), - unique_vertices:mesh.unique_vertices.clone(), - render_config:graphics_group.render, - polys:model::PolygonGroup::PolygonList(model::PolygonList::new(Vec::new())), - instances:Vec::new(), - }); - owned_mesh_id - }); - let owned_mesh=unique_render_config_models.get_mut(owned_mesh_id.get() as usize).unwrap(); - match &mut owned_mesh.polys{ - model::PolygonGroup::PolygonList(polygon_list)=>polygon_list.extend( - graphics_group.groups.iter().flat_map(|polygon_group_id|{ - mesh.polygon_groups[polygon_group_id.get() as usize].polys() - }) - .map(|vertex_id_slice| - vertex_id_slice.to_vec() - ) - ), - } - } - } - owned_mesh_map - }); - for owned_mesh_id in owned_mesh_map.values(){ - let owned_mesh=unique_render_config_models.get_mut(owned_mesh_id.get() as usize).unwrap(); - let render_config=&map.render_configs[owned_mesh.render_config.get() as usize]; - if model.color.w==0.0&&render_config.texture.is_none(){ - continue; - } - owned_mesh.instances.push(instance.clone()); - } - } - //check every model to see if it's using the same (texture,color) but has few instances,if it is combine it into one model - //1. collect unique instances of texture and color,note model id - //2. for each model id,check if removing it from the pool decreases both the model count and instance count by more than one - //3. transpose all models that stay in the set + //split groups with different textures into separate models + //the models received here are supposed to be tightly packed,i.e. no code needs to check if two models are using the same groups. + let indexed_models_len = map.models.len(); + //models split into graphics_group.RenderConfigId + let mut owned_mesh_id_from_mesh_id_render_config_id: HashMap< + model::MeshId, + HashMap, + > = HashMap::new(); + let mut unique_render_config_models: Vec = + Vec::with_capacity(indexed_models_len); + for model in &map.models { + //wow + let instance = GraphicsModelOwned { + transform: model.transform.into(), + normal_transform: glam::Mat3::from_cols_array_2d( + &model + .transform + .matrix3 + .to_array() + .map(|row| row.map(Into::into)), + ) + .inverse() + .transpose(), + color: GraphicsModelColor4::new(model.color), + }; + //get or create owned mesh map + let owned_mesh_map = owned_mesh_id_from_mesh_id_render_config_id + .entry(model.mesh) + .or_insert_with(|| { + let mut owned_mesh_map = HashMap::new(); + //add mesh if renderid never before seen for this model + //add instance + //convert Model into GraphicsModelOwned + //check each group, if it's using a new render config then make a new clone of the model + if let Some(mesh) = map.meshes.get(model.mesh.get() as usize) { + for graphics_group in mesh.graphics_groups.iter() { + //get or create owned mesh + let owned_mesh_id = owned_mesh_map + .entry(graphics_group.render) + .or_insert_with(|| { + //create + let owned_mesh_id = IndexedGraphicsMeshOwnedRenderConfigId::new( + unique_render_config_models.len() as u32, + ); + unique_render_config_models.push( + IndexedGraphicsMeshOwnedRenderConfig { + unique_pos: mesh + .unique_pos + .iter() + .map(|v| v.to_array().map(Into::into)) + .collect(), + unique_tex: mesh + .unique_tex + .iter() + .map(|v| *v.as_ref()) + .collect(), + unique_normal: mesh + .unique_normal + .iter() + .map(|v| v.to_array().map(Into::into)) + .collect(), + unique_color: mesh + .unique_color + .iter() + .map(|v| *v.as_ref()) + .collect(), + unique_vertices: mesh.unique_vertices.clone(), + render_config: graphics_group.render, + polys: model::PolygonGroup::PolygonList( + model::PolygonList::new(Vec::new()), + ), + instances: Vec::new(), + }, + ); + owned_mesh_id + }); + let owned_mesh = unique_render_config_models + .get_mut(owned_mesh_id.get() as usize) + .unwrap(); + match &mut owned_mesh.polys { + model::PolygonGroup::PolygonList(polygon_list) => polygon_list + .extend( + graphics_group + .groups + .iter() + .flat_map(|polygon_group_id| { + mesh.polygon_groups[polygon_group_id.get() as usize] + .polys() + }) + .map(|vertex_id_slice| vertex_id_slice.to_vec()), + ), + } + } + } + owned_mesh_map + }); + for owned_mesh_id in owned_mesh_map.values() { + let owned_mesh = unique_render_config_models + .get_mut(owned_mesh_id.get() as usize) + .unwrap(); + let render_config = &map.render_configs[owned_mesh.render_config.get() as usize]; + if model.color.w == 0.0 && render_config.texture.is_none() { + continue; + } + owned_mesh.instances.push(instance.clone()); + } + } + //check every model to see if it's using the same (texture,color) but has few instances,if it is combine it into one model + //1. collect unique instances of texture and color,note model id + //2. for each model id,check if removing it from the pool decreases both the model count and instance count by more than one + //3. transpose all models that stay in the set - //best plan:benchmark set_bind_group,set_vertex_buffer,set_index_buffer and draw_indexed - //check if the estimated render performance is better by transposing multiple model instances into one model instance + //best plan:benchmark set_bind_group,set_vertex_buffer,set_index_buffer and draw_indexed + //check if the estimated render performance is better by transposing multiple model instances into one model instance - //for now:just deduplicate single models... - let mut deduplicated_models=Vec::with_capacity(indexed_models_len);//use indexed_models_len because the list will likely get smaller instead of bigger - let mut unique_texture_color=HashMap::new();//texture->color->vec![(model_id,instance_id)] - for (model_id,model) in unique_render_config_models.iter().enumerate(){ - //for now:filter out models with more than one instance - if 1color->vec![(model_id,instance_id)] + for (model_id, model) in unique_render_config_models.iter().enumerate() { + //for now:filter out models with more than one instance + if 1 < model.instances.len() { + continue; + } + //populate hashmap + let unique_color = unique_texture_color + .entry(model.render_config) + .or_insert_with(|| HashMap::new()); + //separate instances by color + for (instance_id, instance) in model.instances.iter().enumerate() { + let model_instance_list = unique_color + .entry(instance.color) + .or_insert_with(|| Vec::new()); + //add model instance to list + model_instance_list.push((model_id, instance_id)); + } + } + //populate a hashset of models selected for transposition + //construct transposed models + let mut selected_model_instances = HashSet::new(); + for (render_config, unique_color) in unique_texture_color.into_iter() { + for (color, model_instance_list) in unique_color.into_iter() { + //world transforming one model does not meet the definition of deduplicaiton + if 1 < model_instance_list.len() { + //create model + 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 polys=Vec::new(); - //transform instance vertices - for (model_id,instance_id) in model_instance_list.into_iter(){ - //populate hashset to prevent these models from being copied - selected_model_instances.insert(model_id); - //there is only one instance per model - let model=&unique_render_config_models[model_id]; - let instance=&model.instances[instance_id]; - //just hash word slices LOL - let map_pos_id:Vec=model.unique_pos.iter().map(|untransformed_pos|{ - let pos=instance.transform.transform_point3(glam::Vec3::from_array(untransformed_pos.clone())).to_array(); - let h=bytemuck::cast::<[f32;3],[u32;3]>(pos); - PositionId::new(*pos_id_from.entry(h).or_insert_with(||{ - let pos_id=unique_pos.len(); - unique_pos.push(pos); - pos_id - }) as u32) - }).collect(); - let map_tex_id:Vec=model.unique_tex.iter().map(|&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(tex); - tex_id - }) as u32) - }).collect(); - let map_normal_id:Vec=model.unique_normal.iter().map(|untransformed_normal|{ - let normal=(instance.normal_transform*glam::Vec3::from_array(untransformed_normal.clone())).to_array(); - let h=bytemuck::cast::<[f32;3],[u32;3]>(normal); - NormalId::new(*normal_id_from.entry(h).or_insert_with(||{ - let normal_id=unique_normal.len(); - unique_normal.push(normal); - normal_id - }) as u32) - }).collect(); - let map_color_id:Vec=model.unique_color.iter().map(|&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(color); - color_id - }) as u32) - }).collect(); - //map the indexed vertices onto new indices - //creating the vertex map is slightly different because the vertices are directly hashable - let map_vertex_id:Vec=model.unique_vertices.iter().map(|unmapped_vertex|{ - let vertex=model::IndexedVertex{ - pos:map_pos_id[unmapped_vertex.pos.get() as usize], - tex:map_tex_id[unmapped_vertex.tex.get() as usize], - normal:map_normal_id[unmapped_vertex.normal.get() as usize], - color:map_color_id[unmapped_vertex.color.get() as usize], - }; - 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) - }).collect(); - polys.extend(model.polys.polys().map(|poly| - poly.iter().map(|vertex_id| - map_vertex_id[vertex_id.get() as usize] - ).collect() - )); - } - //push model into dedup - deduplicated_models.push(IndexedGraphicsMeshOwnedRenderConfig{ - unique_pos, - unique_tex, - unique_normal, - unique_color, - unique_vertices, - render_config, - polys:model::PolygonGroup::PolygonList(model::PolygonList::new(polys)), - instances:vec![GraphicsModelOwned{ - transform:glam::Mat4::IDENTITY, - normal_transform:glam::Mat3::IDENTITY, - color - }], - }); - } - } - } - //fill untouched models - for (model_id,model) in unique_render_config_models.into_iter().enumerate(){ - if !selected_model_instances.contains(&model_id){ - deduplicated_models.push(model); - } - } + let mut polys = Vec::new(); + //transform instance vertices + for (model_id, instance_id) in model_instance_list.into_iter() { + //populate hashset to prevent these models from being copied + selected_model_instances.insert(model_id); + //there is only one instance per model + let model = &unique_render_config_models[model_id]; + let instance = &model.instances[instance_id]; + //just hash word slices LOL + let map_pos_id: Vec = model + .unique_pos + .iter() + .map(|untransformed_pos| { + let pos = instance + .transform + .transform_point3(glam::Vec3::from_array( + untransformed_pos.clone(), + )) + .to_array(); + let h = bytemuck::cast::<[f32; 3], [u32; 3]>(pos); + PositionId::new(*pos_id_from.entry(h).or_insert_with(|| { + let pos_id = unique_pos.len(); + unique_pos.push(pos); + pos_id + }) as u32) + }) + .collect(); + let map_tex_id: Vec = model + .unique_tex + .iter() + .map(|&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(tex); + tex_id + }, + ) as u32) + }) + .collect(); + let map_normal_id: Vec = model + .unique_normal + .iter() + .map(|untransformed_normal| { + let normal = (instance.normal_transform + * glam::Vec3::from_array(untransformed_normal.clone())) + .to_array(); + let h = bytemuck::cast::<[f32; 3], [u32; 3]>(normal); + NormalId::new(*normal_id_from.entry(h).or_insert_with(|| { + let normal_id = unique_normal.len(); + unique_normal.push(normal); + normal_id + }) as u32) + }) + .collect(); + let map_color_id: Vec = model + .unique_color + .iter() + .map(|&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(color); + color_id + }) as u32) + }) + .collect(); + //map the indexed vertices onto new indices + //creating the vertex map is slightly different because the vertices are directly hashable + let map_vertex_id: Vec = model + .unique_vertices + .iter() + .map(|unmapped_vertex| { + let vertex = model::IndexedVertex { + pos: map_pos_id[unmapped_vertex.pos.get() as usize], + tex: map_tex_id[unmapped_vertex.tex.get() as usize], + normal: map_normal_id[unmapped_vertex.normal.get() as usize], + color: map_color_id[unmapped_vertex.color.get() as usize], + }; + 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) + }) + .collect(); + polys.extend(model.polys.polys().map(|poly| { + poly.iter() + .map(|vertex_id| map_vertex_id[vertex_id.get() as usize]) + .collect() + })); + } + //push model into dedup + deduplicated_models.push(IndexedGraphicsMeshOwnedRenderConfig { + unique_pos, + unique_tex, + unique_normal, + unique_color, + unique_vertices, + render_config, + polys: model::PolygonGroup::PolygonList(model::PolygonList::new(polys)), + instances: vec![GraphicsModelOwned { + transform: glam::Mat4::IDENTITY, + normal_transform: glam::Mat3::IDENTITY, + color, + }], + }); + } + } + } + //fill untouched models + for (model_id, model) in unique_render_config_models.into_iter().enumerate() { + if !selected_model_instances.contains(&model_id) { + deduplicated_models.push(model); + } + } - //de-index models - let deduplicated_models_len=deduplicated_models.len(); - let models:Vec=deduplicated_models.into_iter().map(|model|{ - let mut vertices=Vec::new(); - let mut index_from_vertex=HashMap::new();//:: - //this mut be combined in a more complex way if the models use different render patterns per group - let mut indices=Vec::new(); - for poly in model.polys.polys(){ - let mut poly_vertices=poly.iter() - .map(|&vertex_index|*index_from_vertex.entry(vertex_index).or_insert_with(||{ - let i=vertices.len(); - let vertex=&model.unique_vertices[vertex_index.get() as usize]; - vertices.push(GraphicsVertex{ - pos:model.unique_pos[vertex.pos.get() as usize], - tex:model.unique_tex[vertex.tex.get() as usize], - normal:model.unique_normal[vertex.normal.get() as usize], - color:model.unique_color[vertex.color.get() as usize], - }); - i - })); + //de-index models + let deduplicated_models_len = deduplicated_models.len(); + let models: Vec = deduplicated_models + .into_iter() + .map(|model| { + let mut vertices = Vec::new(); + let mut index_from_vertex = HashMap::new(); //:: + //this mut be combined in a more complex way if the models use different render patterns per group + let mut indices = Vec::new(); + for poly in model.polys.polys() { + let mut poly_vertices = poly.iter().map(|&vertex_index| { + *index_from_vertex.entry(vertex_index).or_insert_with(|| { + let i = vertices.len(); + let vertex = &model.unique_vertices[vertex_index.get() as usize]; + vertices.push(GraphicsVertex { + pos: model.unique_pos[vertex.pos.get() as usize], + tex: model.unique_tex[vertex.tex.get() as usize], + normal: model.unique_normal[vertex.normal.get() as usize], + color: model.unique_color[vertex.color.get() as usize], + }); + i + }) + }); - let a=poly_vertices.next().unwrap(); - let mut b=poly_vertices.next().unwrap(); + let a = poly_vertices.next().unwrap(); + let mut b = poly_vertices.next().unwrap(); - poly_vertices.for_each(|c|{ - indices.extend([a,b,c]); - b=c; - }); - } - GraphicsMeshOwnedRenderConfig{ - instances:model.instances, - indices:if (u32::MAX as usize)Indices::new(device,indices,wgpu::IndexFormat::Uint32), - model_graphics::Indices::U16(indices)=>Indices::new(device,indices,wgpu::IndexFormat::Uint16), - }, - bind_group, - }); - } - } - println!("Texture References={}",num_textures); - println!("Textures Loaded={}",texture_views.len()); - println!("Indexed Models={}",indexed_models_len); - println!("Deduplicated Models={}",deduplicated_models_len); - println!("Graphics Objects:{}",self.models.len()); - println!("Graphics Instances:{}",instance_count); - } + poly_vertices.for_each(|c| { + indices.extend([a, b, c]); + b = c; + }); + } + GraphicsMeshOwnedRenderConfig { + instances: model.instances, + indices: if (u32::MAX as usize) < vertices.len() { + panic!("Model has too many vertices!") + } else if (u16::MAX as usize) < vertices.len() { + model_graphics::Indices::U32( + indices + .into_iter() + .map(|vertex_idx| vertex_idx as u32) + .collect(), + ) + } else { + model_graphics::Indices::U16( + indices + .into_iter() + .map(|vertex_idx| vertex_idx as u16) + .collect(), + ) + }, + vertices, + render_config: model.render_config, + } + }) + .collect(); + //.into_iter() the modeldata vec so entities can be /moved/ to models.entities + let mut model_count = 0; + let mut instance_count = 0; + let uniform_buffer_binding_size = + crate::setup::required_limits().max_uniform_buffer_binding_size as usize; + let chunk_size = uniform_buffer_binding_size / MODEL_BUFFER_SIZE_BYTES; + self.models.reserve(models.len()); + for model in models.into_iter() { + instance_count += model.instances.len(); + for instances_chunk in model.instances.rchunks(chunk_size) { + model_count += 1; + let mut model_uniforms = get_instances_buffer_data(instances_chunk); + //TEMP: fill with zeroes to pass validation + model_uniforms.resize(MODEL_BUFFER_SIZE * 512, 0.0f32); + let model_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some(format!("Model{} Buf", model_count).as_str()), + contents: bytemuck::cast_slice(&model_uniforms), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }); + let render_config = &map.render_configs[model.render_config.get() as usize]; + let texture_view = render_config + .texture + .and_then(|texture_id| texture_views.get(&texture_id)) + .unwrap_or(&self.temp_squid_texture_view); + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &self.bind_group_layouts.model, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: model_buf.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::TextureView(texture_view), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: wgpu::BindingResource::Sampler(&self.samplers.repeat), + }, + ], + label: Some(format!("Model{} Bind Group", model_count).as_str()), + }); + let vertex_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Vertex"), + contents: bytemuck::cast_slice(&model.vertices), + usage: wgpu::BufferUsages::VERTEX, + }); + //all of these are being moved here + self.models.push(GraphicsModel { + instance_count: instances_chunk.len() as u32, + vertex_buf, + indices: match &model.indices { + model_graphics::Indices::U32(indices) => { + Indices::new(device, indices, wgpu::IndexFormat::Uint32) + } + model_graphics::Indices::U16(indices) => { + Indices::new(device, indices, wgpu::IndexFormat::Uint16) + } + }, + bind_group, + }); + } + } + println!("Texture References={}", num_textures); + println!("Textures Loaded={}", texture_views.len()); + println!("Indexed Models={}", indexed_models_len); + println!("Deduplicated Models={}", deduplicated_models_len); + println!("Graphics Objects:{}", self.models.len()); + println!("Graphics Instances:{}", instance_count); + } - pub fn new( - device:&wgpu::Device, - queue:&wgpu::Queue, - config:&wgpu::SurfaceConfiguration, - )->Self{ - let camera_bind_group_layout=device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor{ - label:None, - entries:&[ - wgpu::BindGroupLayoutEntry{ - binding:0, - visibility:wgpu::ShaderStages::VERTEX, - ty:wgpu::BindingType::Buffer{ - ty:wgpu::BufferBindingType::Uniform, - has_dynamic_offset:false, - min_binding_size:None, - }, - count:None, - }, - ], - }); - let skybox_texture_bind_group_layout=device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor{ - label:Some("Skybox Texture Bind Group Layout"), - entries:&[ - wgpu::BindGroupLayoutEntry{ - binding:0, - visibility:wgpu::ShaderStages::FRAGMENT, - ty:wgpu::BindingType::Texture{ - sample_type:wgpu::TextureSampleType::Float{filterable:true}, - multisampled:false, - view_dimension:wgpu::TextureViewDimension::Cube, - }, - count:None, - }, - wgpu::BindGroupLayoutEntry{ - binding:1, - visibility:wgpu::ShaderStages::FRAGMENT, - ty:wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), - count:None, - }, - ], - }); - let model_bind_group_layout=device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor{ - label:Some("Model Bind Group Layout"), - entries:&[ - wgpu::BindGroupLayoutEntry{ - binding:0, - visibility:wgpu::ShaderStages::VERTEX, - ty:wgpu::BindingType::Buffer{ - ty:wgpu::BufferBindingType::Uniform, - has_dynamic_offset:false, - min_binding_size:None, - }, - count:None, - }, - wgpu::BindGroupLayoutEntry{ - binding:1, - visibility:wgpu::ShaderStages::FRAGMENT, - ty:wgpu::BindingType::Texture{ - sample_type:wgpu::TextureSampleType::Float{filterable:true}, - multisampled:false, - view_dimension:wgpu::TextureViewDimension::D2, - }, - count:None, - }, - wgpu::BindGroupLayoutEntry{ - binding:2, - visibility:wgpu::ShaderStages::FRAGMENT, - ty:wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), - count:None, - }, - ], - }); + pub fn new( + device: &wgpu::Device, + queue: &wgpu::Queue, + config: &wgpu::SurfaceConfiguration, + ) -> Self { + let camera_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }], + }); + let skybox_texture_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("Skybox Texture Bind Group Layout"), + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + multisampled: false, + view_dimension: wgpu::TextureViewDimension::Cube, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + ], + }); + let model_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("Model Bind Group Layout"), + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + multisampled: false, + view_dimension: wgpu::TextureViewDimension::D2, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 2, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + ], + }); - let clamp_sampler=device.create_sampler(&wgpu::SamplerDescriptor{ - label:Some("Clamp Sampler"), - address_mode_u:wgpu::AddressMode::ClampToEdge, - address_mode_v:wgpu::AddressMode::ClampToEdge, - address_mode_w:wgpu::AddressMode::ClampToEdge, - mag_filter:wgpu::FilterMode::Linear, - min_filter:wgpu::FilterMode::Linear, - mipmap_filter:wgpu::FilterMode::Linear, - ..Default::default() - }); - let repeat_sampler=device.create_sampler(&wgpu::SamplerDescriptor{ - label:Some("Repeat Sampler"), - address_mode_u:wgpu::AddressMode::Repeat, - address_mode_v:wgpu::AddressMode::Repeat, - address_mode_w:wgpu::AddressMode::Repeat, - mag_filter:wgpu::FilterMode::Linear, - min_filter:wgpu::FilterMode::Linear, - mipmap_filter:wgpu::FilterMode::Linear, - anisotropy_clamp:16, - ..Default::default() - }); + let clamp_sampler = device.create_sampler(&wgpu::SamplerDescriptor { + label: Some("Clamp Sampler"), + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Linear, + mipmap_filter: wgpu::FilterMode::Linear, + ..Default::default() + }); + let repeat_sampler = device.create_sampler(&wgpu::SamplerDescriptor { + label: Some("Repeat Sampler"), + address_mode_u: wgpu::AddressMode::Repeat, + address_mode_v: wgpu::AddressMode::Repeat, + address_mode_w: wgpu::AddressMode::Repeat, + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Linear, + mipmap_filter: wgpu::FilterMode::Linear, + anisotropy_clamp: 16, + ..Default::default() + }); - // Create the render pipeline - let shader=device.create_shader_module(wgpu::ShaderModuleDescriptor{ - label:None, - source:wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))), - }); + // Create the render pipeline + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))), + }); - //load textures - let device_features=device.features(); + //load textures + let device_features = device.features(); - let skybox_texture_view={ - let skybox_format=if device_features.contains(wgpu::Features::TEXTURE_COMPRESSION_ASTC){ - println!("Using ASTC"); - wgpu::TextureFormat::Astc{ - block:AstcBlock::B4x4, - channel:AstcChannel::UnormSrgb, - } - }else if device_features.contains(wgpu::Features::TEXTURE_COMPRESSION_ETC2){ - println!("Using ETC2"); - wgpu::TextureFormat::Etc2Rgb8UnormSrgb - }else if device_features.contains(wgpu::Features::TEXTURE_COMPRESSION_BC){ - println!("Using BC"); - wgpu::TextureFormat::Bc1RgbaUnormSrgb - }else{ - println!("Using plain"); - wgpu::TextureFormat::Bgra8UnormSrgb - }; + let skybox_texture_view = { + let skybox_format = + if device_features.contains(wgpu::Features::TEXTURE_COMPRESSION_ASTC) { + println!("Using ASTC"); + wgpu::TextureFormat::Astc { + block: AstcBlock::B4x4, + channel: AstcChannel::UnormSrgb, + } + } else if device_features.contains(wgpu::Features::TEXTURE_COMPRESSION_ETC2) { + println!("Using ETC2"); + wgpu::TextureFormat::Etc2Rgb8UnormSrgb + } else if device_features.contains(wgpu::Features::TEXTURE_COMPRESSION_BC) { + println!("Using BC"); + wgpu::TextureFormat::Bc1RgbaUnormSrgb + } else { + println!("Using plain"); + wgpu::TextureFormat::Bgra8UnormSrgb + }; - let bytes=match skybox_format{ - wgpu::TextureFormat::Astc{ - block:AstcBlock::B4x4, - channel:AstcChannel::UnormSrgb, - }=>&include_bytes!("../images/astc.dds")[..], - wgpu::TextureFormat::Etc2Rgb8UnormSrgb=>&include_bytes!("../images/etc2.dds")[..], - wgpu::TextureFormat::Bc1RgbaUnormSrgb=>&include_bytes!("../images/bc1.dds")[..], - wgpu::TextureFormat::Bgra8UnormSrgb=>&include_bytes!("../images/bgra.dds")[..], - _=>unreachable!(), - }; + let bytes = match skybox_format { + wgpu::TextureFormat::Astc { + block: AstcBlock::B4x4, + channel: AstcChannel::UnormSrgb, + } => &include_bytes!("../images/astc.dds")[..], + wgpu::TextureFormat::Etc2Rgb8UnormSrgb => &include_bytes!("../images/etc2.dds")[..], + wgpu::TextureFormat::Bc1RgbaUnormSrgb => &include_bytes!("../images/bc1.dds")[..], + wgpu::TextureFormat::Bgra8UnormSrgb => &include_bytes!("../images/bgra.dds")[..], + _ => unreachable!(), + }; - let skybox_image=ddsfile::Dds::read(&mut std::io::Cursor::new(bytes)).unwrap(); + let skybox_image = ddsfile::Dds::read(&mut std::io::Cursor::new(bytes)).unwrap(); - let size=wgpu::Extent3d{ - width:skybox_image.get_width(), - height:skybox_image.get_height(), - depth_or_array_layers:6, - }; + let size = wgpu::Extent3d { + width: skybox_image.get_width(), + height: skybox_image.get_height(), + depth_or_array_layers: 6, + }; - let layer_size=wgpu::Extent3d{ - depth_or_array_layers:1, - ..size - }; - let max_mips=layer_size.max_mips(wgpu::TextureDimension::D2); + let layer_size = wgpu::Extent3d { + depth_or_array_layers: 1, + ..size + }; + let max_mips = layer_size.max_mips(wgpu::TextureDimension::D2); - let skybox_texture=device.create_texture_with_data( - queue, - &wgpu::TextureDescriptor{ - size, - mip_level_count:max_mips, - sample_count:1, - dimension:wgpu::TextureDimension::D2, - format:skybox_format, - usage:wgpu::TextureUsages::TEXTURE_BINDING|wgpu::TextureUsages::COPY_DST, - label:Some("Skybox Texture"), - view_formats:&[], - }, - wgpu::util::TextureDataOrder::LayerMajor, - &skybox_image.data, - ); + let skybox_texture = device.create_texture_with_data( + queue, + &wgpu::TextureDescriptor { + size, + mip_level_count: max_mips, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: skybox_format, + usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + label: Some("Skybox Texture"), + view_formats: &[], + }, + wgpu::util::TextureDataOrder::LayerMajor, + &skybox_image.data, + ); - skybox_texture.create_view(&wgpu::TextureViewDescriptor{ - label:Some("Skybox Texture View"), - dimension:Some(wgpu::TextureViewDimension::Cube), - ..wgpu::TextureViewDescriptor::default() - }) - }; + skybox_texture.create_view(&wgpu::TextureViewDescriptor { + label: Some("Skybox Texture View"), + dimension: Some(wgpu::TextureViewDimension::Cube), + ..wgpu::TextureViewDescriptor::default() + }) + }; - //squid - let squid_texture_view={ - let bytes=include_bytes!("../images/squid.dds"); + //squid + let squid_texture_view = { + let bytes = include_bytes!("../images/squid.dds"); - let image=ddsfile::Dds::read(&mut std::io::Cursor::new(bytes)).unwrap(); + let image = ddsfile::Dds::read(&mut std::io::Cursor::new(bytes)).unwrap(); - let size=wgpu::Extent3d{ - width:image.get_width(), - height:image.get_height(), - depth_or_array_layers:1, - }; + let size = wgpu::Extent3d { + width: image.get_width(), + height: image.get_height(), + depth_or_array_layers: 1, + }; - let layer_size=wgpu::Extent3d{ - depth_or_array_layers:1, - ..size - }; - let max_mips=layer_size.max_mips(wgpu::TextureDimension::D2); + let layer_size = wgpu::Extent3d { + depth_or_array_layers: 1, + ..size + }; + let max_mips = layer_size.max_mips(wgpu::TextureDimension::D2); - let texture=device.create_texture_with_data( - queue, - &wgpu::TextureDescriptor{ - size, - mip_level_count:max_mips, - sample_count:1, - dimension:wgpu::TextureDimension::D2, - format:wgpu::TextureFormat::Bc7RgbaUnorm, - usage:wgpu::TextureUsages::TEXTURE_BINDING|wgpu::TextureUsages::COPY_DST, - label:Some("Squid Texture"), - view_formats:&[], - }, - wgpu::util::TextureDataOrder::LayerMajor, - &image.data, - ); + let texture = device.create_texture_with_data( + queue, + &wgpu::TextureDescriptor { + size, + mip_level_count: max_mips, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Bc7RgbaUnorm, + usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + label: Some("Squid Texture"), + view_formats: &[], + }, + wgpu::util::TextureDataOrder::LayerMajor, + &image.data, + ); - texture.create_view(&wgpu::TextureViewDescriptor{ - label:Some("Squid Texture View"), - dimension:Some(wgpu::TextureViewDimension::D2), - ..wgpu::TextureViewDescriptor::default() - }) - }; + texture.create_view(&wgpu::TextureViewDescriptor { + label: Some("Squid Texture View"), + dimension: Some(wgpu::TextureViewDimension::D2), + ..wgpu::TextureViewDescriptor::default() + }) + }; - let model_pipeline_layout=device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor{ - label:None, - bind_group_layouts:&[ - &camera_bind_group_layout, - &skybox_texture_bind_group_layout, - &model_bind_group_layout, - ], - push_constant_ranges:&[], - }); - let sky_pipeline_layout=device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor{ - label:None, - bind_group_layouts:&[ - &camera_bind_group_layout, - &skybox_texture_bind_group_layout, - ], - push_constant_ranges:&[], - }); + let model_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[ + &camera_bind_group_layout, + &skybox_texture_bind_group_layout, + &model_bind_group_layout, + ], + push_constant_ranges: &[], + }); + let sky_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[&camera_bind_group_layout, &skybox_texture_bind_group_layout], + push_constant_ranges: &[], + }); - // Create the render pipelines - let sky_pipeline=device.create_render_pipeline(&wgpu::RenderPipelineDescriptor{ - label:Some("Sky Pipeline"), - layout:Some(&sky_pipeline_layout), - vertex:wgpu::VertexState{ - module:&shader, - entry_point:Some("vs_sky"), - buffers:&[], - compilation_options:wgpu::PipelineCompilationOptions::default(), - }, - fragment:Some(wgpu::FragmentState{ - module:&shader, - entry_point:Some("fs_sky"), - targets:&[Some(config.view_formats[0].into())], - compilation_options:wgpu::PipelineCompilationOptions::default(), - }), - primitive:wgpu::PrimitiveState{ - front_face:wgpu::FrontFace::Cw, - ..Default::default() - }, - depth_stencil:Some(wgpu::DepthStencilState{ - format:Self::DEPTH_FORMAT, - depth_write_enabled:false, - depth_compare:wgpu::CompareFunction::LessEqual, - stencil:wgpu::StencilState::default(), - bias:wgpu::DepthBiasState::default(), - }), - multisample:wgpu::MultisampleState::default(), - multiview:None, - cache:None, - }); - let model_pipeline=device.create_render_pipeline(&wgpu::RenderPipelineDescriptor{ + // Create the render pipelines + let sky_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Sky Pipeline"), + layout: Some(&sky_pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: Some("vs_sky"), + buffers: &[], + compilation_options: wgpu::PipelineCompilationOptions::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: Some("fs_sky"), + targets: &[Some(config.view_formats[0].into())], + compilation_options: wgpu::PipelineCompilationOptions::default(), + }), + primitive: wgpu::PrimitiveState { + front_face: wgpu::FrontFace::Cw, + ..Default::default() + }, + depth_stencil: Some(wgpu::DepthStencilState { + format: Self::DEPTH_FORMAT, + depth_write_enabled: false, + depth_compare: wgpu::CompareFunction::LessEqual, + stencil: wgpu::StencilState::default(), + bias: wgpu::DepthBiasState::default(), + }), + multisample: wgpu::MultisampleState::default(), + multiview: None, + cache: None, + }); + let model_pipeline=device.create_render_pipeline(&wgpu::RenderPipelineDescriptor{ label:Some("Model Pipeline"), layout:Some(&model_pipeline_layout), vertex:wgpu::VertexState{ @@ -807,177 +926,185 @@ impl GraphicsState{ cache:None, }); - let camera=GraphicsCamera::default(); - let camera_uniforms=camera.to_uniform_data(glam::Vec3::ZERO,glam::Vec2::ZERO); - let camera_buf=device.create_buffer_init(&wgpu::util::BufferInitDescriptor{ - label:Some("Camera"), - contents:bytemuck::cast_slice(&camera_uniforms), - usage:wgpu::BufferUsages::UNIFORM|wgpu::BufferUsages::COPY_DST, - }); - let camera_bind_group=device.create_bind_group(&wgpu::BindGroupDescriptor{ - layout:&camera_bind_group_layout, - entries:&[ - wgpu::BindGroupEntry{ - binding:0, - resource:camera_buf.as_entire_binding(), - }, - ], - label:Some("Camera"), - }); + let camera = GraphicsCamera::default(); + let camera_uniforms = camera.to_uniform_data(glam::Vec3::ZERO, glam::Vec2::ZERO); + let camera_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Camera"), + contents: bytemuck::cast_slice(&camera_uniforms), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }); + let camera_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &camera_bind_group_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: camera_buf.as_entire_binding(), + }], + label: Some("Camera"), + }); - let skybox_texture_bind_group=device.create_bind_group(&wgpu::BindGroupDescriptor{ - layout:&skybox_texture_bind_group_layout, - entries:&[ - wgpu::BindGroupEntry{ - binding:0, - resource:wgpu::BindingResource::TextureView(&skybox_texture_view), - }, - wgpu::BindGroupEntry{ - binding:1, - resource:wgpu::BindingResource::Sampler(&clamp_sampler), - }, - ], - label:Some("Sky Texture"), - }); + let skybox_texture_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &skybox_texture_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&skybox_texture_view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&clamp_sampler), + }, + ], + label: Some("Sky Texture"), + }); - let depth_view=Self::create_depth_texture(config,device); + let depth_view = Self::create_depth_texture(config, device); - Self{ - pipelines:GraphicsPipelines{ - skybox:sky_pipeline, - model:model_pipeline - }, - bind_groups:GraphicsBindGroups{ - camera:camera_bind_group, - skybox_texture:skybox_texture_bind_group, - }, - camera, - camera_buf, - models:Vec::new(), - depth_view, - staging_belt:wgpu::util::StagingBelt::new(0x100), - bind_group_layouts:GraphicsBindGroupLayouts{model:model_bind_group_layout}, - samplers:GraphicsSamplers{repeat:repeat_sampler}, - temp_squid_texture_view:squid_texture_view, - } - } - pub fn resize( - &mut self, - device:&wgpu::Device, - config:&wgpu::SurfaceConfiguration, - user_settings:&crate::settings::UserSettings, - ){ - self.depth_view=Self::create_depth_texture(config,device); - self.camera.screen_size=glam::uvec2(config.width,config.height); - self.load_user_settings(user_settings); - } - pub fn render( - &mut self, - view:&wgpu::TextureView, - device:&wgpu::Device, - queue:&wgpu::Queue, - frame_state:crate::physics_worker::FrameState, - ){ - //TODO:use scheduled frame times to create beautiful smoothing simulation physics extrapolation assuming no input + Self { + pipelines: GraphicsPipelines { + skybox: sky_pipeline, + model: model_pipeline, + }, + bind_groups: GraphicsBindGroups { + camera: camera_bind_group, + skybox_texture: skybox_texture_bind_group, + }, + camera, + camera_buf, + models: Vec::new(), + depth_view, + staging_belt: wgpu::util::StagingBelt::new(0x100), + bind_group_layouts: GraphicsBindGroupLayouts { + model: model_bind_group_layout, + }, + samplers: GraphicsSamplers { + repeat: repeat_sampler, + }, + temp_squid_texture_view: squid_texture_view, + } + } + pub fn resize( + &mut self, + device: &wgpu::Device, + config: &wgpu::SurfaceConfiguration, + user_settings: &crate::settings::UserSettings, + ) { + self.depth_view = Self::create_depth_texture(config, device); + self.camera.screen_size = glam::uvec2(config.width, config.height); + self.load_user_settings(user_settings); + } + pub fn render( + &mut self, + view: &wgpu::TextureView, + device: &wgpu::Device, + queue: &wgpu::Queue, + frame_state: crate::physics_worker::FrameState, + ) { + //TODO:use scheduled frame times to create beautiful smoothing simulation physics extrapolation assuming no input - let mut encoder=device.create_command_encoder(&wgpu::CommandEncoderDescriptor{label:None}); + let mut encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); - // update rotation - let camera_uniforms=self.camera.to_uniform_data( - frame_state.body.extrapolated_position(frame_state.time).map(Into::::into).to_array().into(), - frame_state.camera.simulate_move_angles(glam::IVec2::ZERO) - ); - self.staging_belt - .write_buffer( - &mut encoder, - &self.camera_buf, - 0, - wgpu::BufferSize::new((camera_uniforms.len() * 4) as wgpu::BufferAddress).unwrap(), - device, - ) - .copy_from_slice(bytemuck::cast_slice(&camera_uniforms)); - //This code only needs to run when the uniforms change - /* - for model in self.models.iter(){ - let model_uniforms=get_instances_buffer_data(&model.instances); - self.staging_belt - .write_buffer( - &mut encoder, - &model.model_buf,//description of where data will be written when command is executed - 0,//offset in staging belt? - wgpu::BufferSize::new((model_uniforms.len() * 4) as wgpu::BufferAddress).unwrap(), - device, - ) - .copy_from_slice(bytemuck::cast_slice(&model_uniforms)); - } - */ - self.staging_belt.finish(); + // update rotation + let camera_uniforms = self.camera.to_uniform_data( + frame_state + .body + .extrapolated_position(frame_state.time) + .map(Into::::into) + .to_array() + .into(), + frame_state.camera.simulate_move_angles(glam::IVec2::ZERO), + ); + self.staging_belt + .write_buffer( + &mut encoder, + &self.camera_buf, + 0, + wgpu::BufferSize::new((camera_uniforms.len() * 4) as wgpu::BufferAddress).unwrap(), + device, + ) + .copy_from_slice(bytemuck::cast_slice(&camera_uniforms)); + //This code only needs to run when the uniforms change + /* + for model in self.models.iter(){ + let model_uniforms=get_instances_buffer_data(&model.instances); + self.staging_belt + .write_buffer( + &mut encoder, + &model.model_buf,//description of where data will be written when command is executed + 0,//offset in staging belt? + wgpu::BufferSize::new((model_uniforms.len() * 4) as wgpu::BufferAddress).unwrap(), + device, + ) + .copy_from_slice(bytemuck::cast_slice(&model_uniforms)); + } + */ + self.staging_belt.finish(); - { - let mut rpass=encoder.begin_render_pass(&wgpu::RenderPassDescriptor{ - label:None, - color_attachments:&[Some(wgpu::RenderPassColorAttachment{ - view, - resolve_target:None, - ops:wgpu::Operations{ - load:wgpu::LoadOp::Clear(wgpu::Color{ - r:0.1, - g:0.2, - b:0.3, - a:1.0, - }), - store:wgpu::StoreOp::Store, - }, - })], - depth_stencil_attachment:Some(wgpu::RenderPassDepthStencilAttachment{ - view:&self.depth_view, - depth_ops:Some(wgpu::Operations{ - load:wgpu::LoadOp::Clear(1.0), - store:wgpu::StoreOp::Discard, - }), - stencil_ops:None, - }), - timestamp_writes:Default::default(), - occlusion_query_set:Default::default(), - }); + { + let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color { + r: 0.1, + g: 0.2, + b: 0.3, + a: 1.0, + }), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { + view: &self.depth_view, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Clear(1.0), + store: wgpu::StoreOp::Discard, + }), + stencil_ops: None, + }), + timestamp_writes: Default::default(), + occlusion_query_set: Default::default(), + }); - rpass.set_bind_group(0,&self.bind_groups.camera,&[]); - rpass.set_bind_group(1,&self.bind_groups.skybox_texture,&[]); + rpass.set_bind_group(0, &self.bind_groups.camera, &[]); + rpass.set_bind_group(1, &self.bind_groups.skybox_texture, &[]); - rpass.set_pipeline(&self.pipelines.model); - for model in &self.models{ - rpass.set_bind_group(2,&model.bind_group,&[]); - rpass.set_vertex_buffer(0,model.vertex_buf.slice(..)); - rpass.set_index_buffer(model.indices.buf.slice(..),model.indices.format); - //TODO: loop over triangle strips - rpass.draw_indexed(0..model.indices.count,0,0..model.instance_count); - } + rpass.set_pipeline(&self.pipelines.model); + for model in &self.models { + rpass.set_bind_group(2, &model.bind_group, &[]); + rpass.set_vertex_buffer(0, model.vertex_buf.slice(..)); + rpass.set_index_buffer(model.indices.buf.slice(..), model.indices.format); + //TODO: loop over triangle strips + rpass.draw_indexed(0..model.indices.count, 0, 0..model.instance_count); + } - rpass.set_pipeline(&self.pipelines.skybox); - rpass.draw(0..3,0..1); - } + rpass.set_pipeline(&self.pipelines.skybox); + rpass.draw(0..3, 0..1); + } - queue.submit(std::iter::once(encoder.finish())); + queue.submit(std::iter::once(encoder.finish())); - self.staging_belt.recall(); - } + self.staging_belt.recall(); + } } -const MODEL_BUFFER_SIZE:usize=4*4 + 12 + 4;//let size=std::mem::size_of::(); -const MODEL_BUFFER_SIZE_BYTES:usize=MODEL_BUFFER_SIZE*4; -fn get_instances_buffer_data(instances:&[GraphicsModelOwned])->Vec{ - let mut raw=Vec::with_capacity(MODEL_BUFFER_SIZE*instances.len()); - for mi in instances{ - //model transform - raw.extend_from_slice(&AsRef::<[f32; 4*4]>::as_ref(&mi.transform)[..]); - //normal transform - raw.extend_from_slice(AsRef::<[f32; 3]>::as_ref(&mi.normal_transform.x_axis)); - raw.extend_from_slice(&[0.0]); - raw.extend_from_slice(AsRef::<[f32; 3]>::as_ref(&mi.normal_transform.y_axis)); - raw.extend_from_slice(&[0.0]); - raw.extend_from_slice(AsRef::<[f32; 3]>::as_ref(&mi.normal_transform.z_axis)); - raw.extend_from_slice(&[0.0]); - //color - raw.extend_from_slice(AsRef::<[f32; 4]>::as_ref(&mi.color.get())); - } - raw +const MODEL_BUFFER_SIZE: usize = 4 * 4 + 12 + 4; //let size=std::mem::size_of::(); +const MODEL_BUFFER_SIZE_BYTES: usize = MODEL_BUFFER_SIZE * 4; +fn get_instances_buffer_data(instances: &[GraphicsModelOwned]) -> Vec { + let mut raw = Vec::with_capacity(MODEL_BUFFER_SIZE * instances.len()); + for mi in instances { + //model transform + raw.extend_from_slice(&AsRef::<[f32; 4 * 4]>::as_ref(&mi.transform)[..]); + //normal transform + raw.extend_from_slice(AsRef::<[f32; 3]>::as_ref(&mi.normal_transform.x_axis)); + raw.extend_from_slice(&[0.0]); + raw.extend_from_slice(AsRef::<[f32; 3]>::as_ref(&mi.normal_transform.y_axis)); + raw.extend_from_slice(&[0.0]); + raw.extend_from_slice(AsRef::<[f32; 3]>::as_ref(&mi.normal_transform.z_axis)); + raw.extend_from_slice(&[0.0]); + //color + raw.extend_from_slice(AsRef::<[f32; 4]>::as_ref(&mi.color.get())); + } + raw } diff --git a/strafe-client/src/graphics_worker.rs b/strafe-client/src/graphics_worker.rs index 67c89be..bdd7a47 100644 --- a/strafe-client/src/graphics_worker.rs +++ b/strafe-client/src/graphics_worker.rs @@ -1,65 +1,65 @@ -pub enum Instruction{ - Render(crate::physics_worker::FrameState), - //UpdateModel(crate::graphics::GraphicsModelUpdate), - Resize(winit::dpi::PhysicalSize,crate::settings::UserSettings), - ChangeMap(strafesnet_common::map::CompleteMap), +pub enum Instruction { + Render(crate::physics_worker::FrameState), + //UpdateModel(crate::graphics::GraphicsModelUpdate), + Resize(winit::dpi::PhysicalSize, crate::settings::UserSettings), + ChangeMap(strafesnet_common::map::CompleteMap), } //Ideally the graphics thread worker description is: /* WorkerDescription{ - input:Immediate, - output:Realtime(PoolOrdering::Ordered(3)), + input:Immediate, + output:Realtime(PoolOrdering::Ordered(3)), } */ //up to three frames in flight, dropping new frame requests when all three are busy, and dropping output frames when one renders out of order pub fn new<'a>( - mut graphics:crate::graphics::GraphicsState, - mut config:wgpu::SurfaceConfiguration, - surface:wgpu::Surface<'a>, - device:wgpu::Device, - queue:wgpu::Queue, -)->crate::compat_worker::INWorker<'a,Instruction>{ - let mut resize=None; - crate::compat_worker::INWorker::new(move |ins:Instruction|{ - match ins{ - Instruction::ChangeMap(map)=>{ - graphics.clear(); - graphics.generate_models(&device,&queue,&map); - }, - Instruction::Resize(size,user_settings)=>{ - resize=Some((size,user_settings)); - } - Instruction::Render(frame_state)=>{ - if let Some((size,user_settings))=resize.take(){ - println!("Resizing to {:?}",size); - let t0=std::time::Instant::now(); - config.width=size.width.max(1); - config.height=size.height.max(1); - surface.configure(&device,&config); - graphics.resize(&device,&config,&user_settings); - println!("Resize took {:?}",t0.elapsed()); - } - //this has to go deeper somehow - let frame=match surface.get_current_texture(){ - Ok(frame)=>frame, - Err(_)=>{ - surface.configure(&device,&config); - surface - .get_current_texture() - .expect("Failed to acquire next surface texture!") - } - }; - let view=frame.texture.create_view(&wgpu::TextureViewDescriptor{ - format:Some(config.view_formats[0]), - ..wgpu::TextureViewDescriptor::default() - }); + mut graphics: crate::graphics::GraphicsState, + mut config: wgpu::SurfaceConfiguration, + surface: wgpu::Surface<'a>, + device: wgpu::Device, + queue: wgpu::Queue, +) -> crate::compat_worker::INWorker<'a, Instruction> { + let mut resize = None; + crate::compat_worker::INWorker::new(move |ins: Instruction| { + match ins { + Instruction::ChangeMap(map) => { + graphics.clear(); + graphics.generate_models(&device, &queue, &map); + } + Instruction::Resize(size, user_settings) => { + resize = Some((size, user_settings)); + } + Instruction::Render(frame_state) => { + if let Some((size, user_settings)) = resize.take() { + println!("Resizing to {:?}", size); + let t0 = std::time::Instant::now(); + config.width = size.width.max(1); + config.height = size.height.max(1); + surface.configure(&device, &config); + graphics.resize(&device, &config, &user_settings); + println!("Resize took {:?}", t0.elapsed()); + } + //this has to go deeper somehow + let frame = match surface.get_current_texture() { + Ok(frame) => frame, + Err(_) => { + surface.configure(&device, &config); + surface + .get_current_texture() + .expect("Failed to acquire next surface texture!") + } + }; + let view = frame.texture.create_view(&wgpu::TextureViewDescriptor { + format: Some(config.view_formats[0]), + ..wgpu::TextureViewDescriptor::default() + }); - graphics.render(&view,&device,&queue,frame_state); + graphics.render(&view, &device, &queue, frame_state); - frame.present(); - } - } - }) + frame.present(); + } + } + }) } diff --git a/strafe-client/src/main.rs b/strafe-client/src/main.rs index faae714..47adc22 100644 --- a/strafe-client/src/main.rs +++ b/strafe-client/src/main.rs @@ -1,21 +1,21 @@ mod body; +mod compat_worker; +mod face_crawler; mod file; +mod graphics; +mod graphics_worker; +mod model_graphics; +mod model_physics; +mod physics; +mod physics_worker; +mod push_solve; +mod settings; mod setup; mod window; mod worker; -mod physics; -mod graphics; -mod settings; -mod push_solve; -mod face_crawler; -mod compat_worker; -mod model_physics; -mod model_graphics; -mod physics_worker; -mod graphics_worker; -const TITLE:&'static str=concat!("Strafe Client v",env!("CARGO_PKG_VERSION")); +const TITLE: &'static str = concat!("Strafe Client v", env!("CARGO_PKG_VERSION")); -fn main(){ - setup::setup_and_start(TITLE); +fn main() { + setup::setup_and_start(TITLE); } diff --git a/strafe-client/src/model_graphics.rs b/strafe-client/src/model_graphics.rs index 2468cda..15a744d 100644 --- a/strafe-client/src/model_graphics.rs +++ b/strafe-client/src/model_graphics.rs @@ -1,48 +1,48 @@ -use bytemuck::{Pod,Zeroable}; -use strafesnet_common::model::{IndexedVertex,PolygonGroup,RenderConfigId}; -#[derive(Clone,Copy,Pod,Zeroable)] +use bytemuck::{Pod, Zeroable}; +use strafesnet_common::model::{IndexedVertex, PolygonGroup, RenderConfigId}; +#[derive(Clone, Copy, Pod, Zeroable)] #[repr(C)] -pub struct GraphicsVertex{ - pub pos:[f32;3], - pub tex:[f32;2], - pub normal:[f32;3], - pub color:[f32;4], +pub struct GraphicsVertex { + pub pos: [f32; 3], + pub tex: [f32; 2], + pub normal: [f32; 3], + pub color: [f32; 4], } -#[derive(Clone,Copy,id::Id)] +#[derive(Clone, Copy, id::Id)] pub struct IndexedGraphicsMeshOwnedRenderConfigId(u32); -pub struct IndexedGraphicsMeshOwnedRenderConfig{ - pub unique_pos:Vec<[f32;3]>, - pub unique_tex:Vec<[f32;2]>, - pub unique_normal:Vec<[f32;3]>, - pub unique_color:Vec<[f32;4]>, - pub unique_vertices:Vec, - pub render_config:RenderConfigId, - pub polys:PolygonGroup, - pub instances:Vec, +pub struct IndexedGraphicsMeshOwnedRenderConfig { + pub unique_pos: Vec<[f32; 3]>, + pub unique_tex: Vec<[f32; 2]>, + pub unique_normal: Vec<[f32; 3]>, + pub unique_color: Vec<[f32; 4]>, + pub unique_vertices: Vec, + pub render_config: RenderConfigId, + pub polys: PolygonGroup, + pub instances: Vec, } -pub enum Indices{ - U32(Vec), - U16(Vec), +pub enum Indices { + U32(Vec), + U16(Vec), } -pub struct GraphicsMeshOwnedRenderConfig{ - pub vertices:Vec, - pub indices:Indices, - pub render_config:RenderConfigId, - pub instances:Vec, +pub struct GraphicsMeshOwnedRenderConfig { + pub vertices: Vec, + pub indices: Indices, + pub render_config: RenderConfigId, + pub instances: Vec, } -#[derive(Clone,Copy,PartialEq,id::Id)] +#[derive(Clone, Copy, PartialEq, id::Id)] pub struct GraphicsModelColor4(glam::Vec4); -impl std::hash::Hash for GraphicsModelColor4{ - fn hash(&self,state:&mut H) { - for &f in self.0.as_ref(){ - bytemuck::cast::(f).hash(state); - } - } +impl std::hash::Hash for GraphicsModelColor4 { + fn hash(&self, state: &mut H) { + for &f in self.0.as_ref() { + bytemuck::cast::(f).hash(state); + } + } } -impl Eq for GraphicsModelColor4{} +impl Eq for GraphicsModelColor4 {} #[derive(Clone)] -pub struct GraphicsModelOwned{ - pub transform:glam::Mat4, - pub normal_transform:glam::Mat3, - pub color:GraphicsModelColor4, +pub struct GraphicsModelOwned { + pub transform: glam::Mat4, + pub normal_transform: glam::Mat3, + pub color: GraphicsModelColor4, } diff --git a/strafe-client/src/model_physics.rs b/strafe-client/src/model_physics.rs index d9d30f1..f474637 100644 --- a/strafe-client/src/model_physics.rs +++ b/strafe-client/src/model_physics.rs @@ -1,1017 +1,1384 @@ -use std::borrow::{Borrow,Cow}; -use std::collections::{HashSet,HashMap}; +use std::borrow::{Borrow, Cow}; +use std::collections::{HashMap, HashSet}; use strafesnet_common::integer::vec3::Vector3; -use strafesnet_common::model::{self,MeshId,PolygonIter}; -use strafesnet_common::integer::{self,vec3,Fixed,Planar64,Planar64Vec3,Ratio}; +use strafesnet_common::integer::{self, vec3, Fixed, Planar64, Planar64Vec3, Ratio}; +use strafesnet_common::model::{self, MeshId, PolygonIter}; use strafesnet_common::physics::Time; -type Body=crate::body::Body; +type Body = crate::body::Body; -pub trait UndirectedEdge{ - type DirectedEdge:Copy+DirectedEdge; - fn as_directed(&self,parity:bool)->Self::DirectedEdge; +pub trait UndirectedEdge { + type DirectedEdge: Copy + DirectedEdge; + fn as_directed(&self, parity: bool) -> Self::DirectedEdge; } -pub trait DirectedEdge{ - type UndirectedEdge:Copy+std::fmt::Debug+UndirectedEdge; - fn as_undirected(&self)->Self::UndirectedEdge; - fn parity(&self)->bool; - //this is stupid but may work fine - fn reverse(&self)-><::UndirectedEdge as UndirectedEdge>::DirectedEdge{ - self.as_undirected().as_directed(!self.parity()) - } +pub trait DirectedEdge { + type UndirectedEdge: Copy + std::fmt::Debug + UndirectedEdge; + fn as_undirected(&self) -> Self::UndirectedEdge; + fn parity(&self) -> bool; + //this is stupid but may work fine + fn reverse(&self) -> <::UndirectedEdge as UndirectedEdge>::DirectedEdge { + self.as_undirected().as_directed(!self.parity()) + } } -#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)] +#[derive(Debug, Clone, Copy, Hash, id::Id, Eq, PartialEq)] pub struct MeshVertId(u32); -#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)] +#[derive(Debug, Clone, Copy, Hash, id::Id, Eq, PartialEq)] pub struct MeshFaceId(u32); -#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)] +#[derive(Debug, Clone, Copy, Hash, id::Id, Eq, PartialEq)] pub struct SubmeshVertId(u32); -#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)] +#[derive(Debug, Clone, Copy, Hash, id::Id, Eq, PartialEq)] pub struct SubmeshEdgeId(u32); /// DirectedEdgeId refers to an EdgeId when undirected. -#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)] +#[derive(Debug, Clone, Copy, Hash, id::Id, Eq, PartialEq)] pub struct SubmeshDirectedEdgeId(u32); -#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)] +#[derive(Debug, Clone, Copy, Hash, id::Id, Eq, PartialEq)] pub struct SubmeshFaceId(u32); -impl UndirectedEdge for SubmeshEdgeId{ - type DirectedEdge=SubmeshDirectedEdgeId; - fn as_directed(&self,parity:bool)->SubmeshDirectedEdgeId{ - SubmeshDirectedEdgeId(self.0|((parity as u32)<<(u32::BITS-1))) - } +impl UndirectedEdge for SubmeshEdgeId { + type DirectedEdge = SubmeshDirectedEdgeId; + fn as_directed(&self, parity: bool) -> SubmeshDirectedEdgeId { + SubmeshDirectedEdgeId(self.0 | ((parity as u32) << (u32::BITS - 1))) + } } -impl DirectedEdge for SubmeshDirectedEdgeId{ - type UndirectedEdge=SubmeshEdgeId; - fn as_undirected(&self)->SubmeshEdgeId{ - SubmeshEdgeId(self.0&!(1<<(u32::BITS-1))) - } - fn parity(&self)->bool{ - self.0&(1<<(u32::BITS-1))!=0 - } +impl DirectedEdge for SubmeshDirectedEdgeId { + type UndirectedEdge = SubmeshEdgeId; + fn as_undirected(&self) -> SubmeshEdgeId { + SubmeshEdgeId(self.0 & !(1 << (u32::BITS - 1))) + } + fn parity(&self) -> bool { + self.0 & (1 << (u32::BITS - 1)) != 0 + } } //Vertex <-> Edge <-> Face -> Collide #[derive(Debug)] -pub enum FEV{ - Face(M::Face), - Edge(::UndirectedEdge), - Vert(M::Vert), +pub enum FEV { + Face(M::Face), + Edge(::UndirectedEdge), + Vert(M::Vert), } //use Unit32 #[repr(C)] for map files -#[derive(Clone,Hash,Eq,PartialEq)] -struct Face{ - normal:Planar64Vec3, - dot:Planar64, +#[derive(Clone, Hash, Eq, PartialEq)] +struct Face { + normal: Planar64Vec3, + dot: Planar64, } struct Vert(Planar64Vec3); -pub trait MeshQuery{ - type Face:Clone; - type Edge:Clone+DirectedEdge; - type Vert:Clone; - // Vertex must be Planar64Vec3 because it represents an actual position - type Normal; - type Offset; - fn edge_n(&self,edge_id:::UndirectedEdge)->Planar64Vec3{ - let verts=self.edge_verts(edge_id); - self.vert(verts[1].clone())-self.vert(verts[0].clone()) - } - fn directed_edge_n(&self,directed_edge_id:Self::Edge)->Planar64Vec3{ - let verts=self.edge_verts(directed_edge_id.as_undirected()); - (self.vert(verts[1].clone())-self.vert(verts[0].clone()))*((directed_edge_id.parity() as i64)*2-1) - } - fn vert(&self,vert_id:Self::Vert)->Planar64Vec3; - fn face_nd(&self,face_id:Self::Face)->(Self::Normal,Self::Offset); - fn face_edges(&self,face_id:Self::Face)->Cow>; - fn edge_faces(&self,edge_id:::UndirectedEdge)->Cow<[Self::Face;2]>; - fn edge_verts(&self,edge_id:::UndirectedEdge)->Cow<[Self::Vert;2]>; - fn vert_edges(&self,vert_id:Self::Vert)->Cow>; - fn vert_faces(&self,vert_id:Self::Vert)->Cow>; +pub trait MeshQuery { + type Face: Clone; + type Edge: Clone + DirectedEdge; + type Vert: Clone; + // Vertex must be Planar64Vec3 because it represents an actual position + type Normal; + type Offset; + fn edge_n(&self, edge_id: ::UndirectedEdge) -> Planar64Vec3 { + let verts = self.edge_verts(edge_id); + self.vert(verts[1].clone()) - self.vert(verts[0].clone()) + } + fn directed_edge_n(&self, directed_edge_id: Self::Edge) -> Planar64Vec3 { + let verts = self.edge_verts(directed_edge_id.as_undirected()); + (self.vert(verts[1].clone()) - self.vert(verts[0].clone())) + * ((directed_edge_id.parity() as i64) * 2 - 1) + } + fn vert(&self, vert_id: Self::Vert) -> Planar64Vec3; + fn face_nd(&self, face_id: Self::Face) -> (Self::Normal, Self::Offset); + fn face_edges(&self, face_id: Self::Face) -> Cow>; + fn edge_faces( + &self, + edge_id: ::UndirectedEdge, + ) -> Cow<[Self::Face; 2]>; + fn edge_verts( + &self, + edge_id: ::UndirectedEdge, + ) -> Cow<[Self::Vert; 2]>; + fn vert_edges(&self, vert_id: Self::Vert) -> Cow>; + fn vert_faces(&self, vert_id: Self::Vert) -> Cow>; } -struct FaceRefs{ - edges:Vec, - //verts:Vec, +struct FaceRefs { + edges: Vec, + //verts:Vec, } -struct EdgeRefs{ - faces:[SubmeshFaceId;2],//left, right - verts:[SubmeshVertId;2],//bottom, top +struct EdgeRefs { + faces: [SubmeshFaceId; 2], //left, right + verts: [SubmeshVertId; 2], //bottom, top } -struct VertRefs{ - faces:Vec, - edges:Vec, +struct VertRefs { + faces: Vec, + edges: Vec, } -pub struct PhysicsMeshData{ - //this contains all real and virtual faces used in both the complete mesh and convex submeshes - //faces are sorted such that all faces that belong to the complete mesh appear first, and then - //all remaining faces are virtual to operate internal logic of the face crawler - //and cannot be part of a physics collision - //virtual faces are only used in convex submeshes. - faces:Vec,//MeshFaceId indexes this list - verts:Vec,//MeshVertId indexes this list +pub struct PhysicsMeshData { + //this contains all real and virtual faces used in both the complete mesh and convex submeshes + //faces are sorted such that all faces that belong to the complete mesh appear first, and then + //all remaining faces are virtual to operate internal logic of the face crawler + //and cannot be part of a physics collision + //virtual faces are only used in convex submeshes. + faces: Vec, //MeshFaceId indexes this list + verts: Vec, //MeshVertId indexes this list } -pub struct PhysicsMeshTopology{ - //mapping of local ids to PhysicsMeshData ids - faces:Vec,//SubmeshFaceId indexes this list - verts:Vec,//SubmeshVertId indexes this list - //all ids here are local to this object - face_topology:Vec, - edge_topology:Vec, - vert_topology:Vec, +pub struct PhysicsMeshTopology { + //mapping of local ids to PhysicsMeshData ids + faces: Vec, //SubmeshFaceId indexes this list + verts: Vec, //SubmeshVertId indexes this list + //all ids here are local to this object + face_topology: Vec, + edge_topology: Vec, + vert_topology: Vec, } -#[derive(Clone,Copy,Hash,id::Id,Eq,PartialEq)] +#[derive(Clone, Copy, Hash, id::Id, Eq, PartialEq)] pub struct PhysicsMeshId(u32); -impl Into for PhysicsMeshId{ - fn into(self)->MeshId{ - MeshId::new(self.0) - } +impl Into for PhysicsMeshId { + fn into(self) -> MeshId { + MeshId::new(self.0) + } } -impl From for PhysicsMeshId{ - fn from(value:MeshId)->Self{ - Self::new(value.get()) - } +impl From for PhysicsMeshId { + fn from(value: MeshId) -> Self { + Self::new(value.get()) + } } -#[derive(Debug,Default,Clone,Copy,Hash,id::Id,Eq,PartialEq)] +#[derive(Debug, Default, Clone, Copy, Hash, id::Id, Eq, PartialEq)] pub struct PhysicsSubmeshId(u32); -pub struct PhysicsMesh{ - data:PhysicsMeshData, - complete_mesh:PhysicsMeshTopology, - //Most objects in roblox maps are already convex, so the list length is 0 - //as soon as the mesh is divided into 2 submeshes, the list length jumps to 2. - //length 1 is unnecessary since the complete mesh would be a duplicate of the only submesh, but would still function properly - submeshes:Vec, +pub struct PhysicsMesh { + data: PhysicsMeshData, + complete_mesh: PhysicsMeshTopology, + //Most objects in roblox maps are already convex, so the list length is 0 + //as soon as the mesh is divided into 2 submeshes, the list length jumps to 2. + //length 1 is unnecessary since the complete mesh would be a duplicate of the only submesh, but would still function properly + submeshes: Vec, } -impl PhysicsMesh{ - pub fn unit_cube()->Self{ - //go go gadget debug print mesh - let data=PhysicsMeshData{ - faces:vec![ - Face{normal:vec3::raw_xyz( 4294967296, 0, 0),dot:Planar64::raw(4294967296)}, - Face{normal:vec3::raw_xyz( 0, 4294967296, 0),dot:Planar64::raw(4294967296)}, - Face{normal:vec3::raw_xyz( 0, 0, 4294967296),dot:Planar64::raw(4294967296)}, - Face{normal:vec3::raw_xyz(-4294967296, 0, 0),dot:Planar64::raw(4294967296)}, - Face{normal:vec3::raw_xyz( 0,-4294967296, 0),dot:Planar64::raw(4294967296)}, - Face{normal:vec3::raw_xyz( 0, 0,-4294967296),dot:Planar64::raw(4294967296)} - ], - verts:vec![ - Vert(vec3::raw_xyz( 4294967296,-4294967296,-4294967296)), - Vert(vec3::raw_xyz( 4294967296, 4294967296,-4294967296)), - Vert(vec3::raw_xyz( 4294967296, 4294967296, 4294967296)), - Vert(vec3::raw_xyz( 4294967296,-4294967296, 4294967296)), - Vert(vec3::raw_xyz(-4294967296, 4294967296,-4294967296)), - Vert(vec3::raw_xyz(-4294967296, 4294967296, 4294967296)), - Vert(vec3::raw_xyz(-4294967296,-4294967296, 4294967296)), - Vert(vec3::raw_xyz(-4294967296,-4294967296,-4294967296)) - ] - }; - let mesh_topology=PhysicsMeshTopology{ - faces:(0..data.faces.len() as u32).map(MeshFaceId::new).collect(), - verts:(0..data.verts.len() as u32).map(MeshVertId::new).collect(), - face_topology:vec![ - FaceRefs{edges:vec![SubmeshDirectedEdgeId((9223372036854775808u64-(1<<63)+(1<<31)) as u32),SubmeshDirectedEdgeId((9223372036854775809u64-(1<<63)+(1<<31)) as u32),SubmeshDirectedEdgeId((9223372036854775810u64-(1<<63)+(1<<31)) as u32),SubmeshDirectedEdgeId(3)]}, - FaceRefs{edges:vec![SubmeshDirectedEdgeId((9223372036854775812u64-(1<<63)+(1<<31)) as u32),SubmeshDirectedEdgeId((9223372036854775813u64-(1<<63)+(1<<31)) as u32),SubmeshDirectedEdgeId(6),SubmeshDirectedEdgeId(1)]}, - FaceRefs{edges:vec![SubmeshDirectedEdgeId(7),SubmeshDirectedEdgeId(2),SubmeshDirectedEdgeId((9223372036854775814u64-(1<<63)+(1<<31)) as u32),SubmeshDirectedEdgeId((9223372036854775816u64-(1<<63)+(1<<31)) as u32)]}, - FaceRefs{edges:vec![SubmeshDirectedEdgeId(8),SubmeshDirectedEdgeId(5),SubmeshDirectedEdgeId((9223372036854775817u64-(1<<63)+(1<<31)) as u32),SubmeshDirectedEdgeId(10)]}, - FaceRefs{edges:vec![SubmeshDirectedEdgeId((9223372036854775815u64-(1<<63)+(1<<31)) as u32),SubmeshDirectedEdgeId((9223372036854775818u64-(1<<63)+(1<<31)) as u32),SubmeshDirectedEdgeId(11),SubmeshDirectedEdgeId((9223372036854775811u64-(1<<63)+(1<<31)) as u32)]}, - FaceRefs{edges:vec![SubmeshDirectedEdgeId(4),SubmeshDirectedEdgeId(0),SubmeshDirectedEdgeId((9223372036854775819u64-(1<<63)+(1<<31)) as u32),SubmeshDirectedEdgeId(9)]} - ], - edge_topology:vec![ - EdgeRefs{faces:[SubmeshFaceId(0),SubmeshFaceId(5)],verts:[SubmeshVertId(0),SubmeshVertId(1)]}, - EdgeRefs{faces:[SubmeshFaceId(0),SubmeshFaceId(1)],verts:[SubmeshVertId(1),SubmeshVertId(2)]}, - EdgeRefs{faces:[SubmeshFaceId(0),SubmeshFaceId(2)],verts:[SubmeshVertId(2),SubmeshVertId(3)]}, - EdgeRefs{faces:[SubmeshFaceId(4),SubmeshFaceId(0)],verts:[SubmeshVertId(0),SubmeshVertId(3)]}, - EdgeRefs{faces:[SubmeshFaceId(1),SubmeshFaceId(5)],verts:[SubmeshVertId(1),SubmeshVertId(4)]}, - EdgeRefs{faces:[SubmeshFaceId(1),SubmeshFaceId(3)],verts:[SubmeshVertId(4),SubmeshVertId(5)]}, - EdgeRefs{faces:[SubmeshFaceId(2),SubmeshFaceId(1)],verts:[SubmeshVertId(2),SubmeshVertId(5)]}, - EdgeRefs{faces:[SubmeshFaceId(4),SubmeshFaceId(2)],verts:[SubmeshVertId(3),SubmeshVertId(6)]}, - EdgeRefs{faces:[SubmeshFaceId(2),SubmeshFaceId(3)],verts:[SubmeshVertId(5),SubmeshVertId(6)]}, - EdgeRefs{faces:[SubmeshFaceId(3),SubmeshFaceId(5)],verts:[SubmeshVertId(4),SubmeshVertId(7)]}, - EdgeRefs{faces:[SubmeshFaceId(4),SubmeshFaceId(3)],verts:[SubmeshVertId(6),SubmeshVertId(7)]}, - EdgeRefs{faces:[SubmeshFaceId(5),SubmeshFaceId(4)],verts:[SubmeshVertId(0),SubmeshVertId(7)]} - ], - vert_topology:vec![ - VertRefs{faces:vec![SubmeshFaceId(0),SubmeshFaceId(4),SubmeshFaceId(5)],edges:vec![SubmeshDirectedEdgeId((9223372036854775811u64-(1<<63)+(1<<31)) as u32),SubmeshDirectedEdgeId((9223372036854775819u64-(1<<63)+(1<<31)) as u32),SubmeshDirectedEdgeId((9223372036854775808u64-(1<<63)+(1<<31)) as u32)]}, - VertRefs{faces:vec![SubmeshFaceId(0),SubmeshFaceId(5),SubmeshFaceId(1)],edges:vec![SubmeshDirectedEdgeId((9223372036854775812u64-(1<<63)+(1<<31)) as u32),SubmeshDirectedEdgeId(0),SubmeshDirectedEdgeId((9223372036854775809u64-(1<<63)+(1<<31)) as u32)]}, - VertRefs{faces:vec![SubmeshFaceId(0),SubmeshFaceId(2),SubmeshFaceId(1)],edges:vec![SubmeshDirectedEdgeId(1),SubmeshDirectedEdgeId((9223372036854775810u64-(1<<63)+(1<<31)) as u32),SubmeshDirectedEdgeId((9223372036854775814u64-(1<<63)+(1<<31)) as u32)]}, - VertRefs{faces:vec![SubmeshFaceId(0),SubmeshFaceId(2),SubmeshFaceId(4)],edges:vec![SubmeshDirectedEdgeId(2),SubmeshDirectedEdgeId(3),SubmeshDirectedEdgeId((9223372036854775815u64-(1<<63)+(1<<31)) as u32)]}, - VertRefs{faces:vec![SubmeshFaceId(3),SubmeshFaceId(5),SubmeshFaceId(1)],edges:vec![SubmeshDirectedEdgeId(4),SubmeshDirectedEdgeId((9223372036854775817u64-(1<<63)+(1<<31)) as u32),SubmeshDirectedEdgeId((9223372036854775813u64-(1<<63)+(1<<31)) as u32)]}, - VertRefs{faces:vec![SubmeshFaceId(2),SubmeshFaceId(3),SubmeshFaceId(1)],edges:vec![SubmeshDirectedEdgeId(5),SubmeshDirectedEdgeId(6),SubmeshDirectedEdgeId((9223372036854775816u64-(1<<63)+(1<<31)) as u32)]}, - VertRefs{faces:vec![SubmeshFaceId(2),SubmeshFaceId(3),SubmeshFaceId(4)],edges:vec![SubmeshDirectedEdgeId(7),SubmeshDirectedEdgeId(8),SubmeshDirectedEdgeId((9223372036854775818u64-(1<<63)+(1<<31)) as u32)]}, - VertRefs{faces:vec![SubmeshFaceId(4),SubmeshFaceId(3),SubmeshFaceId(5)],edges:vec![SubmeshDirectedEdgeId(10),SubmeshDirectedEdgeId(11),SubmeshDirectedEdgeId(9)]} - ] - }; - Self{ - data, - complete_mesh:mesh_topology, - submeshes:Vec::new(), - } - } - pub fn unit_cylinder()->Self{ - Self::unit_cube() - } - #[inline] - pub const fn complete_mesh(&self)->&PhysicsMeshTopology{ - &self.complete_mesh - } - #[inline] - pub const fn complete_mesh_view(&self)->PhysicsMeshView{ - PhysicsMeshView{ - data:&self.data, - topology:self.complete_mesh(), - } - } - #[inline] - pub fn submeshes(&self)->&[PhysicsMeshTopology]{ - //the complete mesh is already a convex mesh when len()==0, len()==1 is invalid but will still work - if self.submeshes.len()==0{ - std::slice::from_ref(&self.complete_mesh) - }else{ - &self.submeshes.as_slice() - } - } - #[inline] - pub fn submesh_view(&self,submesh_id:PhysicsSubmeshId)->PhysicsMeshView{ - PhysicsMeshView{ - data:&self.data, - topology:&self.submeshes()[submesh_id.get() as usize], - } - } - pub fn submesh_views(&self)->impl Iterator{ - self.submeshes().iter().map(|topology|PhysicsMeshView{ - data:&self.data, - topology, - }) - } +impl PhysicsMesh { + pub fn unit_cube() -> Self { + //go go gadget debug print mesh + let data = PhysicsMeshData { + faces: vec![ + Face { + normal: vec3::raw_xyz(4294967296, 0, 0), + dot: Planar64::raw(4294967296), + }, + Face { + normal: vec3::raw_xyz(0, 4294967296, 0), + dot: Planar64::raw(4294967296), + }, + Face { + normal: vec3::raw_xyz(0, 0, 4294967296), + dot: Planar64::raw(4294967296), + }, + Face { + normal: vec3::raw_xyz(-4294967296, 0, 0), + dot: Planar64::raw(4294967296), + }, + Face { + normal: vec3::raw_xyz(0, -4294967296, 0), + dot: Planar64::raw(4294967296), + }, + Face { + normal: vec3::raw_xyz(0, 0, -4294967296), + dot: Planar64::raw(4294967296), + }, + ], + verts: vec![ + Vert(vec3::raw_xyz(4294967296, -4294967296, -4294967296)), + Vert(vec3::raw_xyz(4294967296, 4294967296, -4294967296)), + Vert(vec3::raw_xyz(4294967296, 4294967296, 4294967296)), + Vert(vec3::raw_xyz(4294967296, -4294967296, 4294967296)), + Vert(vec3::raw_xyz(-4294967296, 4294967296, -4294967296)), + Vert(vec3::raw_xyz(-4294967296, 4294967296, 4294967296)), + Vert(vec3::raw_xyz(-4294967296, -4294967296, 4294967296)), + Vert(vec3::raw_xyz(-4294967296, -4294967296, -4294967296)), + ], + }; + let mesh_topology = PhysicsMeshTopology { + faces: (0..data.faces.len() as u32).map(MeshFaceId::new).collect(), + verts: (0..data.verts.len() as u32).map(MeshVertId::new).collect(), + face_topology: vec![ + FaceRefs { + edges: vec![ + SubmeshDirectedEdgeId( + (9223372036854775808u64 - (1 << 63) + (1 << 31)) as u32, + ), + SubmeshDirectedEdgeId( + (9223372036854775809u64 - (1 << 63) + (1 << 31)) as u32, + ), + SubmeshDirectedEdgeId( + (9223372036854775810u64 - (1 << 63) + (1 << 31)) as u32, + ), + SubmeshDirectedEdgeId(3), + ], + }, + FaceRefs { + edges: vec![ + SubmeshDirectedEdgeId( + (9223372036854775812u64 - (1 << 63) + (1 << 31)) as u32, + ), + SubmeshDirectedEdgeId( + (9223372036854775813u64 - (1 << 63) + (1 << 31)) as u32, + ), + SubmeshDirectedEdgeId(6), + SubmeshDirectedEdgeId(1), + ], + }, + FaceRefs { + edges: vec![ + SubmeshDirectedEdgeId(7), + SubmeshDirectedEdgeId(2), + SubmeshDirectedEdgeId( + (9223372036854775814u64 - (1 << 63) + (1 << 31)) as u32, + ), + SubmeshDirectedEdgeId( + (9223372036854775816u64 - (1 << 63) + (1 << 31)) as u32, + ), + ], + }, + FaceRefs { + edges: vec![ + SubmeshDirectedEdgeId(8), + SubmeshDirectedEdgeId(5), + SubmeshDirectedEdgeId( + (9223372036854775817u64 - (1 << 63) + (1 << 31)) as u32, + ), + SubmeshDirectedEdgeId(10), + ], + }, + FaceRefs { + edges: vec![ + SubmeshDirectedEdgeId( + (9223372036854775815u64 - (1 << 63) + (1 << 31)) as u32, + ), + SubmeshDirectedEdgeId( + (9223372036854775818u64 - (1 << 63) + (1 << 31)) as u32, + ), + SubmeshDirectedEdgeId(11), + SubmeshDirectedEdgeId( + (9223372036854775811u64 - (1 << 63) + (1 << 31)) as u32, + ), + ], + }, + FaceRefs { + edges: vec![ + SubmeshDirectedEdgeId(4), + SubmeshDirectedEdgeId(0), + SubmeshDirectedEdgeId( + (9223372036854775819u64 - (1 << 63) + (1 << 31)) as u32, + ), + SubmeshDirectedEdgeId(9), + ], + }, + ], + edge_topology: vec![ + EdgeRefs { + faces: [SubmeshFaceId(0), SubmeshFaceId(5)], + verts: [SubmeshVertId(0), SubmeshVertId(1)], + }, + EdgeRefs { + faces: [SubmeshFaceId(0), SubmeshFaceId(1)], + verts: [SubmeshVertId(1), SubmeshVertId(2)], + }, + EdgeRefs { + faces: [SubmeshFaceId(0), SubmeshFaceId(2)], + verts: [SubmeshVertId(2), SubmeshVertId(3)], + }, + EdgeRefs { + faces: [SubmeshFaceId(4), SubmeshFaceId(0)], + verts: [SubmeshVertId(0), SubmeshVertId(3)], + }, + EdgeRefs { + faces: [SubmeshFaceId(1), SubmeshFaceId(5)], + verts: [SubmeshVertId(1), SubmeshVertId(4)], + }, + EdgeRefs { + faces: [SubmeshFaceId(1), SubmeshFaceId(3)], + verts: [SubmeshVertId(4), SubmeshVertId(5)], + }, + EdgeRefs { + faces: [SubmeshFaceId(2), SubmeshFaceId(1)], + verts: [SubmeshVertId(2), SubmeshVertId(5)], + }, + EdgeRefs { + faces: [SubmeshFaceId(4), SubmeshFaceId(2)], + verts: [SubmeshVertId(3), SubmeshVertId(6)], + }, + EdgeRefs { + faces: [SubmeshFaceId(2), SubmeshFaceId(3)], + verts: [SubmeshVertId(5), SubmeshVertId(6)], + }, + EdgeRefs { + faces: [SubmeshFaceId(3), SubmeshFaceId(5)], + verts: [SubmeshVertId(4), SubmeshVertId(7)], + }, + EdgeRefs { + faces: [SubmeshFaceId(4), SubmeshFaceId(3)], + verts: [SubmeshVertId(6), SubmeshVertId(7)], + }, + EdgeRefs { + faces: [SubmeshFaceId(5), SubmeshFaceId(4)], + verts: [SubmeshVertId(0), SubmeshVertId(7)], + }, + ], + vert_topology: vec![ + VertRefs { + faces: vec![SubmeshFaceId(0), SubmeshFaceId(4), SubmeshFaceId(5)], + edges: vec![ + SubmeshDirectedEdgeId( + (9223372036854775811u64 - (1 << 63) + (1 << 31)) as u32, + ), + SubmeshDirectedEdgeId( + (9223372036854775819u64 - (1 << 63) + (1 << 31)) as u32, + ), + SubmeshDirectedEdgeId( + (9223372036854775808u64 - (1 << 63) + (1 << 31)) as u32, + ), + ], + }, + VertRefs { + faces: vec![SubmeshFaceId(0), SubmeshFaceId(5), SubmeshFaceId(1)], + edges: vec![ + SubmeshDirectedEdgeId( + (9223372036854775812u64 - (1 << 63) + (1 << 31)) as u32, + ), + SubmeshDirectedEdgeId(0), + SubmeshDirectedEdgeId( + (9223372036854775809u64 - (1 << 63) + (1 << 31)) as u32, + ), + ], + }, + VertRefs { + faces: vec![SubmeshFaceId(0), SubmeshFaceId(2), SubmeshFaceId(1)], + edges: vec![ + SubmeshDirectedEdgeId(1), + SubmeshDirectedEdgeId( + (9223372036854775810u64 - (1 << 63) + (1 << 31)) as u32, + ), + SubmeshDirectedEdgeId( + (9223372036854775814u64 - (1 << 63) + (1 << 31)) as u32, + ), + ], + }, + VertRefs { + faces: vec![SubmeshFaceId(0), SubmeshFaceId(2), SubmeshFaceId(4)], + edges: vec![ + SubmeshDirectedEdgeId(2), + SubmeshDirectedEdgeId(3), + SubmeshDirectedEdgeId( + (9223372036854775815u64 - (1 << 63) + (1 << 31)) as u32, + ), + ], + }, + VertRefs { + faces: vec![SubmeshFaceId(3), SubmeshFaceId(5), SubmeshFaceId(1)], + edges: vec![ + SubmeshDirectedEdgeId(4), + SubmeshDirectedEdgeId( + (9223372036854775817u64 - (1 << 63) + (1 << 31)) as u32, + ), + SubmeshDirectedEdgeId( + (9223372036854775813u64 - (1 << 63) + (1 << 31)) as u32, + ), + ], + }, + VertRefs { + faces: vec![SubmeshFaceId(2), SubmeshFaceId(3), SubmeshFaceId(1)], + edges: vec![ + SubmeshDirectedEdgeId(5), + SubmeshDirectedEdgeId(6), + SubmeshDirectedEdgeId( + (9223372036854775816u64 - (1 << 63) + (1 << 31)) as u32, + ), + ], + }, + VertRefs { + faces: vec![SubmeshFaceId(2), SubmeshFaceId(3), SubmeshFaceId(4)], + edges: vec![ + SubmeshDirectedEdgeId(7), + SubmeshDirectedEdgeId(8), + SubmeshDirectedEdgeId( + (9223372036854775818u64 - (1 << 63) + (1 << 31)) as u32, + ), + ], + }, + VertRefs { + faces: vec![SubmeshFaceId(4), SubmeshFaceId(3), SubmeshFaceId(5)], + edges: vec![ + SubmeshDirectedEdgeId(10), + SubmeshDirectedEdgeId(11), + SubmeshDirectedEdgeId(9), + ], + }, + ], + }; + Self { + data, + complete_mesh: mesh_topology, + submeshes: Vec::new(), + } + } + pub fn unit_cylinder() -> Self { + Self::unit_cube() + } + #[inline] + pub const fn complete_mesh(&self) -> &PhysicsMeshTopology { + &self.complete_mesh + } + #[inline] + pub const fn complete_mesh_view(&self) -> PhysicsMeshView { + PhysicsMeshView { + data: &self.data, + topology: self.complete_mesh(), + } + } + #[inline] + pub fn submeshes(&self) -> &[PhysicsMeshTopology] { + //the complete mesh is already a convex mesh when len()==0, len()==1 is invalid but will still work + if self.submeshes.len() == 0 { + std::slice::from_ref(&self.complete_mesh) + } else { + &self.submeshes.as_slice() + } + } + #[inline] + pub fn submesh_view(&self, submesh_id: PhysicsSubmeshId) -> PhysicsMeshView { + PhysicsMeshView { + data: &self.data, + topology: &self.submeshes()[submesh_id.get() as usize], + } + } + pub fn submesh_views(&self) -> impl Iterator { + self.submeshes().iter().map(|topology| PhysicsMeshView { + data: &self.data, + topology, + }) + } } //mesh builder code -#[derive(Default,Clone)] -struct VertRefGuy{ - edges:HashSet, - faces:HashSet, +#[derive(Default, Clone)] +struct VertRefGuy { + edges: HashSet, + faces: HashSet, } -#[derive(Clone,Hash,Eq,PartialEq)] -struct EdgeRefVerts([SubmeshVertId;2]); -impl EdgeRefVerts{ - const fn new(v0:SubmeshVertId,v1:SubmeshVertId)->(Self,bool){ - (if v0.0 (Self, bool) { + ( + if v0.0 < v1.0 { + Self([v0, v1]) + } else { + Self([v1, v0]) + }, + v0.0 < v1.0, + ) + } } -struct EdgeRefFaces([SubmeshFaceId;2]); -impl EdgeRefFaces{ - const fn new()->Self{ - Self([SubmeshFaceId(0);2]) - } - fn push(&mut self,i:usize,face_id:SubmeshFaceId){ - self.0[i]=face_id; - } +struct EdgeRefFaces([SubmeshFaceId; 2]); +impl EdgeRefFaces { + const fn new() -> Self { + Self([SubmeshFaceId(0); 2]) + } + fn push(&mut self, i: usize, face_id: SubmeshFaceId) { + self.0[i] = face_id; + } } struct FaceRefEdges(Vec); #[derive(Default)] -struct EdgePool{ - edge_guys:Vec<(EdgeRefVerts,EdgeRefFaces)>, - edge_id_from_guy:HashMap, +struct EdgePool { + edge_guys: Vec<(EdgeRefVerts, EdgeRefFaces)>, + edge_id_from_guy: HashMap, } -impl EdgePool{ - fn push(&mut self,edge_ref_verts:EdgeRefVerts)->(&mut EdgeRefFaces,SubmeshEdgeId){ - let edge_id=if let Some(&edge_id)=self.edge_id_from_guy.get(&edge_ref_verts){ - edge_id - }else{ - let edge_id=SubmeshEdgeId::new(self.edge_guys.len() as u32); - self.edge_guys.push((edge_ref_verts.clone(),EdgeRefFaces::new())); - self.edge_id_from_guy.insert(edge_ref_verts,edge_id); - edge_id - }; - (&mut unsafe{self.edge_guys.get_unchecked_mut(edge_id.get() as usize)}.1,edge_id) - } +impl EdgePool { + fn push(&mut self, edge_ref_verts: EdgeRefVerts) -> (&mut EdgeRefFaces, SubmeshEdgeId) { + let edge_id = if let Some(&edge_id) = self.edge_id_from_guy.get(&edge_ref_verts) { + edge_id + } else { + let edge_id = SubmeshEdgeId::new(self.edge_guys.len() as u32); + self.edge_guys + .push((edge_ref_verts.clone(), EdgeRefFaces::new())); + self.edge_id_from_guy.insert(edge_ref_verts, edge_id); + edge_id + }; + ( + &mut unsafe { self.edge_guys.get_unchecked_mut(edge_id.get() as usize) }.1, + edge_id, + ) + } } #[derive(Debug)] -pub enum PhysicsMeshError{ - ZeroVertices, - NoPhysicsGroups, +pub enum PhysicsMeshError { + ZeroVertices, + NoPhysicsGroups, } -impl std::fmt::Display for PhysicsMeshError{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f,"{self:?}") - } +impl std::fmt::Display for PhysicsMeshError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{self:?}") + } } -impl std::error::Error for PhysicsMeshError{} +impl std::error::Error for PhysicsMeshError {} -impl TryFrom<&model::Mesh> for PhysicsMesh{ - type Error=PhysicsMeshError; - fn try_from(mesh:&model::Mesh)->Result{ - if mesh.unique_pos.len()==0{ - return Err(PhysicsMeshError::ZeroVertices); - } - let verts=mesh.unique_pos.iter().copied().map(Vert).collect(); - //TODO: fix submeshes - //flat map mesh.physics_groups[$1].groups.polys()[$2] as face_id - //lower face_id points to upper face_id - //the same face is not allowed to be in multiple polygon groups - let mut faces=Vec::new(); - let mut face_id_from_face=HashMap::new(); - let mut mesh_topologies:Vec=mesh.physics_groups.iter().map(|physics_group|{ - //construct submesh - let mut submesh_faces=Vec::new();//these contain a map from submeshId->meshId - let mut submesh_verts=Vec::new(); - let mut submesh_vert_id_from_mesh_vert_id=HashMap::::new(); - //lazy closure - let mut get_submesh_vert_id=|vert_id:MeshVertId|{ - if let Some(&submesh_vert_id)=submesh_vert_id_from_mesh_vert_id.get(&vert_id){ - submesh_vert_id - }else{ - let submesh_vert_id=SubmeshVertId::new(submesh_verts.len() as u32); - submesh_verts.push(vert_id); - submesh_vert_id_from_mesh_vert_id.insert(vert_id,submesh_vert_id); - submesh_vert_id - } - }; - let mut edge_pool=EdgePool::default(); - let mut vert_ref_guys=vec![VertRefGuy::default();mesh.unique_pos.len()]; - let mut face_ref_guys=Vec::new(); - for polygon_group_id in &physics_group.groups{ - let polygon_group=&mesh.polygon_groups[polygon_group_id.get() as usize]; - for poly_vertices in polygon_group.polys(){ - let submesh_face_id=SubmeshFaceId::new(submesh_faces.len() as u32); - //one face per poly - let mut normal=Vector3::new([Fixed::ZERO,Fixed::ZERO,Fixed::ZERO]); - let len=poly_vertices.len(); - let face_edges=poly_vertices.into_iter().enumerate().map(|(i,vert_id)|{ - let vert0_id=MeshVertId::new(mesh.unique_vertices[vert_id.get() as usize].pos.get() as u32); - let vert1_id=MeshVertId::new(mesh.unique_vertices[poly_vertices[(i+1)%len].get() as usize].pos.get() as u32); - //index submesh verts - let submesh_vert0_id=get_submesh_vert_id(vert0_id); - let submesh_vert1_id=get_submesh_vert_id(vert1_id); - //https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal (Newell's Method) - let v0=mesh.unique_pos[vert0_id.get() as usize]; - let v1=mesh.unique_pos[vert1_id.get() as usize]; - normal+=Vector3::new([ - (v0.y-v1.y)*(v0.z+v1.z), - (v0.z-v1.z)*(v0.x+v1.x), - (v0.x-v1.x)*(v0.y+v1.y), - ]); - //get/create edge and push face into it - let (edge_ref_verts,is_sorted)=EdgeRefVerts::new(submesh_vert0_id,submesh_vert1_id); - let (edge_ref_faces,edge_id)=edge_pool.push(edge_ref_verts); - //polygon vertices as assumed to be listed clockwise - //populate the edge face on the left or right depending on how the edge vertices got sorted - edge_ref_faces.push(!is_sorted as usize,submesh_face_id); - //index edges & face into vertices - { - let vert_ref_guy=unsafe{vert_ref_guys.get_unchecked_mut(submesh_vert0_id.get() as usize)}; - vert_ref_guy.edges.insert(edge_id.as_directed(is_sorted)); - vert_ref_guy.faces.insert(submesh_face_id); - unsafe{vert_ref_guys.get_unchecked_mut(submesh_vert1_id.get() as usize)}.edges.insert(edge_id.as_directed(!is_sorted)); - } - //return directed_edge_id - edge_id.as_directed(is_sorted) - }).collect(); - let mut dot=Fixed::ZERO; - // find the average dot - for &v in poly_vertices{ - dot+=normal.dot(mesh.unique_pos[mesh.unique_vertices[v.get() as usize].pos.get() as usize]); - } - //assume face hash is stable, and there are no flush faces... - let face=Face{ - normal:(normal/len as i64).divide().fix_1(), - dot:(dot/(len*len) as i64).fix_1(), - }; - let face_id=match face_id_from_face.get(&face){ - Some(&face_id)=>face_id, - None=>{ - let face_id=MeshFaceId::new(faces.len() as u32); - face_id_from_face.insert(face.clone(),face_id); - faces.push(face); - face_id - } - }; - submesh_faces.push(face_id); - face_ref_guys.push(FaceRefEdges(face_edges)); - } - } - PhysicsMeshTopology{ - faces:submesh_faces, - verts:submesh_verts, - face_topology:face_ref_guys.into_iter().map(|face_ref_guy|{ - FaceRefs{edges:face_ref_guy.0} - }).collect(), - edge_topology:edge_pool.edge_guys.into_iter().map(|(edge_ref_verts,edge_ref_faces)| - EdgeRefs{faces:edge_ref_faces.0,verts:edge_ref_verts.0} - ).collect(), - vert_topology:vert_ref_guys.into_iter().map(|vert_ref_guy| - VertRefs{ - edges:vert_ref_guy.edges.into_iter().collect(), - faces:vert_ref_guy.faces.into_iter().collect(), - } - ).collect(), - } - }).collect(); - Ok(Self{ - data:PhysicsMeshData{ - faces, - verts, - }, - complete_mesh:mesh_topologies.pop().ok_or(PhysicsMeshError::NoPhysicsGroups)?, - submeshes:mesh_topologies, - }) - } +impl TryFrom<&model::Mesh> for PhysicsMesh { + type Error = PhysicsMeshError; + fn try_from(mesh: &model::Mesh) -> Result { + if mesh.unique_pos.len() == 0 { + return Err(PhysicsMeshError::ZeroVertices); + } + let verts = mesh.unique_pos.iter().copied().map(Vert).collect(); + //TODO: fix submeshes + //flat map mesh.physics_groups[$1].groups.polys()[$2] as face_id + //lower face_id points to upper face_id + //the same face is not allowed to be in multiple polygon groups + let mut faces = Vec::new(); + let mut face_id_from_face = HashMap::new(); + let mut mesh_topologies: Vec = mesh + .physics_groups + .iter() + .map(|physics_group| { + //construct submesh + let mut submesh_faces = Vec::new(); //these contain a map from submeshId->meshId + let mut submesh_verts = Vec::new(); + let mut submesh_vert_id_from_mesh_vert_id = + HashMap::::new(); + //lazy closure + let mut get_submesh_vert_id = |vert_id: MeshVertId| { + if let Some(&submesh_vert_id) = submesh_vert_id_from_mesh_vert_id.get(&vert_id) + { + submesh_vert_id + } else { + let submesh_vert_id = SubmeshVertId::new(submesh_verts.len() as u32); + submesh_verts.push(vert_id); + submesh_vert_id_from_mesh_vert_id.insert(vert_id, submesh_vert_id); + submesh_vert_id + } + }; + let mut edge_pool = EdgePool::default(); + let mut vert_ref_guys = vec![VertRefGuy::default(); mesh.unique_pos.len()]; + let mut face_ref_guys = Vec::new(); + for polygon_group_id in &physics_group.groups { + let polygon_group = &mesh.polygon_groups[polygon_group_id.get() as usize]; + for poly_vertices in polygon_group.polys() { + let submesh_face_id = SubmeshFaceId::new(submesh_faces.len() as u32); + //one face per poly + let mut normal = Vector3::new([Fixed::ZERO, Fixed::ZERO, Fixed::ZERO]); + let len = poly_vertices.len(); + let face_edges = poly_vertices + .into_iter() + .enumerate() + .map(|(i, vert_id)| { + let vert0_id = MeshVertId::new( + mesh.unique_vertices[vert_id.get() as usize].pos.get() as u32, + ); + let vert1_id = MeshVertId::new( + mesh.unique_vertices + [poly_vertices[(i + 1) % len].get() as usize] + .pos + .get() as u32, + ); + //index submesh verts + let submesh_vert0_id = get_submesh_vert_id(vert0_id); + let submesh_vert1_id = get_submesh_vert_id(vert1_id); + //https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal (Newell's Method) + let v0 = mesh.unique_pos[vert0_id.get() as usize]; + let v1 = mesh.unique_pos[vert1_id.get() as usize]; + normal += Vector3::new([ + (v0.y - v1.y) * (v0.z + v1.z), + (v0.z - v1.z) * (v0.x + v1.x), + (v0.x - v1.x) * (v0.y + v1.y), + ]); + //get/create edge and push face into it + let (edge_ref_verts, is_sorted) = + EdgeRefVerts::new(submesh_vert0_id, submesh_vert1_id); + let (edge_ref_faces, edge_id) = edge_pool.push(edge_ref_verts); + //polygon vertices as assumed to be listed clockwise + //populate the edge face on the left or right depending on how the edge vertices got sorted + edge_ref_faces.push(!is_sorted as usize, submesh_face_id); + //index edges & face into vertices + { + let vert_ref_guy = unsafe { + vert_ref_guys + .get_unchecked_mut(submesh_vert0_id.get() as usize) + }; + vert_ref_guy.edges.insert(edge_id.as_directed(is_sorted)); + vert_ref_guy.faces.insert(submesh_face_id); + unsafe { + vert_ref_guys + .get_unchecked_mut(submesh_vert1_id.get() as usize) + } + .edges + .insert(edge_id.as_directed(!is_sorted)); + } + //return directed_edge_id + edge_id.as_directed(is_sorted) + }) + .collect(); + let mut dot = Fixed::ZERO; + // find the average dot + for &v in poly_vertices { + dot += normal.dot( + mesh.unique_pos + [mesh.unique_vertices[v.get() as usize].pos.get() as usize], + ); + } + //assume face hash is stable, and there are no flush faces... + let face = Face { + normal: (normal / len as i64).divide().fix_1(), + dot: (dot / (len * len) as i64).fix_1(), + }; + let face_id = match face_id_from_face.get(&face) { + Some(&face_id) => face_id, + None => { + let face_id = MeshFaceId::new(faces.len() as u32); + face_id_from_face.insert(face.clone(), face_id); + faces.push(face); + face_id + } + }; + submesh_faces.push(face_id); + face_ref_guys.push(FaceRefEdges(face_edges)); + } + } + PhysicsMeshTopology { + faces: submesh_faces, + verts: submesh_verts, + face_topology: face_ref_guys + .into_iter() + .map(|face_ref_guy| FaceRefs { + edges: face_ref_guy.0, + }) + .collect(), + edge_topology: edge_pool + .edge_guys + .into_iter() + .map(|(edge_ref_verts, edge_ref_faces)| EdgeRefs { + faces: edge_ref_faces.0, + verts: edge_ref_verts.0, + }) + .collect(), + vert_topology: vert_ref_guys + .into_iter() + .map(|vert_ref_guy| VertRefs { + edges: vert_ref_guy.edges.into_iter().collect(), + faces: vert_ref_guy.faces.into_iter().collect(), + }) + .collect(), + } + }) + .collect(); + Ok(Self { + data: PhysicsMeshData { faces, verts }, + complete_mesh: mesh_topologies + .pop() + .ok_or(PhysicsMeshError::NoPhysicsGroups)?, + submeshes: mesh_topologies, + }) + } } -pub struct PhysicsMeshView<'a>{ - data:&'a PhysicsMeshData, - topology:&'a PhysicsMeshTopology, +pub struct PhysicsMeshView<'a> { + data: &'a PhysicsMeshData, + topology: &'a PhysicsMeshTopology, } -impl MeshQuery for PhysicsMeshView<'_>{ - type Face=SubmeshFaceId; - type Edge=SubmeshDirectedEdgeId; - type Vert=SubmeshVertId; - type Normal=Planar64Vec3; - type Offset=Planar64; - fn face_nd(&self,face_id:SubmeshFaceId)->(Planar64Vec3,Planar64){ - let face_idx=self.topology.faces[face_id.get() as usize].get() as usize; - (self.data.faces[face_idx].normal,self.data.faces[face_idx].dot) - } - //ideally I never calculate the vertex position, but I have to for the graphical meshes... - fn vert(&self,vert_id:SubmeshVertId)->Planar64Vec3{ - let vert_idx=self.topology.verts[vert_id.get() as usize].get() as usize; - self.data.verts[vert_idx].0 - } - fn face_edges(&self,face_id:SubmeshFaceId)->Cow>{ - Cow::Borrowed(&self.topology.face_topology[face_id.get() as usize].edges) - } - fn edge_faces(&self,edge_id:SubmeshEdgeId)->Cow<[SubmeshFaceId;2]>{ - Cow::Borrowed(&self.topology.edge_topology[edge_id.get() as usize].faces) - } - fn edge_verts(&self,edge_id:SubmeshEdgeId)->Cow<[SubmeshVertId;2]>{ - Cow::Borrowed(&self.topology.edge_topology[edge_id.get() as usize].verts) - } - fn vert_edges(&self,vert_id:SubmeshVertId)->Cow>{ - Cow::Borrowed(&self.topology.vert_topology[vert_id.get() as usize].edges) - } - fn vert_faces(&self,vert_id:SubmeshVertId)->Cow>{ - Cow::Borrowed(&self.topology.vert_topology[vert_id.get() as usize].faces) - } +impl MeshQuery for PhysicsMeshView<'_> { + type Face = SubmeshFaceId; + type Edge = SubmeshDirectedEdgeId; + type Vert = SubmeshVertId; + type Normal = Planar64Vec3; + type Offset = Planar64; + fn face_nd(&self, face_id: SubmeshFaceId) -> (Planar64Vec3, Planar64) { + let face_idx = self.topology.faces[face_id.get() as usize].get() as usize; + ( + self.data.faces[face_idx].normal, + self.data.faces[face_idx].dot, + ) + } + //ideally I never calculate the vertex position, but I have to for the graphical meshes... + fn vert(&self, vert_id: SubmeshVertId) -> Planar64Vec3 { + let vert_idx = self.topology.verts[vert_id.get() as usize].get() as usize; + self.data.verts[vert_idx].0 + } + fn face_edges(&self, face_id: SubmeshFaceId) -> Cow> { + Cow::Borrowed(&self.topology.face_topology[face_id.get() as usize].edges) + } + fn edge_faces(&self, edge_id: SubmeshEdgeId) -> Cow<[SubmeshFaceId; 2]> { + Cow::Borrowed(&self.topology.edge_topology[edge_id.get() as usize].faces) + } + fn edge_verts(&self, edge_id: SubmeshEdgeId) -> Cow<[SubmeshVertId; 2]> { + Cow::Borrowed(&self.topology.edge_topology[edge_id.get() as usize].verts) + } + fn vert_edges(&self, vert_id: SubmeshVertId) -> Cow> { + Cow::Borrowed(&self.topology.vert_topology[vert_id.get() as usize].edges) + } + fn vert_faces(&self, vert_id: SubmeshVertId) -> Cow> { + Cow::Borrowed(&self.topology.vert_topology[vert_id.get() as usize].faces) + } } -pub struct PhysicsMeshTransform{ - pub vertex:integer::Planar64Affine3, - pub normal:integer::mat3::Matrix3>, - pub det:Fixed<3,96>, +pub struct PhysicsMeshTransform { + pub vertex: integer::Planar64Affine3, + pub normal: integer::mat3::Matrix3>, + pub det: Fixed<3, 96>, } -impl PhysicsMeshTransform{ - pub fn new(transform:integer::Planar64Affine3)->Self{ - Self{ - normal:transform.matrix3.adjugate().transpose(), - det:transform.matrix3.det(), - vertex:transform, - } - } +impl PhysicsMeshTransform { + pub fn new(transform: integer::Planar64Affine3) -> Self { + Self { + normal: transform.matrix3.adjugate().transpose(), + det: transform.matrix3.det(), + vertex: transform, + } + } } -pub struct TransformedMesh<'a>{ - view:PhysicsMeshView<'a>, - transform:&'a PhysicsMeshTransform, +pub struct TransformedMesh<'a> { + view: PhysicsMeshView<'a>, + transform: &'a PhysicsMeshTransform, } -impl TransformedMesh<'_>{ - pub const fn new<'a>( - view:PhysicsMeshView<'a>, - transform:&'a PhysicsMeshTransform, - )->TransformedMesh<'a>{ - TransformedMesh{ - view, - transform, - } - } - pub fn verts<'a>(&'a self)->impl Iterator>>+'a{ - self.view.data.verts.iter().map(|&Vert(pos)|self.transform.vertex.transform_point3(pos)) - } - fn farthest_vert(&self,dir:Planar64Vec3)->SubmeshVertId{ - //this happens to be well-defined. there are no virtual virtices - SubmeshVertId::new( - self.view.topology.verts.iter() - .enumerate() - .max_by_key(|(_,&vert_id)| - dir.dot(self.transform.vertex.transform_point3(self.view.data.verts[vert_id.get() as usize].0)) - ) - //assume there is more than zero vertices. - .unwrap().0 as u32 - ) - } +impl TransformedMesh<'_> { + pub const fn new<'a>( + view: PhysicsMeshView<'a>, + transform: &'a PhysicsMeshTransform, + ) -> TransformedMesh<'a> { + TransformedMesh { view, transform } + } + pub fn verts<'a>(&'a self) -> impl Iterator>> + 'a { + self.view + .data + .verts + .iter() + .map(|&Vert(pos)| self.transform.vertex.transform_point3(pos)) + } + fn farthest_vert(&self, dir: Planar64Vec3) -> SubmeshVertId { + //this happens to be well-defined. there are no virtual virtices + SubmeshVertId::new( + self.view + .topology + .verts + .iter() + .enumerate() + .max_by_key(|(_, &vert_id)| { + dir.dot( + self.transform + .vertex + .transform_point3(self.view.data.verts[vert_id.get() as usize].0), + ) + }) + //assume there is more than zero vertices. + .unwrap() + .0 as u32, + ) + } } -impl MeshQuery for TransformedMesh<'_>{ - type Face=SubmeshFaceId; - type Edge=SubmeshDirectedEdgeId; - type Vert=SubmeshVertId; - type Normal=Vector3>; - type Offset=Fixed<4,128>; - fn face_nd(&self,face_id:SubmeshFaceId)->(Self::Normal,Self::Offset){ - let (n,d)=self.view.face_nd(face_id); - let transformed_n=self.transform.normal*n; - let transformed_d=d*self.transform.det+transformed_n.dot(self.transform.vertex.translation); - (transformed_n,transformed_d) - } - fn vert(&self,vert_id:SubmeshVertId)->Planar64Vec3{ - self.transform.vertex.transform_point3(self.view.vert(vert_id)).fix_1() - } - #[inline] - fn face_edges(&self,face_id:SubmeshFaceId)->Cow>{ - self.view.face_edges(face_id) - } - #[inline] - fn edge_faces(&self,edge_id:SubmeshEdgeId)->Cow<[SubmeshFaceId;2]>{ - self.view.edge_faces(edge_id) - } - #[inline] - fn edge_verts(&self,edge_id:SubmeshEdgeId)->Cow<[SubmeshVertId;2]>{ - self.view.edge_verts(edge_id) - } - #[inline] - fn vert_edges(&self,vert_id:SubmeshVertId)->Cow>{ - self.view.vert_edges(vert_id) - } - #[inline] - fn vert_faces(&self,vert_id:SubmeshVertId)->Cow>{ - self.view.vert_faces(vert_id) - } +impl MeshQuery for TransformedMesh<'_> { + type Face = SubmeshFaceId; + type Edge = SubmeshDirectedEdgeId; + type Vert = SubmeshVertId; + type Normal = Vector3>; + type Offset = Fixed<4, 128>; + fn face_nd(&self, face_id: SubmeshFaceId) -> (Self::Normal, Self::Offset) { + let (n, d) = self.view.face_nd(face_id); + let transformed_n = self.transform.normal * n; + let transformed_d = + d * self.transform.det + transformed_n.dot(self.transform.vertex.translation); + (transformed_n, transformed_d) + } + fn vert(&self, vert_id: SubmeshVertId) -> Planar64Vec3 { + self.transform + .vertex + .transform_point3(self.view.vert(vert_id)) + .fix_1() + } + #[inline] + fn face_edges(&self, face_id: SubmeshFaceId) -> Cow> { + self.view.face_edges(face_id) + } + #[inline] + fn edge_faces(&self, edge_id: SubmeshEdgeId) -> Cow<[SubmeshFaceId; 2]> { + self.view.edge_faces(edge_id) + } + #[inline] + fn edge_verts(&self, edge_id: SubmeshEdgeId) -> Cow<[SubmeshVertId; 2]> { + self.view.edge_verts(edge_id) + } + #[inline] + fn vert_edges(&self, vert_id: SubmeshVertId) -> Cow> { + self.view.vert_edges(vert_id) + } + #[inline] + fn vert_faces(&self, vert_id: SubmeshVertId) -> Cow> { + self.view.vert_faces(vert_id) + } } //Note that a face on a minkowski mesh refers to a pair of fevs on the meshes it's summed from //(face,vertex) //(edge,edge) //(vertex,face) -#[derive(Clone,Copy,Debug)] -pub enum MinkowskiVert{ - VertVert(SubmeshVertId,SubmeshVertId), +#[derive(Clone, Copy, Debug)] +pub enum MinkowskiVert { + VertVert(SubmeshVertId, SubmeshVertId), } -#[derive(Clone,Copy,Debug)] -pub enum MinkowskiEdge{ - VertEdge(SubmeshVertId,SubmeshEdgeId), - EdgeVert(SubmeshEdgeId,SubmeshVertId), - //EdgeEdge when edges are parallel +#[derive(Clone, Copy, Debug)] +pub enum MinkowskiEdge { + VertEdge(SubmeshVertId, SubmeshEdgeId), + EdgeVert(SubmeshEdgeId, SubmeshVertId), + //EdgeEdge when edges are parallel } -impl UndirectedEdge for MinkowskiEdge{ - type DirectedEdge=MinkowskiDirectedEdge; - fn as_directed(&self,parity:bool)->Self::DirectedEdge{ - match self{ - MinkowskiEdge::VertEdge(v0,e1)=>MinkowskiDirectedEdge::VertEdge(*v0,e1.as_directed(parity)), - MinkowskiEdge::EdgeVert(e0,v1)=>MinkowskiDirectedEdge::EdgeVert(e0.as_directed(parity),*v1), - } - } +impl UndirectedEdge for MinkowskiEdge { + type DirectedEdge = MinkowskiDirectedEdge; + fn as_directed(&self, parity: bool) -> Self::DirectedEdge { + match self { + MinkowskiEdge::VertEdge(v0, e1) => { + MinkowskiDirectedEdge::VertEdge(*v0, e1.as_directed(parity)) + } + MinkowskiEdge::EdgeVert(e0, v1) => { + MinkowskiDirectedEdge::EdgeVert(e0.as_directed(parity), *v1) + } + } + } } -#[derive(Clone,Copy,Debug)] -pub enum MinkowskiDirectedEdge{ - VertEdge(SubmeshVertId,SubmeshDirectedEdgeId), - EdgeVert(SubmeshDirectedEdgeId,SubmeshVertId), - //EdgeEdge when edges are parallel +#[derive(Clone, Copy, Debug)] +pub enum MinkowskiDirectedEdge { + VertEdge(SubmeshVertId, SubmeshDirectedEdgeId), + EdgeVert(SubmeshDirectedEdgeId, SubmeshVertId), + //EdgeEdge when edges are parallel } -impl DirectedEdge for MinkowskiDirectedEdge{ - type UndirectedEdge=MinkowskiEdge; - fn as_undirected(&self)->Self::UndirectedEdge{ - match self{ - MinkowskiDirectedEdge::VertEdge(v0,e1)=>MinkowskiEdge::VertEdge(*v0,e1.as_undirected()), - MinkowskiDirectedEdge::EdgeVert(e0,v1)=>MinkowskiEdge::EdgeVert(e0.as_undirected(),*v1), - } - } - fn parity(&self)->bool{ - match self{ - MinkowskiDirectedEdge::VertEdge(_,e) - |MinkowskiDirectedEdge::EdgeVert(e,_)=>e.parity(), - } - } +impl DirectedEdge for MinkowskiDirectedEdge { + type UndirectedEdge = MinkowskiEdge; + fn as_undirected(&self) -> Self::UndirectedEdge { + match self { + MinkowskiDirectedEdge::VertEdge(v0, e1) => { + MinkowskiEdge::VertEdge(*v0, e1.as_undirected()) + } + MinkowskiDirectedEdge::EdgeVert(e0, v1) => { + MinkowskiEdge::EdgeVert(e0.as_undirected(), *v1) + } + } + } + fn parity(&self) -> bool { + match self { + MinkowskiDirectedEdge::VertEdge(_, e) | MinkowskiDirectedEdge::EdgeVert(e, _) => { + e.parity() + } + } + } } -#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq)] -pub enum MinkowskiFace{ - VertFace(SubmeshVertId,SubmeshFaceId), - EdgeEdge(SubmeshEdgeId,SubmeshEdgeId,bool), - FaceVert(SubmeshFaceId,SubmeshVertId), - //EdgeFace - //FaceEdge - //FaceFace +#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)] +pub enum MinkowskiFace { + VertFace(SubmeshVertId, SubmeshFaceId), + EdgeEdge(SubmeshEdgeId, SubmeshEdgeId, bool), + FaceVert(SubmeshFaceId, SubmeshVertId), + //EdgeFace + //FaceEdge + //FaceFace } -pub struct MinkowskiMesh<'a>{ - mesh0:TransformedMesh<'a>, - mesh1:TransformedMesh<'a>, +pub struct MinkowskiMesh<'a> { + mesh0: TransformedMesh<'a>, + mesh1: TransformedMesh<'a>, } //infinity fev algorithm state transition #[derive(Debug)] -enum Transition{ - Done,//found closest vert, no edges are better - Vert(MinkowskiVert),//transition to vert +enum Transition { + Done, //found closest vert, no edges are better + Vert(MinkowskiVert), //transition to vert } -enum EV{ - Vert(MinkowskiVert), - Edge(MinkowskiEdge), +enum EV { + Vert(MinkowskiVert), + Edge(MinkowskiEdge), } -pub type GigaTime=Ratio,Fixed<4,128>>; +pub type GigaTime = Ratio, Fixed<4, 128>>; -impl MinkowskiMesh<'_>{ - pub fn minkowski_sum<'a>(mesh0:TransformedMesh<'a>,mesh1:TransformedMesh<'a>)->MinkowskiMesh<'a>{ - MinkowskiMesh{ - mesh0, - mesh1, - } - } - fn farthest_vert(&self,dir:Planar64Vec3)->MinkowskiVert{ - MinkowskiVert::VertVert(self.mesh0.farthest_vert(dir),self.mesh1.farthest_vert(-dir)) - } - fn next_transition_vert(&self,vert_id:MinkowskiVert,best_distance_squared:&mut Fixed<2,64>,infinity_dir:Planar64Vec3,point:Planar64Vec3)->Transition{ - let mut best_transition=Transition::Done; - for &directed_edge_id in self.vert_edges(vert_id).iter(){ - let edge_n=self.directed_edge_n(directed_edge_id); - //is boundary uncrossable by a crawl from infinity - let edge_verts=self.edge_verts(directed_edge_id.as_undirected()); - //select opposite vertex - let test_vert_id=edge_verts[directed_edge_id.parity() as usize]; - //test if it's closer - let diff=point-self.vert(test_vert_id); - if edge_n.dot(infinity_dir).is_zero(){ - let distance_squared=diff.dot(diff); - if distance_squared<*best_distance_squared{ - best_transition=Transition::Vert(test_vert_id); - *best_distance_squared=distance_squared; - } - } - } - best_transition - } - fn final_ev(&self,vert_id:MinkowskiVert,best_distance_squared:&mut Fixed<2,64>,infinity_dir:Planar64Vec3,point:Planar64Vec3)->EV{ - let mut best_transition=EV::Vert(vert_id); - let diff=point-self.vert(vert_id); - for &directed_edge_id in self.vert_edges(vert_id).iter(){ - let edge_n=self.directed_edge_n(directed_edge_id); - //is boundary uncrossable by a crawl from infinity - //check if time of collision is outside Time::MIN..Time::MAX - if edge_n.dot(infinity_dir).is_zero(){ - let d=edge_n.dot(diff); - //test the edge - let edge_nn=edge_n.dot(edge_n); - if !d.is_negative()&&d<=edge_nn{ - let distance_squared={ - let c=diff.cross(edge_n); - (c.dot(c)/edge_nn).divide().fix_2() - }; - if distance_squared<=*best_distance_squared{ - best_transition=EV::Edge(directed_edge_id.as_undirected()); - *best_distance_squared=distance_squared; - } - } - } - } - best_transition - } - fn crawl_boundaries(&self,mut vert_id:MinkowskiVert,infinity_dir:Planar64Vec3,point:Planar64Vec3)->EV{ - let mut best_distance_squared={ - let diff=point-self.vert(vert_id); - diff.dot(diff) - }; - loop{ - match self.next_transition_vert(vert_id,&mut best_distance_squared,infinity_dir,point){ - Transition::Done=>return self.final_ev(vert_id,&mut best_distance_squared,infinity_dir,point), - Transition::Vert(new_vert_id)=>vert_id=new_vert_id, - } - } - } - /// This function drops a vertex down to an edge or a face if the path from infinity did not cross any vertex-edge boundaries but the point is supposed to have already crossed a boundary down from a vertex - fn infinity_fev(&self,infinity_dir:Planar64Vec3,point:Planar64Vec3)->FEV::{ - //start on any vertex - //cross uncrossable vertex-edge boundaries until you find the closest vertex or edge - //cross edge-face boundary if it's uncrossable - match self.crawl_boundaries(self.farthest_vert(infinity_dir),infinity_dir,point){ - //if a vert is returned, it is the closest point to the infinity point - EV::Vert(vert_id)=>FEV::Vert(vert_id), - EV::Edge(edge_id)=>{ - //cross to face if the boundary is not crossable and we are on the wrong side - let edge_n=self.edge_n(edge_id); - // point is multiplied by two because vert_sum sums two vertices. - let delta_pos=point*2-{ - let &[v0,v1]=self.edge_verts(edge_id).borrow(); - self.vert(v0)+self.vert(v1) - }; - for (i,&face_id) in self.edge_faces(edge_id).iter().enumerate(){ - let face_n=self.face_nd(face_id).0; - //edge-face boundary nd, n facing out of the face towards the edge - let boundary_n=face_n.cross(edge_n)*(i as i64*2-1); - let boundary_d=boundary_n.dot(delta_pos); - //check if time of collision is outside Time::MIN..Time::MAX - //infinity_dir can always be treated as a velocity - if !boundary_d.is_positive()&&boundary_n.dot(infinity_dir).is_zero(){ - //both faces cannot pass this condition, return early if one does. - return FEV::Face(face_id); - } - } - FEV::Edge(edge_id) - }, - } - } - fn closest_fev_not_inside(&self,mut infinity_body:Body)->Option>{ - infinity_body.infinity_dir().map_or(None,|dir|{ - let infinity_fev=self.infinity_fev(-dir,infinity_body.position); - //a line is simpler to solve than a parabola - infinity_body.velocity=dir; - infinity_body.acceleration=vec3::ZERO; - //crawl in from negative infinity along a tangent line to get the closest fev - // TODO: change crawl_fev args to delta time? Optional values? - match infinity_fev.crawl(self,&infinity_body,Time::MIN/4,infinity_body.time){ - crate::face_crawler::CrawlResult::Miss(fev)=>Some(fev), - crate::face_crawler::CrawlResult::Hit(_,_)=>None, - } - }) - } - pub fn predict_collision_in(&self,relative_body:&Body,time_limit:Time)->Option<(MinkowskiFace,GigaTime)>{ - self.closest_fev_not_inside(relative_body.clone()).map_or(None,|fev|{ - //continue forwards along the body parabola - match fev.crawl(self,relative_body,relative_body.time,time_limit){ - crate::face_crawler::CrawlResult::Miss(_)=>None, - crate::face_crawler::CrawlResult::Hit(face,time)=>Some((face,time)), - } - }) - } - pub fn predict_collision_out(&self,relative_body:&Body,time_limit:Time)->Option<(MinkowskiFace,GigaTime)>{ - //create an extrapolated body at time_limit - let infinity_body=Body::new( - relative_body.extrapolated_position(time_limit), - -relative_body.extrapolated_velocity(time_limit), - relative_body.acceleration, - -time_limit, - ); - self.closest_fev_not_inside(infinity_body).map_or(None,|fev|{ - //continue backwards along the body parabola - match fev.crawl(self,&-relative_body.clone(),-time_limit,-relative_body.time){ - crate::face_crawler::CrawlResult::Miss(_)=>None, - crate::face_crawler::CrawlResult::Hit(face,time)=>Some((face,-time)),//no need to test -timeOption<(MinkowskiEdge,GigaTime)>{ - //no algorithm needed, there is only one state and two cases (Edge,None) - //determine when it passes an edge ("sliding off" case) - let mut best_time={ - let r=(time_limit-relative_body.time).to_ratio(); - Ratio::new(r.num.fix_4(),r.den.fix_4()) - }; - let mut best_edge=None; - let face_n=self.face_nd(contact_face_id).0; - for &directed_edge_id in self.face_edges(contact_face_id).iter(){ - let edge_n=self.directed_edge_n(directed_edge_id); - //f x e points in - let n=face_n.cross(edge_n); - let verts=self.edge_verts(directed_edge_id.as_undirected()); - let d=n.dot(self.vert(verts[0])+self.vert(verts[1])); - //WARNING! d outside of *2 - //WARNING: truncated precision - for dt in Fixed::<4,128>::zeroes2(((n.dot(relative_body.position))*2-d).fix_4(),n.dot(relative_body.velocity).fix_4()*2,n.dot(relative_body.acceleration).fix_4()){ - if Ratio::new(Planar64::ZERO,Planar64::EPSILON).le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(relative_body.extrapolated_velocity_ratio_dt(dt)).is_negative(){ - best_time=dt; - best_edge=Some(directed_edge_id); - break; - } - } - } - best_edge.map(|e|(e.as_undirected(),best_time)) - } - fn infinity_in(&self,infinity_body:Body)->Option<(MinkowskiFace,GigaTime)>{ - let infinity_fev=self.infinity_fev(-infinity_body.velocity,infinity_body.position); - match infinity_fev.crawl(self,&infinity_body,Time::MIN/4,infinity_body.time){ - crate::face_crawler::CrawlResult::Miss(_)=>None, - crate::face_crawler::CrawlResult::Hit(face,time)=>Some((face,time)), - } - } - pub fn is_point_in_mesh(&self,point:Planar64Vec3)->bool{ - let infinity_body=Body::new(point,vec3::Y,vec3::ZERO,Time::ZERO); - //movement must escape the mesh forwards and backwards in time, - //otherwise the point is not inside the mesh - self.infinity_in(infinity_body) - .is_some_and(|_| - self.infinity_in(-infinity_body) - .is_some() - ) - } +impl MinkowskiMesh<'_> { + pub fn minkowski_sum<'a>( + mesh0: TransformedMesh<'a>, + mesh1: TransformedMesh<'a>, + ) -> MinkowskiMesh<'a> { + MinkowskiMesh { mesh0, mesh1 } + } + fn farthest_vert(&self, dir: Planar64Vec3) -> MinkowskiVert { + MinkowskiVert::VertVert( + self.mesh0.farthest_vert(dir), + self.mesh1.farthest_vert(-dir), + ) + } + fn next_transition_vert( + &self, + vert_id: MinkowskiVert, + best_distance_squared: &mut Fixed<2, 64>, + infinity_dir: Planar64Vec3, + point: Planar64Vec3, + ) -> Transition { + let mut best_transition = Transition::Done; + for &directed_edge_id in self.vert_edges(vert_id).iter() { + let edge_n = self.directed_edge_n(directed_edge_id); + //is boundary uncrossable by a crawl from infinity + let edge_verts = self.edge_verts(directed_edge_id.as_undirected()); + //select opposite vertex + let test_vert_id = edge_verts[directed_edge_id.parity() as usize]; + //test if it's closer + let diff = point - self.vert(test_vert_id); + if edge_n.dot(infinity_dir).is_zero() { + let distance_squared = diff.dot(diff); + if distance_squared < *best_distance_squared { + best_transition = Transition::Vert(test_vert_id); + *best_distance_squared = distance_squared; + } + } + } + best_transition + } + fn final_ev( + &self, + vert_id: MinkowskiVert, + best_distance_squared: &mut Fixed<2, 64>, + infinity_dir: Planar64Vec3, + point: Planar64Vec3, + ) -> EV { + let mut best_transition = EV::Vert(vert_id); + let diff = point - self.vert(vert_id); + for &directed_edge_id in self.vert_edges(vert_id).iter() { + let edge_n = self.directed_edge_n(directed_edge_id); + //is boundary uncrossable by a crawl from infinity + //check if time of collision is outside Time::MIN..Time::MAX + if edge_n.dot(infinity_dir).is_zero() { + let d = edge_n.dot(diff); + //test the edge + let edge_nn = edge_n.dot(edge_n); + if !d.is_negative() && d <= edge_nn { + let distance_squared = { + let c = diff.cross(edge_n); + (c.dot(c) / edge_nn).divide().fix_2() + }; + if distance_squared <= *best_distance_squared { + best_transition = EV::Edge(directed_edge_id.as_undirected()); + *best_distance_squared = distance_squared; + } + } + } + } + best_transition + } + fn crawl_boundaries( + &self, + mut vert_id: MinkowskiVert, + infinity_dir: Planar64Vec3, + point: Planar64Vec3, + ) -> EV { + let mut best_distance_squared = { + let diff = point - self.vert(vert_id); + diff.dot(diff) + }; + loop { + match self.next_transition_vert( + vert_id, + &mut best_distance_squared, + infinity_dir, + point, + ) { + Transition::Done => { + return self.final_ev(vert_id, &mut best_distance_squared, infinity_dir, point) + } + Transition::Vert(new_vert_id) => vert_id = new_vert_id, + } + } + } + /// This function drops a vertex down to an edge or a face if the path from infinity did not cross any vertex-edge boundaries but the point is supposed to have already crossed a boundary down from a vertex + fn infinity_fev(&self, infinity_dir: Planar64Vec3, point: Planar64Vec3) -> FEV { + //start on any vertex + //cross uncrossable vertex-edge boundaries until you find the closest vertex or edge + //cross edge-face boundary if it's uncrossable + match self.crawl_boundaries(self.farthest_vert(infinity_dir), infinity_dir, point) { + //if a vert is returned, it is the closest point to the infinity point + EV::Vert(vert_id) => FEV::Vert(vert_id), + EV::Edge(edge_id) => { + //cross to face if the boundary is not crossable and we are on the wrong side + let edge_n = self.edge_n(edge_id); + // point is multiplied by two because vert_sum sums two vertices. + let delta_pos = point * 2 - { + let &[v0, v1] = self.edge_verts(edge_id).borrow(); + self.vert(v0) + self.vert(v1) + }; + for (i, &face_id) in self.edge_faces(edge_id).iter().enumerate() { + let face_n = self.face_nd(face_id).0; + //edge-face boundary nd, n facing out of the face towards the edge + let boundary_n = face_n.cross(edge_n) * (i as i64 * 2 - 1); + let boundary_d = boundary_n.dot(delta_pos); + //check if time of collision is outside Time::MIN..Time::MAX + //infinity_dir can always be treated as a velocity + if !boundary_d.is_positive() && boundary_n.dot(infinity_dir).is_zero() { + //both faces cannot pass this condition, return early if one does. + return FEV::Face(face_id); + } + } + FEV::Edge(edge_id) + } + } + } + fn closest_fev_not_inside(&self, mut infinity_body: Body) -> Option> { + infinity_body.infinity_dir().map_or(None, |dir| { + let infinity_fev = self.infinity_fev(-dir, infinity_body.position); + //a line is simpler to solve than a parabola + infinity_body.velocity = dir; + infinity_body.acceleration = vec3::ZERO; + //crawl in from negative infinity along a tangent line to get the closest fev + // TODO: change crawl_fev args to delta time? Optional values? + match infinity_fev.crawl(self, &infinity_body, Time::MIN / 4, infinity_body.time) { + crate::face_crawler::CrawlResult::Miss(fev) => Some(fev), + crate::face_crawler::CrawlResult::Hit(_, _) => None, + } + }) + } + pub fn predict_collision_in( + &self, + relative_body: &Body, + time_limit: Time, + ) -> Option<(MinkowskiFace, GigaTime)> { + self.closest_fev_not_inside(relative_body.clone()) + .map_or(None, |fev| { + //continue forwards along the body parabola + match fev.crawl(self, relative_body, relative_body.time, time_limit) { + crate::face_crawler::CrawlResult::Miss(_) => None, + crate::face_crawler::CrawlResult::Hit(face, time) => Some((face, time)), + } + }) + } + pub fn predict_collision_out( + &self, + relative_body: &Body, + time_limit: Time, + ) -> Option<(MinkowskiFace, GigaTime)> { + //create an extrapolated body at time_limit + let infinity_body = Body::new( + relative_body.extrapolated_position(time_limit), + -relative_body.extrapolated_velocity(time_limit), + relative_body.acceleration, + -time_limit, + ); + self.closest_fev_not_inside(infinity_body) + .map_or(None, |fev| { + //continue backwards along the body parabola + match fev.crawl( + self, + &-relative_body.clone(), + -time_limit, + -relative_body.time, + ) { + crate::face_crawler::CrawlResult::Miss(_) => None, + crate::face_crawler::CrawlResult::Hit(face, time) => Some((face, -time)), //no need to test -time Option<(MinkowskiEdge, GigaTime)> { + //no algorithm needed, there is only one state and two cases (Edge,None) + //determine when it passes an edge ("sliding off" case) + let mut best_time = { + let r = (time_limit - relative_body.time).to_ratio(); + Ratio::new(r.num.fix_4(), r.den.fix_4()) + }; + let mut best_edge = None; + let face_n = self.face_nd(contact_face_id).0; + for &directed_edge_id in self.face_edges(contact_face_id).iter() { + let edge_n = self.directed_edge_n(directed_edge_id); + //f x e points in + let n = face_n.cross(edge_n); + let verts = self.edge_verts(directed_edge_id.as_undirected()); + let d = n.dot(self.vert(verts[0]) + self.vert(verts[1])); + //WARNING! d outside of *2 + //WARNING: truncated precision + for dt in Fixed::<4, 128>::zeroes2( + ((n.dot(relative_body.position)) * 2 - d).fix_4(), + n.dot(relative_body.velocity).fix_4() * 2, + n.dot(relative_body.acceleration).fix_4(), + ) { + if Ratio::new(Planar64::ZERO, Planar64::EPSILON).le_ratio(dt) + && dt.lt_ratio(best_time) + && n.dot(relative_body.extrapolated_velocity_ratio_dt(dt)) + .is_negative() + { + best_time = dt; + best_edge = Some(directed_edge_id); + break; + } + } + } + best_edge.map(|e| (e.as_undirected(), best_time)) + } + fn infinity_in(&self, infinity_body: Body) -> Option<(MinkowskiFace, GigaTime)> { + let infinity_fev = self.infinity_fev(-infinity_body.velocity, infinity_body.position); + match infinity_fev.crawl(self, &infinity_body, Time::MIN / 4, infinity_body.time) { + crate::face_crawler::CrawlResult::Miss(_) => None, + crate::face_crawler::CrawlResult::Hit(face, time) => Some((face, time)), + } + } + pub fn is_point_in_mesh(&self, point: Planar64Vec3) -> bool { + let infinity_body = Body::new(point, vec3::Y, vec3::ZERO, Time::ZERO); + //movement must escape the mesh forwards and backwards in time, + //otherwise the point is not inside the mesh + self.infinity_in(infinity_body) + .is_some_and(|_| self.infinity_in(-infinity_body).is_some()) + } } -impl MeshQuery for MinkowskiMesh<'_>{ - type Face=MinkowskiFace; - type Edge=MinkowskiDirectedEdge; - type Vert=MinkowskiVert; - type Normal=Vector3>; - type Offset=Fixed<4,128>; - // TODO: relative d - fn face_nd(&self,face_id:MinkowskiFace)->(Self::Normal,Self::Offset){ - match face_id{ - MinkowskiFace::VertFace(v0,f1)=>{ - let (n,d)=self.mesh1.face_nd(f1); - (-n,d-n.dot(self.mesh0.vert(v0))) - }, - MinkowskiFace::EdgeEdge(e0,e1,parity)=>{ - let edge0_n=self.mesh0.edge_n(e0); - let edge1_n=self.mesh1.edge_n(e1); - let &[e0v0,e0v1]=self.mesh0.edge_verts(e0).borrow(); - let &[e1v0,e1v1]=self.mesh1.edge_verts(e1).borrow(); - let n=edge0_n.cross(edge1_n); - let e0d=n.dot(self.mesh0.vert(e0v0)+self.mesh0.vert(e0v1)); - let e1d=n.dot(self.mesh1.vert(e1v0)+self.mesh1.vert(e1v1)); - ((n*(parity as i64*4-2)).fix_3(),((e0d-e1d)*(parity as i64*2-1)).fix_4()) - }, - MinkowskiFace::FaceVert(f0,v1)=>{ - let (n,d)=self.mesh0.face_nd(f0); - (n,d-n.dot(self.mesh1.vert(v1))) - }, - } - } - fn vert(&self,vert_id:MinkowskiVert)->Planar64Vec3{ - match vert_id{ - MinkowskiVert::VertVert(v0,v1)=>{ - self.mesh0.vert(v0)-self.mesh1.vert(v1) - }, - } - } - fn face_edges(&self,face_id:MinkowskiFace)->Cow>{ - match face_id{ - MinkowskiFace::VertFace(v0,f1)=>{ - Cow::Owned(self.mesh1.face_edges(f1).iter().map(|&edge_id1|{ - MinkowskiDirectedEdge::VertEdge(v0,edge_id1.reverse()) - }).collect()) - }, - MinkowskiFace::EdgeEdge(e0,e1,parity)=>{ - let e0v=self.mesh0.edge_verts(e0); - let e1v=self.mesh1.edge_verts(e1); - //could sort this if ordered edges are needed - //probably just need to reverse this list according to parity - Cow::Owned(vec![ - MinkowskiDirectedEdge::VertEdge(e0v[0],e1.as_directed(parity)), - MinkowskiDirectedEdge::EdgeVert(e0.as_directed(!parity),e1v[0]), - MinkowskiDirectedEdge::VertEdge(e0v[1],e1.as_directed(!parity)), - MinkowskiDirectedEdge::EdgeVert(e0.as_directed(parity),e1v[1]), - ]) - }, - MinkowskiFace::FaceVert(f0,v1)=>{ - Cow::Owned(self.mesh0.face_edges(f0).iter().map(|&edge_id0|{ - MinkowskiDirectedEdge::EdgeVert(edge_id0,v1) - }).collect()) - }, - } - } - fn edge_faces(&self,edge_id:MinkowskiEdge)->Cow<[MinkowskiFace;2]>{ - match edge_id{ - MinkowskiEdge::VertEdge(v0,e1)=>{ - //faces are listed backwards from the minkowski mesh - let v0e=self.mesh0.vert_edges(v0); - let &[e1f0,e1f1]=self.mesh1.edge_faces(e1).borrow(); - Cow::Owned([(e1f1,false),(e1f0,true)].map(|(edge_face_id1,face_parity)|{ - let mut best_edge=None; - let mut best_d:Ratio,Fixed<8,256>>=Ratio::new(Fixed::ZERO,Fixed::ONE); - let edge_face1_n=self.mesh1.face_nd(edge_face_id1).0; - let edge_face1_nn=edge_face1_n.dot(edge_face1_n); - for &directed_edge_id0 in v0e.iter(){ - let edge0_n=self.mesh0.directed_edge_n(directed_edge_id0); - //must be behind other face. - let d=edge_face1_n.dot(edge0_n); - if d.is_negative(){ - let edge0_nn=edge0_n.dot(edge0_n); - // Assume not every number is huge - // TODO: revisit this - let dd=(d*d)/(edge_face1_nn*edge0_nn); - if best_d{ - //tracking index with an external variable because .enumerate() is not available - let v1e=self.mesh1.vert_edges(v1); - let &[e0f0,e0f1]=self.mesh0.edge_faces(e0).borrow(); - Cow::Owned([(e0f0,true),(e0f1,false)].map(|(edge_face_id0,face_parity)|{ - let mut best_edge=None; - let mut best_d:Ratio,Fixed<8,256>>=Ratio::new(Fixed::ZERO,Fixed::ONE); - let edge_face0_n=self.mesh0.face_nd(edge_face_id0).0; - let edge_face0_nn=edge_face0_n.dot(edge_face0_n); - for &directed_edge_id1 in v1e.iter(){ - let edge1_n=self.mesh1.directed_edge_n(directed_edge_id1); - let d=edge_face0_n.dot(edge1_n); - if d.is_negative(){ - let edge1_nn=edge1_n.dot(edge1_n); - let dd=(d*d)/(edge_face0_nn*edge1_nn); - if best_dCow<[MinkowskiVert;2]>{ - match edge_id{ - MinkowskiEdge::VertEdge(v0,e1)=>{ - Cow::Owned(self.mesh1.edge_verts(e1).map(|vert_id1|{ - MinkowskiVert::VertVert(v0,vert_id1) - })) - }, - MinkowskiEdge::EdgeVert(e0,v1)=>{ - Cow::Owned(self.mesh0.edge_verts(e0).map(|vert_id0|{ - MinkowskiVert::VertVert(vert_id0,v1) - })) - }, - } - } - fn vert_edges(&self,vert_id:MinkowskiVert)->Cow>{ - match vert_id{ - MinkowskiVert::VertVert(v0,v1)=>{ - let mut edges=Vec::new(); - //detect shared volume when the other mesh is mirrored along a test edge dir - let v0f=self.mesh0.vert_faces(v0); - let v1f=self.mesh1.vert_faces(v1); - let v0f_n:Vec<_>=v0f.iter().map(|&face_id|self.mesh0.face_nd(face_id).0).collect(); - let v1f_n:Vec<_>=v1f.iter().map(|&face_id|self.mesh1.face_nd(face_id).0).collect(); - let the_len=v0f.len()+v1f.len(); - for &directed_edge_id in self.mesh0.vert_edges(v0).iter(){ - let n=self.mesh0.directed_edge_n(directed_edge_id); - let nn=n.dot(n); - // TODO: there's gotta be a better way to do this - //make a set of faces - let mut face_normals=Vec::with_capacity(the_len); - //add mesh0 faces as-is - face_normals.clone_from(&v0f_n); - for face_n in &v1f_n{ - //add reflected mesh1 faces - face_normals.push(*face_n-(n*face_n.dot(n)*2/nn).divide().fix_3()); - } - if is_empty_volume(face_normals){ - edges.push(MinkowskiDirectedEdge::EdgeVert(directed_edge_id,v1)); - } - } - for &directed_edge_id in self.mesh1.vert_edges(v1).iter(){ - let n=self.mesh1.directed_edge_n(directed_edge_id); - let nn=n.dot(n); - let mut face_normals=Vec::with_capacity(the_len); - face_normals.clone_from(&v1f_n); - for face_n in &v0f_n{ - face_normals.push(*face_n-(n*face_n.dot(n)*2/nn).divide().fix_3()); - } - if is_empty_volume(face_normals){ - edges.push(MinkowskiDirectedEdge::VertEdge(v0,directed_edge_id)); - } - } - Cow::Owned(edges) - }, - } - } - fn vert_faces(&self,_vert_id:MinkowskiVert)->Cow>{ - unimplemented!() - } +impl MeshQuery for MinkowskiMesh<'_> { + type Face = MinkowskiFace; + type Edge = MinkowskiDirectedEdge; + type Vert = MinkowskiVert; + type Normal = Vector3>; + type Offset = Fixed<4, 128>; + // TODO: relative d + fn face_nd(&self, face_id: MinkowskiFace) -> (Self::Normal, Self::Offset) { + match face_id { + MinkowskiFace::VertFace(v0, f1) => { + let (n, d) = self.mesh1.face_nd(f1); + (-n, d - n.dot(self.mesh0.vert(v0))) + } + MinkowskiFace::EdgeEdge(e0, e1, parity) => { + let edge0_n = self.mesh0.edge_n(e0); + let edge1_n = self.mesh1.edge_n(e1); + let &[e0v0, e0v1] = self.mesh0.edge_verts(e0).borrow(); + let &[e1v0, e1v1] = self.mesh1.edge_verts(e1).borrow(); + let n = edge0_n.cross(edge1_n); + let e0d = n.dot(self.mesh0.vert(e0v0) + self.mesh0.vert(e0v1)); + let e1d = n.dot(self.mesh1.vert(e1v0) + self.mesh1.vert(e1v1)); + ( + (n * (parity as i64 * 4 - 2)).fix_3(), + ((e0d - e1d) * (parity as i64 * 2 - 1)).fix_4(), + ) + } + MinkowskiFace::FaceVert(f0, v1) => { + let (n, d) = self.mesh0.face_nd(f0); + (n, d - n.dot(self.mesh1.vert(v1))) + } + } + } + fn vert(&self, vert_id: MinkowskiVert) -> Planar64Vec3 { + match vert_id { + MinkowskiVert::VertVert(v0, v1) => self.mesh0.vert(v0) - self.mesh1.vert(v1), + } + } + fn face_edges(&self, face_id: MinkowskiFace) -> Cow> { + match face_id { + MinkowskiFace::VertFace(v0, f1) => Cow::Owned( + self.mesh1 + .face_edges(f1) + .iter() + .map(|&edge_id1| MinkowskiDirectedEdge::VertEdge(v0, edge_id1.reverse())) + .collect(), + ), + MinkowskiFace::EdgeEdge(e0, e1, parity) => { + let e0v = self.mesh0.edge_verts(e0); + let e1v = self.mesh1.edge_verts(e1); + //could sort this if ordered edges are needed + //probably just need to reverse this list according to parity + Cow::Owned(vec![ + MinkowskiDirectedEdge::VertEdge(e0v[0], e1.as_directed(parity)), + MinkowskiDirectedEdge::EdgeVert(e0.as_directed(!parity), e1v[0]), + MinkowskiDirectedEdge::VertEdge(e0v[1], e1.as_directed(!parity)), + MinkowskiDirectedEdge::EdgeVert(e0.as_directed(parity), e1v[1]), + ]) + } + MinkowskiFace::FaceVert(f0, v1) => Cow::Owned( + self.mesh0 + .face_edges(f0) + .iter() + .map(|&edge_id0| MinkowskiDirectedEdge::EdgeVert(edge_id0, v1)) + .collect(), + ), + } + } + fn edge_faces(&self, edge_id: MinkowskiEdge) -> Cow<[MinkowskiFace; 2]> { + match edge_id { + MinkowskiEdge::VertEdge(v0, e1) => { + //faces are listed backwards from the minkowski mesh + let v0e = self.mesh0.vert_edges(v0); + let &[e1f0, e1f1] = self.mesh1.edge_faces(e1).borrow(); + Cow::Owned( + [(e1f1, false), (e1f0, true)].map(|(edge_face_id1, face_parity)| { + let mut best_edge = None; + let mut best_d: Ratio, Fixed<8, 256>> = + Ratio::new(Fixed::ZERO, Fixed::ONE); + let edge_face1_n = self.mesh1.face_nd(edge_face_id1).0; + let edge_face1_nn = edge_face1_n.dot(edge_face1_n); + for &directed_edge_id0 in v0e.iter() { + let edge0_n = self.mesh0.directed_edge_n(directed_edge_id0); + //must be behind other face. + let d = edge_face1_n.dot(edge0_n); + if d.is_negative() { + let edge0_nn = edge0_n.dot(edge0_n); + // Assume not every number is huge + // TODO: revisit this + let dd = (d * d) / (edge_face1_nn * edge0_nn); + if best_d < dd { + best_d = dd; + best_edge = Some(directed_edge_id0); + } + } + } + best_edge.map_or( + MinkowskiFace::VertFace(v0, edge_face_id1), + |directed_edge_id0| { + MinkowskiFace::EdgeEdge( + directed_edge_id0.as_undirected(), + e1, + directed_edge_id0.parity() ^ face_parity, + ) + }, + ) + }), + ) + } + MinkowskiEdge::EdgeVert(e0, v1) => { + //tracking index with an external variable because .enumerate() is not available + let v1e = self.mesh1.vert_edges(v1); + let &[e0f0, e0f1] = self.mesh0.edge_faces(e0).borrow(); + Cow::Owned( + [(e0f0, true), (e0f1, false)].map(|(edge_face_id0, face_parity)| { + let mut best_edge = None; + let mut best_d: Ratio, Fixed<8, 256>> = + Ratio::new(Fixed::ZERO, Fixed::ONE); + let edge_face0_n = self.mesh0.face_nd(edge_face_id0).0; + let edge_face0_nn = edge_face0_n.dot(edge_face0_n); + for &directed_edge_id1 in v1e.iter() { + let edge1_n = self.mesh1.directed_edge_n(directed_edge_id1); + let d = edge_face0_n.dot(edge1_n); + if d.is_negative() { + let edge1_nn = edge1_n.dot(edge1_n); + let dd = (d * d) / (edge_face0_nn * edge1_nn); + if best_d < dd { + best_d = dd; + best_edge = Some(directed_edge_id1); + } + } + } + best_edge.map_or( + MinkowskiFace::FaceVert(edge_face_id0, v1), + |directed_edge_id1| { + MinkowskiFace::EdgeEdge( + e0, + directed_edge_id1.as_undirected(), + directed_edge_id1.parity() ^ face_parity, + ) + }, + ) + }), + ) + } + } + } + fn edge_verts(&self, edge_id: MinkowskiEdge) -> Cow<[MinkowskiVert; 2]> { + match edge_id { + MinkowskiEdge::VertEdge(v0, e1) => Cow::Owned( + self.mesh1 + .edge_verts(e1) + .map(|vert_id1| MinkowskiVert::VertVert(v0, vert_id1)), + ), + MinkowskiEdge::EdgeVert(e0, v1) => Cow::Owned( + self.mesh0 + .edge_verts(e0) + .map(|vert_id0| MinkowskiVert::VertVert(vert_id0, v1)), + ), + } + } + fn vert_edges(&self, vert_id: MinkowskiVert) -> Cow> { + match vert_id { + MinkowskiVert::VertVert(v0, v1) => { + let mut edges = Vec::new(); + //detect shared volume when the other mesh is mirrored along a test edge dir + let v0f = self.mesh0.vert_faces(v0); + let v1f = self.mesh1.vert_faces(v1); + let v0f_n: Vec<_> = v0f + .iter() + .map(|&face_id| self.mesh0.face_nd(face_id).0) + .collect(); + let v1f_n: Vec<_> = v1f + .iter() + .map(|&face_id| self.mesh1.face_nd(face_id).0) + .collect(); + let the_len = v0f.len() + v1f.len(); + for &directed_edge_id in self.mesh0.vert_edges(v0).iter() { + let n = self.mesh0.directed_edge_n(directed_edge_id); + let nn = n.dot(n); + // TODO: there's gotta be a better way to do this + //make a set of faces + let mut face_normals = Vec::with_capacity(the_len); + //add mesh0 faces as-is + face_normals.clone_from(&v0f_n); + for face_n in &v1f_n { + //add reflected mesh1 faces + face_normals.push(*face_n - (n * face_n.dot(n) * 2 / nn).divide().fix_3()); + } + if is_empty_volume(face_normals) { + edges.push(MinkowskiDirectedEdge::EdgeVert(directed_edge_id, v1)); + } + } + for &directed_edge_id in self.mesh1.vert_edges(v1).iter() { + let n = self.mesh1.directed_edge_n(directed_edge_id); + let nn = n.dot(n); + let mut face_normals = Vec::with_capacity(the_len); + face_normals.clone_from(&v1f_n); + for face_n in &v0f_n { + face_normals.push(*face_n - (n * face_n.dot(n) * 2 / nn).divide().fix_3()); + } + if is_empty_volume(face_normals) { + edges.push(MinkowskiDirectedEdge::VertEdge(v0, directed_edge_id)); + } + } + Cow::Owned(edges) + } + } + } + fn vert_faces(&self, _vert_id: MinkowskiVert) -> Cow> { + unimplemented!() + } } -fn is_empty_volume(normals:Vec>>)->bool{ - let len=normals.len(); - for i in 0..len-1{ - for j in i+1..len{ - let n=normals[i].cross(normals[j]); - let mut d_comp=None; - for k in 0..len{ - if k!=i&&k!=j{ - let d=n.dot(normals[k]).is_negative(); - if let Some(comp)=&d_comp{ - // This is testing if d_comp*d < 0 - if comp^d{ - return true; - } - }else{ - d_comp=Some(d); - } - } - } - } - } - return false; +fn is_empty_volume(normals: Vec>>) -> bool { + let len = normals.len(); + for i in 0..len - 1 { + for j in i + 1..len { + let n = normals[i].cross(normals[j]); + let mut d_comp = None; + for k in 0..len { + if k != i && k != j { + let d = n.dot(normals[k]).is_negative(); + if let Some(comp) = &d_comp { + // This is testing if d_comp*d < 0 + if comp ^ d { + return true; + } + } else { + d_comp = Some(d); + } + } + } + } + } + return false; } #[test] -fn test_is_empty_volume(){ - assert!(!is_empty_volume([vec3::X.fix_3(),vec3::Y.fix_3(),vec3::Z.fix_3()].to_vec())); - assert!(is_empty_volume([vec3::X.fix_3(),vec3::Y.fix_3(),vec3::Z.fix_3(),vec3::NEG_X.fix_3()].to_vec())); +fn test_is_empty_volume() { + assert!(!is_empty_volume( + [vec3::X.fix_3(), vec3::Y.fix_3(), vec3::Z.fix_3()].to_vec() + )); + assert!(is_empty_volume( + [ + vec3::X.fix_3(), + vec3::Y.fix_3(), + vec3::Z.fix_3(), + vec3::NEG_X.fix_3() + ] + .to_vec() + )); } #[test] -fn build_me_a_cube(){ - let mesh=PhysicsMesh::unit_cube(); - //println!("mesh={:?}",mesh); +fn build_me_a_cube() { + let mesh = PhysicsMesh::unit_cube(); + //println!("mesh={:?}",mesh); } diff --git a/strafe-client/src/physics.rs b/strafe-client/src/physics.rs index c6c4491..c03640f 100644 --- a/strafe-client/src/physics.rs +++ b/strafe-client/src/physics.rs @@ -1,21 +1,28 @@ -use std::collections::{HashMap,HashSet}; -use crate::model_physics::{self,PhysicsMesh,PhysicsMeshTransform,TransformedMesh,MeshQuery,PhysicsMeshId,PhysicsSubmeshId}; -use strafesnet_common::bvh; -use strafesnet_common::map; -use strafesnet_common::run; -use strafesnet_common::aabb; -use strafesnet_common::model::{MeshId,ModelId}; -use strafesnet_common::gameplay_attributes::{self,CollisionAttributesId}; -use strafesnet_common::gameplay_modes::{self,StageId}; -use strafesnet_common::gameplay_style::{self,StyleModifiers}; -use strafesnet_common::controls_bitflag::Controls; -use strafesnet_common::instruction::{self,InstructionEmitter,InstructionConsumer,TimedInstruction}; -use strafesnet_common::integer::{self,vec3,mat3,Planar64,Planar64Vec3,Planar64Mat3,Angle32,Ratio64Vec2}; -pub use strafesnet_common::physics::{Time,TimeInner}; +use crate::model_physics::{ + self, MeshQuery, PhysicsMesh, PhysicsMeshId, PhysicsMeshTransform, PhysicsSubmeshId, + TransformedMesh, +}; use gameplay::ModeState; +use std::collections::{HashMap, HashSet}; +use strafesnet_common::aabb; +use strafesnet_common::bvh; +use strafesnet_common::controls_bitflag::Controls; +use strafesnet_common::gameplay_attributes::{self, CollisionAttributesId}; +use strafesnet_common::gameplay_modes::{self, StageId}; +use strafesnet_common::gameplay_style::{self, StyleModifiers}; +use strafesnet_common::instruction::{ + self, InstructionConsumer, InstructionEmitter, TimedInstruction, +}; +use strafesnet_common::integer::{ + self, mat3, vec3, Angle32, Planar64, Planar64Mat3, Planar64Vec3, Ratio64Vec2, +}; +use strafesnet_common::map; +use strafesnet_common::model::{MeshId, ModelId}; +pub use strafesnet_common::physics::{Time, TimeInner}; +use strafesnet_common::run; -pub type Body=crate::body::Body; -type MouseState=strafesnet_common::mouse::MouseState; +pub type Body = crate::body::Body; +type MouseState = strafesnet_common::mouse::MouseState; //external influence //this is how you influence the physics from outside @@ -24,1048 +31,1438 @@ use strafesnet_common::physics::Instruction as PhysicsInputInstruction; //internal influence //when the physics asks itself what happens next, this is how it's represented #[derive(Debug)] -pub enum PhysicsInternalInstruction{ - CollisionStart(Collision,model_physics::GigaTime), - CollisionEnd(Collision,model_physics::GigaTime), - StrafeTick, - ReachWalkTargetVelocity, - // Water, +pub enum PhysicsInternalInstruction { + CollisionStart(Collision, model_physics::GigaTime), + CollisionEnd(Collision, model_physics::GigaTime), + StrafeTick, + ReachWalkTargetVelocity, + // Water, } #[derive(Debug)] -pub enum PhysicsInstruction{ - Internal(PhysicsInternalInstruction), - //InputInstructions conditionally activate RefreshWalkTarget - //(by doing what SetWalkTargetVelocity used to do and then flagging it) - Input(PhysicsInputInstruction), +pub enum PhysicsInstruction { + Internal(PhysicsInternalInstruction), + //InputInstructions conditionally activate RefreshWalkTarget + //(by doing what SetWalkTargetVelocity used to do and then flagging it) + Input(PhysicsInputInstruction), } -#[derive(Clone,Debug,Default)] -pub struct InputState{ - mouse:MouseState, - next_mouse:MouseState, - controls:strafesnet_common::controls_bitflag::Controls, +#[derive(Clone, Debug, Default)] +pub struct InputState { + mouse: MouseState, + next_mouse: MouseState, + controls: strafesnet_common::controls_bitflag::Controls, } -impl InputState{ - pub const fn get_next_mouse(&self)->&MouseState{ - &self.next_mouse - } - fn set_next_mouse(&mut self,next_mouse:MouseState){ - //I like your functions magic language - self.mouse=std::mem::replace(&mut self.next_mouse,next_mouse); - //equivalently: - //(self.next_mouse,self.mouse)=(next_mouse,self.next_mouse.clone()); - } - fn replace_mouse(&mut self,mouse:MouseState,next_mouse:MouseState){ - (self.next_mouse,self.mouse)=(next_mouse,mouse); - } - fn set_control(&mut self,control:Controls,state:bool){ - self.controls.set(control,state) - } - fn time_delta(&self)->Time{ - self.next_mouse.time-self.mouse.time - } - fn mouse_delta(&self)->glam::IVec2{ - self.next_mouse.pos-self.mouse.pos - } - fn lerp_delta(&self,time:Time)->glam::IVec2{ - //these are deltas - let dm=self.mouse_delta().as_i64vec2(); - let t=(time-self.mouse.time).nanos(); - let dt=self.time_delta().nanos(); - ((dm*t)/dt).as_ivec2() - } +impl InputState { + pub const fn get_next_mouse(&self) -> &MouseState { + &self.next_mouse + } + fn set_next_mouse(&mut self, next_mouse: MouseState) { + //I like your functions magic language + self.mouse = std::mem::replace(&mut self.next_mouse, next_mouse); + //equivalently: + //(self.next_mouse,self.mouse)=(next_mouse,self.next_mouse.clone()); + } + fn replace_mouse(&mut self, mouse: MouseState, next_mouse: MouseState) { + (self.next_mouse, self.mouse) = (next_mouse, mouse); + } + fn set_control(&mut self, control: Controls, state: bool) { + self.controls.set(control, state) + } + fn time_delta(&self) -> Time { + self.next_mouse.time - self.mouse.time + } + fn mouse_delta(&self) -> glam::IVec2 { + self.next_mouse.pos - self.mouse.pos + } + fn lerp_delta(&self, time: Time) -> glam::IVec2 { + //these are deltas + let dm = self.mouse_delta().as_i64vec2(); + let t = (time - self.mouse.time).nanos(); + let dt = self.time_delta().nanos(); + ((dm * t) / dt).as_ivec2() + } } -#[derive(Clone,Debug)] -enum JumpDirection{ - Exactly(Planar64Vec3), - FromContactNormal, +#[derive(Clone, Debug)] +enum JumpDirection { + Exactly(Planar64Vec3), + FromContactNormal, } -impl JumpDirection{ - fn direction(&self,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,contact:&ContactCollision)->Planar64Vec3{ - match self{ - JumpDirection::FromContactNormal=>contact_normal(models,hitbox_mesh,contact), - &JumpDirection::Exactly(dir)=>dir, - } - } +impl JumpDirection { + fn direction( + &self, + models: &PhysicsModels, + hitbox_mesh: &HitboxMesh, + contact: &ContactCollision, + ) -> Planar64Vec3 { + match self { + JumpDirection::FromContactNormal => contact_normal(models, hitbox_mesh, contact), + &JumpDirection::Exactly(dir) => dir, + } + } } -#[derive(Clone,Debug)] -enum TransientAcceleration{ - Reached, - Reachable{ - acceleration:Planar64Vec3, - time:Time, - }, - //walk target will never be reached - Unreachable{ - acceleration:Planar64Vec3, - } +#[derive(Clone, Debug)] +enum TransientAcceleration { + Reached, + Reachable { + acceleration: Planar64Vec3, + time: Time, + }, + //walk target will never be reached + Unreachable { + acceleration: Planar64Vec3, + }, } -#[derive(Clone,Debug)] -struct ContactMoveState{ - jump_direction:JumpDirection, - contact:ContactCollision, - target:TransientAcceleration, +#[derive(Clone, Debug)] +struct ContactMoveState { + jump_direction: JumpDirection, + contact: ContactCollision, + target: TransientAcceleration, } -impl TransientAcceleration{ - fn with_target_diff(target_diff:Planar64Vec3,accel:Planar64,time:Time)->Self{ - if target_diff==vec3::ZERO{ - TransientAcceleration::Reached - }else{ - //normal friction acceleration is clippedAcceleration.dot(normal)*friction - TransientAcceleration::Reachable{ - acceleration:target_diff.with_length(accel).divide().fix_1(), - time:time+Time::from((target_diff.length()/accel).divide().fix_1()) - } - } - } - fn ground(walk_settings:&gameplay_style::WalkSettings,body:&Body,gravity:Planar64Vec3,target_velocity:Planar64Vec3)->Self{ - let target_diff=target_velocity-body.velocity; - //precalculate accel - let accel=walk_settings.accel(target_diff,gravity); - Self::with_target_diff(target_diff,accel,body.time) - } - fn ladder(ladder_settings:&gameplay_style::LadderSettings,body:&Body,gravity:Planar64Vec3,target_velocity:Planar64Vec3)->Self{ - let target_diff=target_velocity-body.velocity; - let accel=ladder_settings.accel(target_diff,gravity); - Self::with_target_diff(target_diff,accel,body.time) - } - fn acceleration(&self)->Planar64Vec3{ - match self{ - TransientAcceleration::Reached=>vec3::ZERO, - &TransientAcceleration::Reachable{acceleration,time:_}=>acceleration, - &TransientAcceleration::Unreachable{acceleration}=>acceleration, - } - } +impl TransientAcceleration { + fn with_target_diff(target_diff: Planar64Vec3, accel: Planar64, time: Time) -> Self { + if target_diff == vec3::ZERO { + TransientAcceleration::Reached + } else { + //normal friction acceleration is clippedAcceleration.dot(normal)*friction + TransientAcceleration::Reachable { + acceleration: target_diff.with_length(accel).divide().fix_1(), + time: time + Time::from((target_diff.length() / accel).divide().fix_1()), + } + } + } + fn ground( + walk_settings: &gameplay_style::WalkSettings, + body: &Body, + gravity: Planar64Vec3, + target_velocity: Planar64Vec3, + ) -> Self { + let target_diff = target_velocity - body.velocity; + //precalculate accel + let accel = walk_settings.accel(target_diff, gravity); + Self::with_target_diff(target_diff, accel, body.time) + } + fn ladder( + ladder_settings: &gameplay_style::LadderSettings, + body: &Body, + gravity: Planar64Vec3, + target_velocity: Planar64Vec3, + ) -> Self { + let target_diff = target_velocity - body.velocity; + let accel = ladder_settings.accel(target_diff, gravity); + Self::with_target_diff(target_diff, accel, body.time) + } + fn acceleration(&self) -> Planar64Vec3 { + match self { + TransientAcceleration::Reached => vec3::ZERO, + &TransientAcceleration::Reachable { + acceleration, + time: _, + } => acceleration, + &TransientAcceleration::Unreachable { acceleration } => acceleration, + } + } } -impl ContactMoveState{ - fn ground(walk_settings:&gameplay_style::WalkSettings,body:&Body,gravity:Planar64Vec3,target_velocity:Planar64Vec3,contact:ContactCollision)->Self{ - Self{ - target:TransientAcceleration::ground(walk_settings,body,gravity,target_velocity), - contact, - jump_direction:JumpDirection::Exactly(vec3::Y), - } - } - fn ladder(ladder_settings:&gameplay_style::LadderSettings,body:&Body,gravity:Planar64Vec3,target_velocity:Planar64Vec3,contact:ContactCollision)->Self{ - Self{//,style,velocity,normal,style.ladder_speed,style.ladder_accel - target:TransientAcceleration::ladder(ladder_settings,body,gravity,target_velocity), - contact, - jump_direction:JumpDirection::FromContactNormal, - } - } +impl ContactMoveState { + fn ground( + walk_settings: &gameplay_style::WalkSettings, + body: &Body, + gravity: Planar64Vec3, + target_velocity: Planar64Vec3, + contact: ContactCollision, + ) -> Self { + Self { + target: TransientAcceleration::ground(walk_settings, body, gravity, target_velocity), + contact, + jump_direction: JumpDirection::Exactly(vec3::Y), + } + } + fn ladder( + ladder_settings: &gameplay_style::LadderSettings, + body: &Body, + gravity: Planar64Vec3, + target_velocity: Planar64Vec3, + contact: ContactCollision, + ) -> Self { + Self { + //,style,velocity,normal,style.ladder_speed,style.ladder_accel + target: TransientAcceleration::ladder(ladder_settings, body, gravity, target_velocity), + contact, + jump_direction: JumpDirection::FromContactNormal, + } + } } -fn ground_things(walk_settings:&gameplay_style::WalkSettings,contact:&ContactCollision,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState)->(Planar64Vec3,Planar64Vec3){ - let normal=contact_normal(models,hitbox_mesh,contact); - let gravity=touching.base_acceleration(models,style,camera,input_state); - let control_dir=style.get_y_control_dir(camera,input_state.controls); - let mut target_velocity=walk_settings.get_walk_target_velocity(control_dir,normal); - touching.constrain_velocity(models,hitbox_mesh,&mut target_velocity); - (gravity,target_velocity) +fn ground_things( + walk_settings: &gameplay_style::WalkSettings, + contact: &ContactCollision, + touching: &TouchingState, + models: &PhysicsModels, + hitbox_mesh: &HitboxMesh, + style: &StyleModifiers, + camera: &PhysicsCamera, + input_state: &InputState, +) -> (Planar64Vec3, Planar64Vec3) { + let normal = contact_normal(models, hitbox_mesh, contact); + let gravity = touching.base_acceleration(models, style, camera, input_state); + let control_dir = style.get_y_control_dir(camera, input_state.controls); + let mut target_velocity = walk_settings.get_walk_target_velocity(control_dir, normal); + touching.constrain_velocity(models, hitbox_mesh, &mut target_velocity); + (gravity, target_velocity) } -fn ladder_things(ladder_settings:&gameplay_style::LadderSettings,contact:&ContactCollision,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState)->(Planar64Vec3,Planar64Vec3){ - let normal=contact_normal(models,hitbox_mesh,contact); - let gravity=touching.base_acceleration(models,style,camera,input_state); - let control_dir=style.get_y_control_dir(camera,input_state.controls); - let mut target_velocity=ladder_settings.get_ladder_target_velocity(control_dir,normal); - touching.constrain_velocity(models,hitbox_mesh,&mut target_velocity); - (gravity,target_velocity) +fn ladder_things( + ladder_settings: &gameplay_style::LadderSettings, + contact: &ContactCollision, + touching: &TouchingState, + models: &PhysicsModels, + hitbox_mesh: &HitboxMesh, + style: &StyleModifiers, + camera: &PhysicsCamera, + input_state: &InputState, +) -> (Planar64Vec3, Planar64Vec3) { + let normal = contact_normal(models, hitbox_mesh, contact); + let gravity = touching.base_acceleration(models, style, camera, input_state); + let control_dir = style.get_y_control_dir(camera, input_state.controls); + let mut target_velocity = ladder_settings.get_ladder_target_velocity(control_dir, normal); + touching.constrain_velocity(models, hitbox_mesh, &mut target_velocity); + (gravity, target_velocity) } #[derive(Default)] -struct PhysicsModels{ - meshes:HashMap, - contact_models:HashMap, - intersect_models:HashMap, - contact_attributes:HashMap, - intersect_attributes:HashMap, +struct PhysicsModels { + meshes: HashMap, + contact_models: HashMap, + intersect_models: HashMap, + contact_attributes: HashMap, + intersect_attributes: HashMap, } -impl PhysicsModels{ - fn clear(&mut self){ - self.meshes.clear(); - self.contact_models.clear(); - self.intersect_models.clear(); - self.contact_attributes.clear(); - self.intersect_attributes.clear(); - } - fn mesh(&self,convex_mesh_id:ConvexMeshId)->TransformedMesh{ - let (mesh_id,transform)=match convex_mesh_id.model_id{ - PhysicsModelId::Contact(model_id)=>{ - let model=&self.contact_models[&model_id]; - (&model.mesh_id,&model.transform) - }, - PhysicsModelId::Intersect(model_id)=>{ - let model=&self.intersect_models[&model_id]; - (&model.mesh_id,&model.transform) - }, - }; - TransformedMesh::new( - self.meshes[mesh_id].submesh_view(convex_mesh_id.submesh_id), - transform - ) - } - //it's a bit weird to have three functions, but it's always going to be one of these - fn contact_mesh(&self,contact:&ContactCollision)->TransformedMesh{ - let model=&self.contact_models[&contact.model_id]; - TransformedMesh::new( - self.meshes[&model.mesh_id].submesh_view(contact.submesh_id), - &model.transform - ) - } - fn intersect_mesh(&self,intersect:&IntersectCollision)->TransformedMesh{ - let model=&self.intersect_models[&intersect.model_id]; - TransformedMesh::new( - self.meshes[&model.mesh_id].submesh_view(intersect.submesh_id), - &model.transform - ) - } - fn get_model_transform(&self,model_id:ModelId)->Option<&PhysicsMeshTransform>{ - //ModelId can possibly be a decoration - match self.contact_models.get(&ContactModelId::new(model_id.get())){ - Some(model)=>Some(&model.transform), - None=>self.intersect_models.get(&IntersectModelId::new(model_id.get())) - .map(|model|&model.transform), - } - } - fn contact_model(&self,model_id:ContactModelId)->&ContactModel{ - &self.contact_models[&model_id] - } - fn intersect_model(&self,model_id:IntersectModelId)->&IntersectModel{ - &self.intersect_models[&model_id] - } - fn contact_attr(&self,model_id:ContactModelId)->&gameplay_attributes::ContactAttributes{ - &self.contact_attributes[&self.contact_models[&model_id].attr_id] - } - fn intersect_attr(&self,model_id:IntersectModelId)->&gameplay_attributes::IntersectAttributes{ - &self.intersect_attributes[&self.intersect_models[&model_id].attr_id] - } +impl PhysicsModels { + fn clear(&mut self) { + self.meshes.clear(); + self.contact_models.clear(); + self.intersect_models.clear(); + self.contact_attributes.clear(); + self.intersect_attributes.clear(); + } + fn mesh(&self, convex_mesh_id: ConvexMeshId) -> TransformedMesh { + let (mesh_id, transform) = match convex_mesh_id.model_id { + PhysicsModelId::Contact(model_id) => { + let model = &self.contact_models[&model_id]; + (&model.mesh_id, &model.transform) + } + PhysicsModelId::Intersect(model_id) => { + let model = &self.intersect_models[&model_id]; + (&model.mesh_id, &model.transform) + } + }; + TransformedMesh::new( + self.meshes[mesh_id].submesh_view(convex_mesh_id.submesh_id), + transform, + ) + } + //it's a bit weird to have three functions, but it's always going to be one of these + fn contact_mesh(&self, contact: &ContactCollision) -> TransformedMesh { + let model = &self.contact_models[&contact.model_id]; + TransformedMesh::new( + self.meshes[&model.mesh_id].submesh_view(contact.submesh_id), + &model.transform, + ) + } + fn intersect_mesh(&self, intersect: &IntersectCollision) -> TransformedMesh { + let model = &self.intersect_models[&intersect.model_id]; + TransformedMesh::new( + self.meshes[&model.mesh_id].submesh_view(intersect.submesh_id), + &model.transform, + ) + } + fn get_model_transform(&self, model_id: ModelId) -> Option<&PhysicsMeshTransform> { + //ModelId can possibly be a decoration + match self + .contact_models + .get(&ContactModelId::new(model_id.get())) + { + Some(model) => Some(&model.transform), + None => self + .intersect_models + .get(&IntersectModelId::new(model_id.get())) + .map(|model| &model.transform), + } + } + fn contact_model(&self, model_id: ContactModelId) -> &ContactModel { + &self.contact_models[&model_id] + } + fn intersect_model(&self, model_id: IntersectModelId) -> &IntersectModel { + &self.intersect_models[&model_id] + } + fn contact_attr(&self, model_id: ContactModelId) -> &gameplay_attributes::ContactAttributes { + &self.contact_attributes[&self.contact_models[&model_id].attr_id] + } + fn intersect_attr( + &self, + model_id: IntersectModelId, + ) -> &gameplay_attributes::IntersectAttributes { + &self.intersect_attributes[&self.intersect_models[&model_id].attr_id] + } } -#[derive(Clone,Copy,Debug)] -pub struct PhysicsCamera{ - //punch: Planar64Vec3, - //punch_velocity: Planar64Vec3, - sensitivity:Ratio64Vec2,//dots to Angle32 ratios - clamped_mouse_pos:glam::IVec2,//angles are calculated from this cumulative value - //angle limits could be an enum + struct that defines whether it's limited and selects clamp or wrap depending - // enum AngleLimit{ - // Unlimited, - // Limited{lower:Angle32,upper:Angle32}, - // } - //pitch_limit:AngleLimit, - //yaw_limit:AngleLimit, +#[derive(Clone, Copy, Debug)] +pub struct PhysicsCamera { + //punch: Planar64Vec3, + //punch_velocity: Planar64Vec3, + sensitivity: Ratio64Vec2, //dots to Angle32 ratios + clamped_mouse_pos: glam::IVec2, //angles are calculated from this cumulative value + //angle limits could be an enum + struct that defines whether it's limited and selects clamp or wrap depending + // enum AngleLimit{ + // Unlimited, + // Limited{lower:Angle32,upper:Angle32}, + // } + //pitch_limit:AngleLimit, + //yaw_limit:AngleLimit, } -impl PhysicsCamera{ - const ANGLE_PITCH_LOWER_LIMIT:Angle32=Angle32::NEG_FRAC_PI_2; - const ANGLE_PITCH_UPPER_LIMIT:Angle32=Angle32::FRAC_PI_2; - pub fn move_mouse(&mut self,mouse_delta:glam::IVec2){ - let mut unclamped_mouse_pos=self.clamped_mouse_pos+mouse_delta; - unclamped_mouse_pos.y=unclamped_mouse_pos.y.clamp( - self.sensitivity.y.rhs_div_int(Self::ANGLE_PITCH_LOWER_LIMIT.get() as i64) as i32, - self.sensitivity.y.rhs_div_int(Self::ANGLE_PITCH_UPPER_LIMIT.get() as i64) as i32, - ); - self.clamped_mouse_pos=unclamped_mouse_pos; - } - pub fn simulate_move_angles(&self,mouse_delta:glam::IVec2)->glam::Vec2 { - let a=-self.sensitivity.mul_int((self.clamped_mouse_pos+mouse_delta).as_i64vec2()); - let ax=Angle32::wrap_from_i64(a.x); - let ay=Angle32::clamp_from_i64(a.y) - //clamp to actual vertical cam limit - .clamp(Self::ANGLE_PITCH_LOWER_LIMIT,Self::ANGLE_PITCH_UPPER_LIMIT); - return glam::vec2(ax.into(),ay.into()); - } - #[inline] - fn get_rotation(&self,mouse_pos:glam::IVec2)->Planar64Mat3{ - let a=-self.sensitivity.mul_int(mouse_pos.as_i64vec2()); - let ax=Angle32::wrap_from_i64(a.x); - let ay=Angle32::clamp_from_i64(a.y) - //clamp to actual vertical cam limit - .clamp(Self::ANGLE_PITCH_LOWER_LIMIT,Self::ANGLE_PITCH_UPPER_LIMIT); - mat3::from_rotation_yx(ax,ay) - } - fn rotation(&self)->Planar64Mat3{ - self.get_rotation(self.clamped_mouse_pos) - } - fn simulate_move_rotation(&self,mouse_delta:glam::IVec2)->Planar64Mat3{ - self.get_rotation(self.clamped_mouse_pos+mouse_delta) - } - fn get_rotation_y(&self,mouse_pos_x:i32)->Planar64Mat3{ - let ax=-self.sensitivity.x.mul_int(mouse_pos_x as i64); - mat3::from_rotation_y(Angle32::wrap_from_i64(ax)) - } - fn rotation_y(&self)->Planar64Mat3{ - self.get_rotation_y(self.clamped_mouse_pos.x) - } - fn simulate_move_rotation_y(&self,mouse_delta_x:i32)->Planar64Mat3{ - self.get_rotation_y(self.clamped_mouse_pos.x+mouse_delta_x) - } +impl PhysicsCamera { + const ANGLE_PITCH_LOWER_LIMIT: Angle32 = Angle32::NEG_FRAC_PI_2; + const ANGLE_PITCH_UPPER_LIMIT: Angle32 = Angle32::FRAC_PI_2; + pub fn move_mouse(&mut self, mouse_delta: glam::IVec2) { + let mut unclamped_mouse_pos = self.clamped_mouse_pos + mouse_delta; + unclamped_mouse_pos.y = unclamped_mouse_pos.y.clamp( + self.sensitivity + .y + .rhs_div_int(Self::ANGLE_PITCH_LOWER_LIMIT.get() as i64) as i32, + self.sensitivity + .y + .rhs_div_int(Self::ANGLE_PITCH_UPPER_LIMIT.get() as i64) as i32, + ); + self.clamped_mouse_pos = unclamped_mouse_pos; + } + pub fn simulate_move_angles(&self, mouse_delta: glam::IVec2) -> glam::Vec2 { + let a = -self + .sensitivity + .mul_int((self.clamped_mouse_pos + mouse_delta).as_i64vec2()); + let ax = Angle32::wrap_from_i64(a.x); + let ay = Angle32::clamp_from_i64(a.y) + //clamp to actual vertical cam limit + .clamp(Self::ANGLE_PITCH_LOWER_LIMIT, Self::ANGLE_PITCH_UPPER_LIMIT); + return glam::vec2(ax.into(), ay.into()); + } + #[inline] + fn get_rotation(&self, mouse_pos: glam::IVec2) -> Planar64Mat3 { + let a = -self.sensitivity.mul_int(mouse_pos.as_i64vec2()); + let ax = Angle32::wrap_from_i64(a.x); + let ay = Angle32::clamp_from_i64(a.y) + //clamp to actual vertical cam limit + .clamp(Self::ANGLE_PITCH_LOWER_LIMIT, Self::ANGLE_PITCH_UPPER_LIMIT); + mat3::from_rotation_yx(ax, ay) + } + fn rotation(&self) -> Planar64Mat3 { + self.get_rotation(self.clamped_mouse_pos) + } + fn simulate_move_rotation(&self, mouse_delta: glam::IVec2) -> Planar64Mat3 { + self.get_rotation(self.clamped_mouse_pos + mouse_delta) + } + fn get_rotation_y(&self, mouse_pos_x: i32) -> Planar64Mat3 { + let ax = -self.sensitivity.x.mul_int(mouse_pos_x as i64); + mat3::from_rotation_y(Angle32::wrap_from_i64(ax)) + } + fn rotation_y(&self) -> Planar64Mat3 { + self.get_rotation_y(self.clamped_mouse_pos.x) + } + fn simulate_move_rotation_y(&self, mouse_delta_x: i32) -> Planar64Mat3 { + self.get_rotation_y(self.clamped_mouse_pos.x + mouse_delta_x) + } } -impl std::default::Default for PhysicsCamera{ - fn default()->Self{ - Self{ - sensitivity:Ratio64Vec2::ONE*200_000, - clamped_mouse_pos:glam::IVec2::ZERO, - } - } +impl std::default::Default for PhysicsCamera { + fn default() -> Self { + Self { + sensitivity: Ratio64Vec2::ONE * 200_000, + clamped_mouse_pos: glam::IVec2::ZERO, + } + } } -mod gameplay{ - use super::{gameplay_modes,HashSet,HashMap,ModelId}; - pub enum JumpIncrementResult{ - Allowed, - ExceededLimit, - } - impl JumpIncrementResult{ - pub const fn is_allowed(self)->bool{ - matches!(self,JumpIncrementResult::Allowed) - } - } - #[derive(Clone,Debug)] - pub struct ModeState{ - mode_id:gameplay_modes::ModeId, - stage_id:gameplay_modes::StageId, - next_ordered_checkpoint_id:gameplay_modes::CheckpointId,//which OrderedCheckpoint model_id you must pass next (if 0 you haven't passed OrderedCheckpoint0) - unordered_checkpoints:HashSet, - jump_counts:HashMap,//model_id -> jump count - } - impl ModeState{ - pub const fn get_mode_id(&self)->gameplay_modes::ModeId{ - self.mode_id - } - pub const fn get_stage_id(&self)->gameplay_modes::StageId{ - self.stage_id - } - pub const fn get_next_ordered_checkpoint_id(&self)->gameplay_modes::CheckpointId{ - self.next_ordered_checkpoint_id - } - fn increment_jump_count(&mut self,model_id:ModelId)->u32{ - *self.jump_counts.entry(model_id).and_modify(|c|*c+=1).or_insert(1) - } - pub fn try_increment_jump_count(&mut self,model_id:ModelId,jump_limit:Option)->JumpIncrementResult{ - match jump_limit{ - Some(jump_limit) if (jump_limit as u32)JumpIncrementResult::ExceededLimit, - _=>JumpIncrementResult::Allowed, - } - } - pub const fn ordered_checkpoint_count(&self)->u32{ - self.next_ordered_checkpoint_id.get() - } - pub fn unordered_checkpoint_count(&self)->u32{ - self.unordered_checkpoints.len() as u32 - } - pub fn set_mode_id(&mut self,mode_id:gameplay_modes::ModeId){ - self.clear(); - self.mode_id=mode_id; - } - pub fn set_stage_id(&mut self,stage_id:gameplay_modes::StageId){ - self.clear_checkpoints(); - self.stage_id=stage_id; - } - pub fn accumulate_ordered_checkpoint(&mut self,stage:&gameplay_modes::Stage,model_id:ModelId){ - if stage.is_next_ordered_checkpoint(self.get_next_ordered_checkpoint_id(),model_id){ - self.next_ordered_checkpoint_id=gameplay_modes::CheckpointId::new(self.next_ordered_checkpoint_id.get()+1); - } - } - pub fn accumulate_unordered_checkpoint(&mut self,stage:&gameplay_modes::Stage,model_id:ModelId){ - if stage.is_unordered_checkpoint(model_id){ - self.unordered_checkpoints.insert(model_id); - } - } - pub fn clear(&mut self){ - self.clear_jump_counts(); - self.clear_checkpoints(); - } - pub fn clear_jump_counts(&mut self){ - self.jump_counts.clear(); - } - pub fn clear_checkpoints(&mut self){ - self.next_ordered_checkpoint_id=gameplay_modes::CheckpointId::FIRST; - self.unordered_checkpoints.clear(); - } - } - impl std::default::Default for ModeState{ - fn default()->Self{ - Self{ - mode_id:gameplay_modes::ModeId::MAIN, - stage_id:gameplay_modes::StageId::FIRST, - next_ordered_checkpoint_id:gameplay_modes::CheckpointId::FIRST, - unordered_checkpoints:HashSet::new(), - jump_counts:HashMap::new(), - } - } - } +mod gameplay { + use super::{gameplay_modes, HashMap, HashSet, ModelId}; + pub enum JumpIncrementResult { + Allowed, + ExceededLimit, + } + impl JumpIncrementResult { + pub const fn is_allowed(self) -> bool { + matches!(self, JumpIncrementResult::Allowed) + } + } + #[derive(Clone, Debug)] + pub struct ModeState { + mode_id: gameplay_modes::ModeId, + stage_id: gameplay_modes::StageId, + next_ordered_checkpoint_id: gameplay_modes::CheckpointId, //which OrderedCheckpoint model_id you must pass next (if 0 you haven't passed OrderedCheckpoint0) + unordered_checkpoints: HashSet, + jump_counts: HashMap, //model_id -> jump count + } + impl ModeState { + pub const fn get_mode_id(&self) -> gameplay_modes::ModeId { + self.mode_id + } + pub const fn get_stage_id(&self) -> gameplay_modes::StageId { + self.stage_id + } + pub const fn get_next_ordered_checkpoint_id(&self) -> gameplay_modes::CheckpointId { + self.next_ordered_checkpoint_id + } + fn increment_jump_count(&mut self, model_id: ModelId) -> u32 { + *self + .jump_counts + .entry(model_id) + .and_modify(|c| *c += 1) + .or_insert(1) + } + pub fn try_increment_jump_count( + &mut self, + model_id: ModelId, + jump_limit: Option, + ) -> JumpIncrementResult { + match jump_limit { + Some(jump_limit) if (jump_limit as u32) < self.increment_jump_count(model_id) => { + JumpIncrementResult::ExceededLimit + } + _ => JumpIncrementResult::Allowed, + } + } + pub const fn ordered_checkpoint_count(&self) -> u32 { + self.next_ordered_checkpoint_id.get() + } + pub fn unordered_checkpoint_count(&self) -> u32 { + self.unordered_checkpoints.len() as u32 + } + pub fn set_mode_id(&mut self, mode_id: gameplay_modes::ModeId) { + self.clear(); + self.mode_id = mode_id; + } + pub fn set_stage_id(&mut self, stage_id: gameplay_modes::StageId) { + self.clear_checkpoints(); + self.stage_id = stage_id; + } + pub fn accumulate_ordered_checkpoint( + &mut self, + stage: &gameplay_modes::Stage, + model_id: ModelId, + ) { + if stage.is_next_ordered_checkpoint(self.get_next_ordered_checkpoint_id(), model_id) { + self.next_ordered_checkpoint_id = + gameplay_modes::CheckpointId::new(self.next_ordered_checkpoint_id.get() + 1); + } + } + pub fn accumulate_unordered_checkpoint( + &mut self, + stage: &gameplay_modes::Stage, + model_id: ModelId, + ) { + if stage.is_unordered_checkpoint(model_id) { + self.unordered_checkpoints.insert(model_id); + } + } + pub fn clear(&mut self) { + self.clear_jump_counts(); + self.clear_checkpoints(); + } + pub fn clear_jump_counts(&mut self) { + self.jump_counts.clear(); + } + pub fn clear_checkpoints(&mut self) { + self.next_ordered_checkpoint_id = gameplay_modes::CheckpointId::FIRST; + self.unordered_checkpoints.clear(); + } + } + impl std::default::Default for ModeState { + fn default() -> Self { + Self { + mode_id: gameplay_modes::ModeId::MAIN, + stage_id: gameplay_modes::StageId::FIRST, + next_ordered_checkpoint_id: gameplay_modes::CheckpointId::FIRST, + unordered_checkpoints: HashSet::new(), + jump_counts: HashMap::new(), + } + } + } } -#[derive(Clone,Debug)] -struct WorldState{} +#[derive(Clone, Debug)] +struct WorldState {} -struct HitboxMesh{ - halfsize:Planar64Vec3, - mesh:PhysicsMesh, - transform:PhysicsMeshTransform, +struct HitboxMesh { + halfsize: Planar64Vec3, + mesh: PhysicsMesh, + transform: PhysicsMeshTransform, } -impl HitboxMesh{ - fn new(mesh:PhysicsMesh,transform:integer::Planar64Affine3)->Self{ - //calculate extents - let mut aabb=aabb::Aabb::default(); - let transform=PhysicsMeshTransform::new(transform); - let transformed_mesh=TransformedMesh::new(mesh.complete_mesh_view(),&transform); - for vert in transformed_mesh.verts(){ - aabb.grow(vert.fix_1()); - } - Self{ - halfsize:aabb.size()>>1, - mesh, - transform, - } - } - #[inline] - const fn transformed_mesh(&self)->TransformedMesh{ - TransformedMesh::new(self.mesh.complete_mesh_view(),&self.transform) - } +impl HitboxMesh { + fn new(mesh: PhysicsMesh, transform: integer::Planar64Affine3) -> Self { + //calculate extents + let mut aabb = aabb::Aabb::default(); + let transform = PhysicsMeshTransform::new(transform); + let transformed_mesh = TransformedMesh::new(mesh.complete_mesh_view(), &transform); + for vert in transformed_mesh.verts() { + aabb.grow(vert.fix_1()); + } + Self { + halfsize: aabb.size() >> 1, + mesh, + transform, + } + } + #[inline] + const fn transformed_mesh(&self) -> TransformedMesh { + TransformedMesh::new(self.mesh.complete_mesh_view(), &self.transform) + } } -trait StyleHelper{ - fn get_control(&self,control:Controls,controls:Controls)->bool; - fn get_control_dir(&self,controls:Controls)->Planar64Vec3; - fn get_y_control_dir(&self,camera:&PhysicsCamera,controls:Controls)->Planar64Vec3; - fn get_propulsion_control_dir(&self,camera:&PhysicsCamera,controls:Controls)->Planar64Vec3; - fn calculate_mesh(&self)->HitboxMesh; +trait StyleHelper { + fn get_control(&self, control: Controls, controls: Controls) -> bool; + fn get_control_dir(&self, controls: Controls) -> Planar64Vec3; + fn get_y_control_dir(&self, camera: &PhysicsCamera, controls: Controls) -> Planar64Vec3; + fn get_propulsion_control_dir( + &self, + camera: &PhysicsCamera, + controls: Controls, + ) -> Planar64Vec3; + fn calculate_mesh(&self) -> HitboxMesh; } -impl StyleHelper for StyleModifiers{ - fn get_control(&self,control:Controls,controls:Controls)->bool{ - controls.intersection(self.controls_mask).contains(control) - } +impl StyleHelper for StyleModifiers { + fn get_control(&self, control: Controls, controls: Controls) -> bool { + controls.intersection(self.controls_mask).contains(control) + } - fn get_control_dir(&self,controls:Controls)->Planar64Vec3{ - //don't get fancy just do it - let mut control_dir:Planar64Vec3=vec3::ZERO; - //Apply mask after held check so you can require non-allowed keys to be held for some reason - let controls=controls.intersection(self.controls_mask); - if controls.contains(Controls::MoveForward){ - control_dir+=Self::FORWARD_DIR; - } - if controls.contains(Controls::MoveBackward){ - control_dir-=Self::FORWARD_DIR; - } - if controls.contains(Controls::MoveLeft){ - control_dir-=Self::RIGHT_DIR; - } - if controls.contains(Controls::MoveRight){ - control_dir+=Self::RIGHT_DIR; - } - if controls.contains(Controls::MoveUp){ - control_dir+=Self::UP_DIR; - } - if controls.contains(Controls::MoveDown){ - control_dir-=Self::UP_DIR; - } - return control_dir - } + fn get_control_dir(&self, controls: Controls) -> Planar64Vec3 { + //don't get fancy just do it + let mut control_dir: Planar64Vec3 = vec3::ZERO; + //Apply mask after held check so you can require non-allowed keys to be held for some reason + let controls = controls.intersection(self.controls_mask); + if controls.contains(Controls::MoveForward) { + control_dir += Self::FORWARD_DIR; + } + if controls.contains(Controls::MoveBackward) { + control_dir -= Self::FORWARD_DIR; + } + if controls.contains(Controls::MoveLeft) { + control_dir -= Self::RIGHT_DIR; + } + if controls.contains(Controls::MoveRight) { + control_dir += Self::RIGHT_DIR; + } + if controls.contains(Controls::MoveUp) { + control_dir += Self::UP_DIR; + } + if controls.contains(Controls::MoveDown) { + control_dir -= Self::UP_DIR; + } + return control_dir; + } - fn get_y_control_dir(&self,camera:&PhysicsCamera,controls:Controls)->Planar64Vec3{ - (camera.rotation_y()*self.get_control_dir(controls)).fix_1() - } + fn get_y_control_dir(&self, camera: &PhysicsCamera, controls: Controls) -> Planar64Vec3 { + (camera.rotation_y() * self.get_control_dir(controls)).fix_1() + } - fn get_propulsion_control_dir(&self,camera:&PhysicsCamera,controls:Controls)->Planar64Vec3{ - //don't interpolate this! discrete mouse movement, constant acceleration - (camera.rotation()*self.get_control_dir(controls)).fix_1() - } - fn calculate_mesh(&self)->HitboxMesh{ - let mesh=match self.hitbox.mesh{ - gameplay_style::HitboxMesh::Box=>PhysicsMesh::unit_cube(), - gameplay_style::HitboxMesh::Cylinder=>PhysicsMesh::unit_cylinder(), - }; - let transform=integer::Planar64Affine3::new( - mat3::from_diagonal(self.hitbox.halfsize), - vec3::ZERO - ); - HitboxMesh::new(mesh,transform) - } + fn get_propulsion_control_dir( + &self, + camera: &PhysicsCamera, + controls: Controls, + ) -> Planar64Vec3 { + //don't interpolate this! discrete mouse movement, constant acceleration + (camera.rotation() * self.get_control_dir(controls)).fix_1() + } + fn calculate_mesh(&self) -> HitboxMesh { + let mesh = match self.hitbox.mesh { + gameplay_style::HitboxMesh::Box => PhysicsMesh::unit_cube(), + gameplay_style::HitboxMesh::Cylinder => PhysicsMesh::unit_cylinder(), + }; + let transform = + integer::Planar64Affine3::new(mat3::from_diagonal(self.hitbox.halfsize), vec3::ZERO); + HitboxMesh::new(mesh, transform) + } } -#[derive(Clone,Debug)] -enum MoveState{ - Air, - Walk(ContactMoveState), - Ladder(ContactMoveState), - Water, - Fly, +#[derive(Clone, Debug)] +enum MoveState { + Air, + Walk(ContactMoveState), + Ladder(ContactMoveState), + Water, + Fly, } -impl MoveState{ - //call this after state.move_state is changed - fn apply_enum(&self,body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState){ - match self{ - MoveState::Fly=>body.acceleration=vec3::ZERO, - MoveState::Air=>{ - //calculate base acceleration - let a=touching.base_acceleration(models,style,camera,input_state); - //set_acceleration clips according to contacts - set_acceleration(body,touching,models,hitbox_mesh,a); - }, - _=>(), - } - } - //function to coerce &mut self into &self - fn apply_to_body(&self,body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState){ - match self{ - MoveState::Air=>(), - MoveState::Water=>(), - MoveState::Fly=>{ - //set velocity according to current control state - let v=style.get_propulsion_control_dir(camera,input_state.controls)*80; - //set_velocity clips velocity according to current touching state - set_velocity(body,touching,models,hitbox_mesh,v); - }, - MoveState::Walk(walk_state) - |MoveState::Ladder(walk_state) - =>{ - //accelerate towards walk target or do nothing - let a=walk_state.target.acceleration(); - set_acceleration(body,touching,models,hitbox_mesh,a); - }, - } - } - /// changes the move state - fn apply_input(&mut self,body:&Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState){ - match self{ - MoveState::Fly - |MoveState::Air - |MoveState::Water=>(), - MoveState::Walk(ContactMoveState{target,contact,jump_direction:_})=>{ - if let Some(walk_settings)=&style.walk{ - let (gravity,target_velocity)=ground_things(walk_settings,contact,touching,models,hitbox_mesh,style,camera,input_state); - *target=TransientAcceleration::ground(walk_settings,body,gravity,target_velocity); - }else{ - panic!("ContactMoveState exists in style which does not allow walking!"); - } - }, - MoveState::Ladder(ContactMoveState{target,contact,jump_direction:_})=>{ - if let Some(ladder_settings)=&style.ladder{ - let (gravity,target_velocity)=ladder_things(ladder_settings,contact,touching,models,hitbox_mesh,style,camera,input_state); - *target=TransientAcceleration::ladder(ladder_settings,body,gravity,target_velocity); - }else{ - panic!("ContactMoveState exists in style which does not allow walking!"); - } - }, - } - } - fn get_walk_state(&self)->Option<&ContactMoveState>{ - match self{ - MoveState::Walk(walk_state) - |MoveState::Ladder(walk_state) - =>Some(walk_state), - MoveState::Air - |MoveState::Water - |MoveState::Fly - =>None, - } - } - fn next_move_instruction(&self,strafe:&Option,time:Time)->Option>{ - //check if you have a valid walk state and create an instruction - match self{ - MoveState::Walk(walk_state)|MoveState::Ladder(walk_state)=>match &walk_state.target{ - &TransientAcceleration::Reachable{acceleration:_,time}=>Some(TimedInstruction{ - time, - instruction:PhysicsInternalInstruction::ReachWalkTargetVelocity - }), - TransientAcceleration::Unreachable{acceleration:_} - |TransientAcceleration::Reached - =>None, - } - MoveState::Air=>strafe.as_ref().map(|strafe|{ - TimedInstruction{ - time:strafe.next_tick(time), - //only poll the physics if there is a before and after mouse event - instruction:PhysicsInternalInstruction::StrafeTick - } - }), - MoveState::Water=>None,//TODO - MoveState::Fly=>None, - } - } - //lmao idk this is convenient - fn apply_enum_and_input_and_body(&mut self,body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState){ - self.apply_enum(body,touching,models,hitbox_mesh,style,camera,input_state); - self.apply_input(body,touching,models,hitbox_mesh,style,camera,input_state); - self.apply_to_body(body,touching,models,hitbox_mesh,style,camera,input_state); - } - fn apply_enum_and_body(&mut self,body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState){ - self.apply_enum(body,touching,models,hitbox_mesh,style,camera,input_state); - self.apply_to_body(body,touching,models,hitbox_mesh,style,camera,input_state); - } - fn apply_input_and_body(&mut self,body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState){ - self.apply_input(body,touching,models,hitbox_mesh,style,camera,input_state); - self.apply_to_body(body,touching,models,hitbox_mesh,style,camera,input_state); - } - fn set_move_state(&mut self,move_state:MoveState,body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState){ - *self=move_state; - //this function call reads the above state that was just set - self.apply_enum_and_body(body,touching,models,hitbox_mesh,style,camera,input_state); - } - fn cull_velocity(&mut self,velocity:Planar64Vec3,body:&mut Body,touching:&mut TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState){ - //TODO: be more precise about contacts - if set_velocity_cull(body,touching,models,hitbox_mesh,velocity){ - //TODO do better - match self.get_walk_state(){ - //did you stop touching the thing you were walking on? - Some(walk_state)=>if !touching.contacts.contains(&walk_state.contact){ - self.set_move_state(MoveState::Air,body,touching,models,hitbox_mesh,style,camera,input_state); - }, - None=>self.apply_enum_and_body(body,touching,models,hitbox_mesh,style,camera,input_state), - } - } - } +impl MoveState { + //call this after state.move_state is changed + fn apply_enum( + &self, + body: &mut Body, + touching: &TouchingState, + models: &PhysicsModels, + hitbox_mesh: &HitboxMesh, + style: &StyleModifiers, + camera: &PhysicsCamera, + input_state: &InputState, + ) { + match self { + MoveState::Fly => body.acceleration = vec3::ZERO, + MoveState::Air => { + //calculate base acceleration + let a = touching.base_acceleration(models, style, camera, input_state); + //set_acceleration clips according to contacts + set_acceleration(body, touching, models, hitbox_mesh, a); + } + _ => (), + } + } + //function to coerce &mut self into &self + fn apply_to_body( + &self, + body: &mut Body, + touching: &TouchingState, + models: &PhysicsModels, + hitbox_mesh: &HitboxMesh, + style: &StyleModifiers, + camera: &PhysicsCamera, + input_state: &InputState, + ) { + match self { + MoveState::Air => (), + MoveState::Water => (), + MoveState::Fly => { + //set velocity according to current control state + let v = style.get_propulsion_control_dir(camera, input_state.controls) * 80; + //set_velocity clips velocity according to current touching state + set_velocity(body, touching, models, hitbox_mesh, v); + } + MoveState::Walk(walk_state) | MoveState::Ladder(walk_state) => { + //accelerate towards walk target or do nothing + let a = walk_state.target.acceleration(); + set_acceleration(body, touching, models, hitbox_mesh, a); + } + } + } + /// changes the move state + fn apply_input( + &mut self, + body: &Body, + touching: &TouchingState, + models: &PhysicsModels, + hitbox_mesh: &HitboxMesh, + style: &StyleModifiers, + camera: &PhysicsCamera, + input_state: &InputState, + ) { + match self { + MoveState::Fly | MoveState::Air | MoveState::Water => (), + MoveState::Walk(ContactMoveState { + target, + contact, + jump_direction: _, + }) => { + if let Some(walk_settings) = &style.walk { + let (gravity, target_velocity) = ground_things( + walk_settings, + contact, + touching, + models, + hitbox_mesh, + style, + camera, + input_state, + ); + *target = TransientAcceleration::ground( + walk_settings, + body, + gravity, + target_velocity, + ); + } else { + panic!("ContactMoveState exists in style which does not allow walking!"); + } + } + MoveState::Ladder(ContactMoveState { + target, + contact, + jump_direction: _, + }) => { + if let Some(ladder_settings) = &style.ladder { + let (gravity, target_velocity) = ladder_things( + ladder_settings, + contact, + touching, + models, + hitbox_mesh, + style, + camera, + input_state, + ); + *target = TransientAcceleration::ladder( + ladder_settings, + body, + gravity, + target_velocity, + ); + } else { + panic!("ContactMoveState exists in style which does not allow walking!"); + } + } + } + } + fn get_walk_state(&self) -> Option<&ContactMoveState> { + match self { + MoveState::Walk(walk_state) | MoveState::Ladder(walk_state) => Some(walk_state), + MoveState::Air | MoveState::Water | MoveState::Fly => None, + } + } + fn next_move_instruction( + &self, + strafe: &Option, + time: Time, + ) -> Option> { + //check if you have a valid walk state and create an instruction + match self { + MoveState::Walk(walk_state) | MoveState::Ladder(walk_state) => match &walk_state.target + { + &TransientAcceleration::Reachable { + acceleration: _, + time, + } => Some(TimedInstruction { + time, + instruction: PhysicsInternalInstruction::ReachWalkTargetVelocity, + }), + TransientAcceleration::Unreachable { acceleration: _ } + | TransientAcceleration::Reached => None, + }, + MoveState::Air => strafe.as_ref().map(|strafe| { + TimedInstruction { + time: strafe.next_tick(time), + //only poll the physics if there is a before and after mouse event + instruction: PhysicsInternalInstruction::StrafeTick, + } + }), + MoveState::Water => None, //TODO + MoveState::Fly => None, + } + } + //lmao idk this is convenient + fn apply_enum_and_input_and_body( + &mut self, + body: &mut Body, + touching: &TouchingState, + models: &PhysicsModels, + hitbox_mesh: &HitboxMesh, + style: &StyleModifiers, + camera: &PhysicsCamera, + input_state: &InputState, + ) { + self.apply_enum( + body, + touching, + models, + hitbox_mesh, + style, + camera, + input_state, + ); + self.apply_input( + body, + touching, + models, + hitbox_mesh, + style, + camera, + input_state, + ); + self.apply_to_body( + body, + touching, + models, + hitbox_mesh, + style, + camera, + input_state, + ); + } + fn apply_enum_and_body( + &mut self, + body: &mut Body, + touching: &TouchingState, + models: &PhysicsModels, + hitbox_mesh: &HitboxMesh, + style: &StyleModifiers, + camera: &PhysicsCamera, + input_state: &InputState, + ) { + self.apply_enum( + body, + touching, + models, + hitbox_mesh, + style, + camera, + input_state, + ); + self.apply_to_body( + body, + touching, + models, + hitbox_mesh, + style, + camera, + input_state, + ); + } + fn apply_input_and_body( + &mut self, + body: &mut Body, + touching: &TouchingState, + models: &PhysicsModels, + hitbox_mesh: &HitboxMesh, + style: &StyleModifiers, + camera: &PhysicsCamera, + input_state: &InputState, + ) { + self.apply_input( + body, + touching, + models, + hitbox_mesh, + style, + camera, + input_state, + ); + self.apply_to_body( + body, + touching, + models, + hitbox_mesh, + style, + camera, + input_state, + ); + } + fn set_move_state( + &mut self, + move_state: MoveState, + body: &mut Body, + touching: &TouchingState, + models: &PhysicsModels, + hitbox_mesh: &HitboxMesh, + style: &StyleModifiers, + camera: &PhysicsCamera, + input_state: &InputState, + ) { + *self = move_state; + //this function call reads the above state that was just set + self.apply_enum_and_body( + body, + touching, + models, + hitbox_mesh, + style, + camera, + input_state, + ); + } + fn cull_velocity( + &mut self, + velocity: Planar64Vec3, + body: &mut Body, + touching: &mut TouchingState, + models: &PhysicsModels, + hitbox_mesh: &HitboxMesh, + style: &StyleModifiers, + camera: &PhysicsCamera, + input_state: &InputState, + ) { + //TODO: be more precise about contacts + if set_velocity_cull(body, touching, models, hitbox_mesh, velocity) { + //TODO do better + match self.get_walk_state() { + //did you stop touching the thing you were walking on? + Some(walk_state) => { + if !touching.contacts.contains(&walk_state.contact) { + self.set_move_state( + MoveState::Air, + body, + touching, + models, + hitbox_mesh, + style, + camera, + input_state, + ); + } + } + None => self.apply_enum_and_body( + body, + touching, + models, + hitbox_mesh, + style, + camera, + input_state, + ), + } + } + } } -#[derive(Clone,Hash,Eq,PartialEq)] -enum PhysicsCollisionAttributes{ - Contact(gameplay_attributes::ContactAttributes), - Intersect(gameplay_attributes::IntersectAttributes), +#[derive(Clone, Hash, Eq, PartialEq)] +enum PhysicsCollisionAttributes { + Contact(gameplay_attributes::ContactAttributes), + Intersect(gameplay_attributes::IntersectAttributes), } struct NonPhysicsError; -impl TryFrom<&gameplay_attributes::CollisionAttributes> for PhysicsCollisionAttributes{ - type Error=NonPhysicsError; - fn try_from(value:&gameplay_attributes::CollisionAttributes)->Result{ - match value{ - gameplay_attributes::CollisionAttributes::Decoration=>Err(NonPhysicsError), - gameplay_attributes::CollisionAttributes::Contact(attr)=>Ok(Self::Contact(attr.clone())), - gameplay_attributes::CollisionAttributes::Intersect(attr)=>Ok(Self::Intersect(attr.clone())), - } - } +impl TryFrom<&gameplay_attributes::CollisionAttributes> for PhysicsCollisionAttributes { + type Error = NonPhysicsError; + fn try_from(value: &gameplay_attributes::CollisionAttributes) -> Result { + match value { + gameplay_attributes::CollisionAttributes::Decoration => Err(NonPhysicsError), + gameplay_attributes::CollisionAttributes::Contact(attr) => { + Ok(Self::Contact(attr.clone())) + } + gameplay_attributes::CollisionAttributes::Intersect(attr) => { + Ok(Self::Intersect(attr.clone())) + } + } + } } -#[derive(Clone,Copy,Hash,id::Id,Eq,PartialEq)] +#[derive(Clone, Copy, Hash, id::Id, Eq, PartialEq)] struct ContactAttributesId(u32); -impl Into for ContactAttributesId{ - fn into(self)->CollisionAttributesId{ - CollisionAttributesId::new(self.0) - } +impl Into for ContactAttributesId { + fn into(self) -> CollisionAttributesId { + CollisionAttributesId::new(self.0) + } } -impl From for ContactAttributesId{ - fn from(value:CollisionAttributesId)->Self{ - Self::new(value.get()) - } +impl From for ContactAttributesId { + fn from(value: CollisionAttributesId) -> Self { + Self::new(value.get()) + } } -#[derive(Clone,Copy,Hash,id::Id,Eq,PartialEq)] +#[derive(Clone, Copy, Hash, id::Id, Eq, PartialEq)] struct IntersectAttributesId(u32); -impl Into for IntersectAttributesId{ - fn into(self)->CollisionAttributesId{ - CollisionAttributesId::new(self.0) - } +impl Into for IntersectAttributesId { + fn into(self) -> CollisionAttributesId { + CollisionAttributesId::new(self.0) + } } -impl From for IntersectAttributesId{ - fn from(value:CollisionAttributesId)->Self{ - Self::new(value.get()) - } +impl From for IntersectAttributesId { + fn from(value: CollisionAttributesId) -> Self { + Self::new(value.get()) + } } -#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)] +#[derive(Debug, Clone, Copy, Hash, id::Id, Eq, PartialEq)] struct ContactModelId(u32); -impl Into for ContactModelId{ - fn into(self)->ModelId{ - ModelId::new(self.get()) - } +impl Into for ContactModelId { + fn into(self) -> ModelId { + ModelId::new(self.get()) + } } -#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)] +#[derive(Debug, Clone, Copy, Hash, id::Id, Eq, PartialEq)] struct IntersectModelId(u32); -impl Into for IntersectModelId{ - fn into(self)->ModelId{ - ModelId::new(self.get()) - } +impl Into for IntersectModelId { + fn into(self) -> ModelId { + ModelId::new(self.get()) + } } -#[derive(Debug,Clone,Copy,Hash,Eq,PartialEq)] -enum PhysicsModelId{ - Contact(ContactModelId), - Intersect(IntersectModelId), +#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] +enum PhysicsModelId { + Contact(ContactModelId), + Intersect(IntersectModelId), } -impl Into for PhysicsModelId{ - fn into(self)->ModelId{ - ModelId::new(match self{ - PhysicsModelId::Contact(model_id)=>model_id.get(), - PhysicsModelId::Intersect(model_id)=>model_id.get(), - }) - } +impl Into for PhysicsModelId { + fn into(self) -> ModelId { + ModelId::new(match self { + PhysicsModelId::Contact(model_id) => model_id.get(), + PhysicsModelId::Intersect(model_id) => model_id.get(), + }) + } } //unique physics meshes indexed by this -#[derive(Debug,Clone,Copy,Eq,Hash,PartialEq)] -struct ConvexMeshId{ - model_id:PhysicsModelId, - submesh_id:PhysicsSubmeshId, +#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)] +struct ConvexMeshId { + model_id: PhysicsModelId, + submesh_id: PhysicsSubmeshId, } -struct ContactModel{ - mesh_id:PhysicsMeshId, - attr_id:ContactAttributesId, - transform:PhysicsMeshTransform, +struct ContactModel { + mesh_id: PhysicsMeshId, + attr_id: ContactAttributesId, + transform: PhysicsMeshTransform, } -struct IntersectModel{ - mesh_id:PhysicsMeshId, - attr_id:IntersectAttributesId, - transform:PhysicsMeshTransform, +struct IntersectModel { + mesh_id: PhysicsMeshId, + attr_id: IntersectAttributesId, + transform: PhysicsMeshTransform, } -#[derive(Debug,Clone,Copy,Eq,Hash,PartialEq)] -struct ContactCollision{ - face_id:model_physics::MinkowskiFace, - model_id:ContactModelId, - submesh_id:PhysicsSubmeshId, +#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)] +struct ContactCollision { + face_id: model_physics::MinkowskiFace, + model_id: ContactModelId, + submesh_id: PhysicsSubmeshId, } -#[derive(Debug,Clone,Copy,Eq,Hash,PartialEq)] -struct IntersectCollision{ - model_id:IntersectModelId, - submesh_id:PhysicsSubmeshId, +#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)] +struct IntersectCollision { + model_id: IntersectModelId, + submesh_id: PhysicsSubmeshId, } -#[derive(Debug,Clone,Eq,Hash,PartialEq)] -enum Collision{ - Contact(ContactCollision), - Intersect(IntersectCollision), +#[derive(Debug, Clone, Eq, Hash, PartialEq)] +enum Collision { + Contact(ContactCollision), + Intersect(IntersectCollision), } -impl Collision{ - const fn new(convex_mesh_id:ConvexMeshId,face_id:model_physics::MinkowskiFace)->Self{ - match convex_mesh_id.model_id{ - PhysicsModelId::Contact(model_id)=>Collision::Contact(ContactCollision{model_id,submesh_id:convex_mesh_id.submesh_id,face_id}), - PhysicsModelId::Intersect(model_id)=>Collision::Intersect(IntersectCollision{model_id,submesh_id:convex_mesh_id.submesh_id}), - } - } +impl Collision { + const fn new(convex_mesh_id: ConvexMeshId, face_id: model_physics::MinkowskiFace) -> Self { + match convex_mesh_id.model_id { + PhysicsModelId::Contact(model_id) => Collision::Contact(ContactCollision { + model_id, + submesh_id: convex_mesh_id.submesh_id, + face_id, + }), + PhysicsModelId::Intersect(model_id) => Collision::Intersect(IntersectCollision { + model_id, + submesh_id: convex_mesh_id.submesh_id, + }), + } + } } -#[derive(Clone,Debug,Default)] -struct TouchingState{ - contacts:HashSet, - intersects:HashSet, +#[derive(Clone, Debug, Default)] +struct TouchingState { + contacts: HashSet, + intersects: HashSet, } -impl TouchingState{ - fn clear(&mut self){ - self.contacts.clear(); - self.intersects.clear(); - } - fn insert(&mut self,collision:Collision)->bool{ - match collision{ - Collision::Contact(collision)=>self.contacts.insert(collision), - Collision::Intersect(collision)=>self.intersects.insert(collision), - } - } - fn remove(&mut self,collision:&Collision)->bool{ - match collision{ - Collision::Contact(collision)=>self.contacts.remove(collision), - Collision::Intersect(collision)=>self.intersects.remove(collision), - } - } - fn base_acceleration(&self,models:&PhysicsModels,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState)->Planar64Vec3{ - let mut a=style.gravity; - if let Some(rocket_settings)=&style.rocket{ - a+=rocket_settings.acceleration(style.get_propulsion_control_dir(camera,input_state.controls)); - } - //add accelerators - for contact in &self.contacts{ - if let Some(accelerator)=&models.contact_attr(contact.model_id).general.accelerator{ - a+=accelerator.acceleration; - } - } - for intersect in &self.intersects{ - if let Some(accelerator)=&models.intersect_attr(intersect.model_id).general.accelerator{ - a+=accelerator.acceleration; - } - } - //TODO: add water - a - } - fn constrain_velocity(&self,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,velocity:&mut Planar64Vec3){ - let contacts=self.contacts.iter().map(|contact|{ - let n=contact_normal(models,hitbox_mesh,contact); - crate::push_solve::Contact{ - position:vec3::ZERO, - velocity:n, - normal:n, - } - }).collect(); - match crate::push_solve::push_solve(&contacts,*velocity){ - Some(new_velocity)=>*velocity=new_velocity, - None=>println!("Algorithm silently failing :)"), - } - } - fn constrain_acceleration(&self,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,acceleration:&mut Planar64Vec3){ - let contacts=self.contacts.iter().map(|contact|{ - let n=contact_normal(models,hitbox_mesh,contact); - crate::push_solve::Contact{ - position:vec3::ZERO, - velocity:n, - normal:n, - } - }).collect(); - match crate::push_solve::push_solve(&contacts,*acceleration){ - Some(new_acceleration)=>*acceleration=new_acceleration, - None=>println!("Algorithm silently failing :)"), - } - } - fn predict_collision_end(&self,collector:&mut instruction::InstructionCollector,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,body:&Body,time:Time){ - let relative_body=crate::body::VirtualBody::relative(&Body::ZERO,body).body(time); - for contact in &self.contacts{ - //detect face slide off - let model_mesh=models.contact_mesh(contact); - let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh()); - collector.collect(minkowski.predict_collision_face_out(&relative_body,collector.time(),contact.face_id).map(|(_face,time)|{ - TimedInstruction{ - time:relative_body.time+time.into(), - instruction:PhysicsInternalInstruction::CollisionEnd( - Collision::Contact(*contact), - time - ), - } - })); - } - for intersect in &self.intersects{ - //detect model collision in reverse - let model_mesh=models.intersect_mesh(intersect); - let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh()); - collector.collect(minkowski.predict_collision_out(&relative_body,collector.time()).map(|(_face,time)|{ - TimedInstruction{ - time:relative_body.time+time.into(), - instruction:PhysicsInternalInstruction::CollisionEnd( - Collision::Intersect(*intersect), - time - ), - } - })); - } - } +impl TouchingState { + fn clear(&mut self) { + self.contacts.clear(); + self.intersects.clear(); + } + fn insert(&mut self, collision: Collision) -> bool { + match collision { + Collision::Contact(collision) => self.contacts.insert(collision), + Collision::Intersect(collision) => self.intersects.insert(collision), + } + } + fn remove(&mut self, collision: &Collision) -> bool { + match collision { + Collision::Contact(collision) => self.contacts.remove(collision), + Collision::Intersect(collision) => self.intersects.remove(collision), + } + } + fn base_acceleration( + &self, + models: &PhysicsModels, + style: &StyleModifiers, + camera: &PhysicsCamera, + input_state: &InputState, + ) -> Planar64Vec3 { + let mut a = style.gravity; + if let Some(rocket_settings) = &style.rocket { + a += rocket_settings + .acceleration(style.get_propulsion_control_dir(camera, input_state.controls)); + } + //add accelerators + for contact in &self.contacts { + if let Some(accelerator) = &models.contact_attr(contact.model_id).general.accelerator { + a += accelerator.acceleration; + } + } + for intersect in &self.intersects { + if let Some(accelerator) = &models + .intersect_attr(intersect.model_id) + .general + .accelerator + { + a += accelerator.acceleration; + } + } + //TODO: add water + a + } + fn constrain_velocity( + &self, + models: &PhysicsModels, + hitbox_mesh: &HitboxMesh, + velocity: &mut Planar64Vec3, + ) { + let contacts = self + .contacts + .iter() + .map(|contact| { + let n = contact_normal(models, hitbox_mesh, contact); + crate::push_solve::Contact { + position: vec3::ZERO, + velocity: n, + normal: n, + } + }) + .collect(); + match crate::push_solve::push_solve(&contacts, *velocity) { + Some(new_velocity) => *velocity = new_velocity, + None => println!("Algorithm silently failing :)"), + } + } + fn constrain_acceleration( + &self, + models: &PhysicsModels, + hitbox_mesh: &HitboxMesh, + acceleration: &mut Planar64Vec3, + ) { + let contacts = self + .contacts + .iter() + .map(|contact| { + let n = contact_normal(models, hitbox_mesh, contact); + crate::push_solve::Contact { + position: vec3::ZERO, + velocity: n, + normal: n, + } + }) + .collect(); + match crate::push_solve::push_solve(&contacts, *acceleration) { + Some(new_acceleration) => *acceleration = new_acceleration, + None => println!("Algorithm silently failing :)"), + } + } + fn predict_collision_end( + &self, + collector: &mut instruction::InstructionCollector, + models: &PhysicsModels, + hitbox_mesh: &HitboxMesh, + body: &Body, + time: Time, + ) { + let relative_body = crate::body::VirtualBody::relative(&Body::ZERO, body).body(time); + for contact in &self.contacts { + //detect face slide off + let model_mesh = models.contact_mesh(contact); + let minkowski = model_physics::MinkowskiMesh::minkowski_sum( + model_mesh, + hitbox_mesh.transformed_mesh(), + ); + collector.collect( + minkowski + .predict_collision_face_out(&relative_body, collector.time(), contact.face_id) + .map(|(_face, time)| TimedInstruction { + time: relative_body.time + time.into(), + instruction: PhysicsInternalInstruction::CollisionEnd( + Collision::Contact(*contact), + time, + ), + }), + ); + } + for intersect in &self.intersects { + //detect model collision in reverse + let model_mesh = models.intersect_mesh(intersect); + let minkowski = model_physics::MinkowskiMesh::minkowski_sum( + model_mesh, + hitbox_mesh.transformed_mesh(), + ); + collector.collect( + minkowski + .predict_collision_out(&relative_body, collector.time()) + .map(|(_face, time)| TimedInstruction { + time: relative_body.time + time.into(), + instruction: PhysicsInternalInstruction::CollisionEnd( + Collision::Intersect(*intersect), + time, + ), + }), + ); + } + } } -#[derive(Clone,Debug)] -pub struct PhysicsState{ - time:Time, - body:Body, - _world:WorldState,//currently there is only one state the world can be in - touching:TouchingState, - //camera must exist in state because wormholes modify the camera, also camera punch - camera:PhysicsCamera, - //input_state - input_state:InputState, - //style - style:StyleModifiers,//mode style with custom style updates applied - //gameplay_state - mode_state:ModeState, - move_state:MoveState, - //run is non optional: when you spawn in a run is created - //the run cannot be finished unless you start it by visiting - //a start zone. If you change mode, a new run is created. - run:run::Run, +#[derive(Clone, Debug)] +pub struct PhysicsState { + time: Time, + body: Body, + _world: WorldState, //currently there is only one state the world can be in + touching: TouchingState, + //camera must exist in state because wormholes modify the camera, also camera punch + camera: PhysicsCamera, + //input_state + input_state: InputState, + //style + style: StyleModifiers, //mode style with custom style updates applied + //gameplay_state + mode_state: ModeState, + move_state: MoveState, + //run is non optional: when you spawn in a run is created + //the run cannot be finished unless you start it by visiting + //a start zone. If you change mode, a new run is created. + run: run::Run, } //random collection of contextual data that doesn't belong in PhysicsState -pub struct PhysicsData{ - //permanent map data - bvh:bvh::BvhNode, - //transient map/environment data (open world loads/unloads parts of this data) - models:PhysicsModels, - //semi-transient data - modes:gameplay_modes::Modes, - //cached calculations - hitbox_mesh:HitboxMesh, +pub struct PhysicsData { + //permanent map data + bvh: bvh::BvhNode, + //transient map/environment data (open world loads/unloads parts of this data) + models: PhysicsModels, + //semi-transient data + modes: gameplay_modes::Modes, + //cached calculations + hitbox_mesh: HitboxMesh, } -impl Default for PhysicsState{ - fn default()->Self{ - Self{ - body:Body::new(vec3::int(0,50,0),vec3::int(0,0,0),vec3::int(0,-100,0),Time::ZERO), - time:Time::ZERO, - style:StyleModifiers::default(), - touching:TouchingState::default(), - move_state:MoveState::Air, - camera:PhysicsCamera::default(), - input_state:InputState::default(), - _world:WorldState{}, - mode_state:ModeState::default(), - run:run::Run::new(), - } - } +impl Default for PhysicsState { + fn default() -> Self { + Self { + body: Body::new( + vec3::int(0, 50, 0), + vec3::int(0, 0, 0), + vec3::int(0, -100, 0), + Time::ZERO, + ), + time: Time::ZERO, + style: StyleModifiers::default(), + touching: TouchingState::default(), + move_state: MoveState::Air, + camera: PhysicsCamera::default(), + input_state: InputState::default(), + _world: WorldState {}, + mode_state: ModeState::default(), + run: run::Run::new(), + } + } } -impl Default for PhysicsData{ - fn default()->Self{ - Self{ - bvh:bvh::BvhNode::default(), - models:Default::default(), - modes:Default::default(), - hitbox_mesh:StyleModifiers::default().calculate_mesh(), - } - } +impl Default for PhysicsData { + fn default() -> Self { + Self { + bvh: bvh::BvhNode::default(), + models: Default::default(), + modes: Default::default(), + hitbox_mesh: StyleModifiers::default().calculate_mesh(), + } + } } -impl PhysicsState{ - fn clear(&mut self){ - self.touching.clear(); - } - fn reset_to_default(&mut self){ - let mut new_state=Self::default(); - new_state.camera.sensitivity=self.camera.sensitivity; - *self=new_state; - } - fn next_move_instruction(&self)->Option>{ - self.move_state.next_move_instruction(&self.style.strafe,self.time) - } - fn cull_velocity(&mut self,data:&PhysicsData,velocity:Planar64Vec3){ - self.move_state.cull_velocity(velocity,&mut self.body,&mut self.touching,&data.models,&data.hitbox_mesh,&self.style,&self.camera,&self.input_state); - } - fn set_move_state(&mut self,data:&PhysicsData,move_state:MoveState){ - self.move_state.set_move_state(move_state,&mut self.body,&self.touching,&data.models,&data.hitbox_mesh,&self.style,&self.camera,&self.input_state); - } - fn apply_input_and_body(&mut self,data:&PhysicsData){ - self.move_state.apply_input_and_body(&mut self.body,&self.touching,&data.models,&data.hitbox_mesh,&self.style,&self.camera,&self.input_state); - } - //state mutated on collision: - //Accelerator - //stair step-up +impl PhysicsState { + fn clear(&mut self) { + self.touching.clear(); + } + fn reset_to_default(&mut self) { + let mut new_state = Self::default(); + new_state.camera.sensitivity = self.camera.sensitivity; + *self = new_state; + } + fn next_move_instruction( + &self, + ) -> Option> { + self.move_state + .next_move_instruction(&self.style.strafe, self.time) + } + fn cull_velocity(&mut self, data: &PhysicsData, velocity: Planar64Vec3) { + self.move_state.cull_velocity( + velocity, + &mut self.body, + &mut self.touching, + &data.models, + &data.hitbox_mesh, + &self.style, + &self.camera, + &self.input_state, + ); + } + fn set_move_state(&mut self, data: &PhysicsData, move_state: MoveState) { + self.move_state.set_move_state( + move_state, + &mut self.body, + &self.touching, + &data.models, + &data.hitbox_mesh, + &self.style, + &self.camera, + &self.input_state, + ); + } + fn apply_input_and_body(&mut self, data: &PhysicsData) { + self.move_state.apply_input_and_body( + &mut self.body, + &self.touching, + &data.models, + &data.hitbox_mesh, + &self.style, + &self.camera, + &self.input_state, + ); + } + //state mutated on collision: + //Accelerator + //stair step-up - //state mutated on instruction - //change fly acceleration (fly_sustain) - //change fly velocity + //state mutated on instruction + //change fly acceleration (fly_sustain) + //change fly velocity - //generic event emmiters - //PlatformStandTime - //walk/swim/air/ladder sounds - //VState? + //generic event emmiters + //PlatformStandTime + //walk/swim/air/ladder sounds + //VState? - //falling under the map - // fn next_respawn_instruction(&self) -> Option> { - // if self.body.position Option> { + // if self.body.position Option> { - // return Some(TimedInstruction{ - // time:(self.time*self.strafe_tick_num/self.strafe_tick_den+1)*self.strafe_tick_den/self.strafe_tick_num, - // //only poll the physics if there is a before and after mouse event - // instruction:PhysicsInstruction::Water - // }); - // } + // fn next_water_instruction(&self) -> Option> { + // return Some(TimedInstruction{ + // time:(self.time*self.strafe_tick_num/self.strafe_tick_den+1)*self.strafe_tick_den/self.strafe_tick_num, + // //only poll the physics if there is a before and after mouse event + // instruction:PhysicsInstruction::Water + // }); + // } } #[derive(Default)] -pub struct PhysicsContext{ - state:PhysicsState,//this captures the entire state of the physics. - data:PhysicsData,//data currently loaded into memory which is needded for physics to run, but is not part of the state. +pub struct PhysicsContext { + state: PhysicsState, //this captures the entire state of the physics. + data: PhysicsData, //data currently loaded into memory which is needded for physics to run, but is not part of the state. } //the physics consumes the generic PhysicsInstruction, but can only emit the more narrow PhysicsInternalInstruction -impl instruction::InstructionConsumer for PhysicsContext{ - type Instruction=PhysicsInstruction; - type TimeInner=TimeInner; - fn process_instruction(&mut self,ins:TimedInstruction){ - atomic_state_update(&mut self.state,&self.data,ins) - } +impl instruction::InstructionConsumer for PhysicsContext { + type Instruction = PhysicsInstruction; + type TimeInner = TimeInner; + fn process_instruction(&mut self, ins: TimedInstruction) { + atomic_state_update(&mut self.state, &self.data, ins) + } } -impl instruction::InstructionEmitter for PhysicsContext{ - type Instruction=PhysicsInternalInstruction; - type TimeInner=TimeInner; - //this little next instruction function could cache its return value and invalidate the cached value by watching the State. - fn next_instruction(&self,time_limit:Time)->Option>{ - next_instruction_internal(&self.state,&self.data,time_limit) - } +impl instruction::InstructionEmitter for PhysicsContext { + type Instruction = PhysicsInternalInstruction; + type TimeInner = TimeInner; + //this little next instruction function could cache its return value and invalidate the cached value by watching the State. + fn next_instruction( + &self, + time_limit: Time, + ) -> Option> { + next_instruction_internal(&self.state, &self.data, time_limit) + } } -impl PhysicsContext{ - pub fn camera_body(&self)->Body{ - Body{ - position:self.state.body.position+self.state.style.camera_offset, - ..self.state.body - } - } - pub const fn camera(&self)->PhysicsCamera{ - self.state.camera - } - pub const fn get_next_mouse(&self)->&MouseState{ - self.state.input_state.get_next_mouse() - } - /// use with caution, this is the only non-instruction way to mess with physics - pub fn generate_models(&mut self,map:&map::CompleteMap){ - self.state.clear(); - let mut modes=map.modes.clone(); - for mode in &mut modes.modes{ - mode.denormalize_data(); - } - let mut used_contact_attributes=Vec::new(); - let mut used_intersect_attributes=Vec::new(); +impl PhysicsContext { + pub fn camera_body(&self) -> Body { + Body { + position: self.state.body.position + self.state.style.camera_offset, + ..self.state.body + } + } + pub const fn camera(&self) -> PhysicsCamera { + self.state.camera + } + pub const fn get_next_mouse(&self) -> &MouseState { + self.state.input_state.get_next_mouse() + } + /// use with caution, this is the only non-instruction way to mess with physics + pub fn generate_models(&mut self, map: &map::CompleteMap) { + self.state.clear(); + let mut modes = map.modes.clone(); + for mode in &mut modes.modes { + mode.denormalize_data(); + } + let mut used_contact_attributes = Vec::new(); + let mut used_intersect_attributes = Vec::new(); - //temporary type for type safety lol - #[derive(Clone,Copy,Hash,Eq,PartialEq)] - enum PhysicsAttributesId{ - Contact(ContactAttributesId), - Intersect(IntersectAttributesId), - } + //temporary type for type safety lol + #[derive(Clone, Copy, Hash, Eq, PartialEq)] + enum PhysicsAttributesId { + Contact(ContactAttributesId), + Intersect(IntersectAttributesId), + } - let mut contact_models=HashMap::new(); - let mut intersect_models=HashMap::new(); + let mut contact_models = HashMap::new(); + let mut intersect_models = HashMap::new(); - let mut physics_attr_id_from_model_attr_id=HashMap::::new(); - let mut used_meshes=Vec::new(); - let mut physics_mesh_id_from_model_mesh_id=HashMap::::new(); - for (model_id,model) in map.models.iter().enumerate(){ - //TODO: use .entry().or_insert_with(||{ - let attr_id=if let Some(&attr_id)=physics_attr_id_from_model_attr_id.get(&model.attributes){ - attr_id - }else{ - //check if it's real - match map.attributes.get(model.attributes.get() as usize).and_then(|m_attr|{ - PhysicsCollisionAttributes::try_from(m_attr).map_or(None,|p_attr|{ - let attr_id=match p_attr{ - PhysicsCollisionAttributes::Contact(attr)=>{ - let attr_id=ContactAttributesId::new(used_contact_attributes.len() as u32); - used_contact_attributes.push(attr); - PhysicsAttributesId::Contact(attr_id) - }, - PhysicsCollisionAttributes::Intersect(attr)=>{ - let attr_id=IntersectAttributesId::new(used_intersect_attributes.len() as u32); - used_intersect_attributes.push(attr); - PhysicsAttributesId::Intersect(attr_id) - }, - }; - physics_attr_id_from_model_attr_id.insert(model.attributes,attr_id); - Some(attr_id) - }) - }){ - Some(attr_id)=>attr_id, - None=>continue, - } - }; - let mesh_id=if let Some(&mesh_id)=physics_mesh_id_from_model_mesh_id.get(&model.mesh){ - mesh_id - }else{ - match map.meshes.get(model.mesh.get() as usize).and_then(|mesh|{ - match PhysicsMesh::try_from(mesh){ - Ok(physics_mesh)=>{ - let mesh_id=PhysicsMeshId::new(used_meshes.len() as u32); - used_meshes.push(physics_mesh); - physics_mesh_id_from_model_mesh_id.insert(model.mesh,mesh_id); - Some(mesh_id) - }, - Err(e)=>{ - println!("Failed to build PhysicsMesh: {e}"); - None - } - } - }){ - Some(mesh_id)=>mesh_id, - None=>continue, - } - }; - let transform=PhysicsMeshTransform::new(model.transform); - match attr_id{ - PhysicsAttributesId::Contact(attr_id)=>{ - contact_models.insert(ContactModelId::new(model_id as u32),ContactModel{ - mesh_id, - attr_id, - transform, - }); - }, - PhysicsAttributesId::Intersect(attr_id)=>{ - intersect_models.insert(IntersectModelId::new(model_id as u32),IntersectModel{ - mesh_id, - attr_id, - transform, - }); - }, - } - } - let meshes:HashMap=used_meshes.into_iter() - .enumerate() - .map(|(mesh_id,mesh)| - (PhysicsMeshId::new(mesh_id as u32),mesh) - ).collect(); - let convex_mesh_aabb_list= + let mut physics_attr_id_from_model_attr_id = + HashMap::::new(); + let mut used_meshes = Vec::new(); + let mut physics_mesh_id_from_model_mesh_id = HashMap::::new(); + for (model_id, model) in map.models.iter().enumerate() { + //TODO: use .entry().or_insert_with(||{ + let attr_id = if let Some(&attr_id) = + physics_attr_id_from_model_attr_id.get(&model.attributes) + { + attr_id + } else { + //check if it's real + match map + .attributes + .get(model.attributes.get() as usize) + .and_then(|m_attr| { + PhysicsCollisionAttributes::try_from(m_attr).map_or(None, |p_attr| { + let attr_id = match p_attr { + PhysicsCollisionAttributes::Contact(attr) => { + let attr_id = ContactAttributesId::new( + used_contact_attributes.len() as u32, + ); + used_contact_attributes.push(attr); + PhysicsAttributesId::Contact(attr_id) + } + PhysicsCollisionAttributes::Intersect(attr) => { + let attr_id = IntersectAttributesId::new( + used_intersect_attributes.len() as u32, + ); + used_intersect_attributes.push(attr); + PhysicsAttributesId::Intersect(attr_id) + } + }; + physics_attr_id_from_model_attr_id.insert(model.attributes, attr_id); + Some(attr_id) + }) + }) { + Some(attr_id) => attr_id, + None => continue, + } + }; + let mesh_id = + if let Some(&mesh_id) = physics_mesh_id_from_model_mesh_id.get(&model.mesh) { + mesh_id + } else { + match map.meshes.get(model.mesh.get() as usize).and_then(|mesh| { + match PhysicsMesh::try_from(mesh) { + Ok(physics_mesh) => { + let mesh_id = PhysicsMeshId::new(used_meshes.len() as u32); + used_meshes.push(physics_mesh); + physics_mesh_id_from_model_mesh_id.insert(model.mesh, mesh_id); + Some(mesh_id) + } + Err(e) => { + println!("Failed to build PhysicsMesh: {e}"); + None + } + } + }) { + Some(mesh_id) => mesh_id, + None => continue, + } + }; + let transform = PhysicsMeshTransform::new(model.transform); + match attr_id { + PhysicsAttributesId::Contact(attr_id) => { + contact_models.insert( + ContactModelId::new(model_id as u32), + ContactModel { + mesh_id, + attr_id, + transform, + }, + ); + } + PhysicsAttributesId::Intersect(attr_id) => { + intersect_models.insert( + IntersectModelId::new(model_id as u32), + IntersectModel { + mesh_id, + attr_id, + transform, + }, + ); + } + } + } + let meshes: HashMap = used_meshes + .into_iter() + .enumerate() + .map(|(mesh_id, mesh)| (PhysicsMeshId::new(mesh_id as u32), mesh)) + .collect(); + let convex_mesh_aabb_list= //map the two lists into a single type so they can be processed with one closure contact_models.iter().map(|(&model_id,model)| (PhysicsModelId::Contact(model_id),&model.mesh_id,&model.transform) @@ -1086,1041 +1483,1608 @@ impl PhysicsContext{ },aabb) }) }).collect(); - let bvh=bvh::generate_bvh(convex_mesh_aabb_list); - let model_count=contact_models.len()+intersect_models.len(); - let models=PhysicsModels{ - meshes, - contact_models, - intersect_models, - contact_attributes:used_contact_attributes.into_iter() - .enumerate() - .map(|(attr_id,attr)| - (ContactAttributesId::new(attr_id as u32),attr) - ).collect(), - intersect_attributes:used_intersect_attributes.into_iter() - .enumerate() - .map(|(attr_id,attr)| - (IntersectAttributesId::new(attr_id as u32),attr) - ).collect(), - }; - self.data.bvh=bvh; - self.data.models=models; - self.data.modes=modes; - //hitbox_mesh is unchanged - println!("Physics Objects: {}",model_count); - } + let bvh = bvh::generate_bvh(convex_mesh_aabb_list); + let model_count = contact_models.len() + intersect_models.len(); + let models = PhysicsModels { + meshes, + contact_models, + intersect_models, + contact_attributes: used_contact_attributes + .into_iter() + .enumerate() + .map(|(attr_id, attr)| (ContactAttributesId::new(attr_id as u32), attr)) + .collect(), + intersect_attributes: used_intersect_attributes + .into_iter() + .enumerate() + .map(|(attr_id, attr)| (IntersectAttributesId::new(attr_id as u32), attr)) + .collect(), + }; + self.data.bvh = bvh; + self.data.models = models; + self.data.modes = modes; + //hitbox_mesh is unchanged + println!("Physics Objects: {}", model_count); + } - //tickless gaming - fn run_internal_exhaustive(&mut self,time_limit:Time){ - //prepare is ommitted - everything is done via instructions. - while let Some(instruction)=self.next_instruction(time_limit){//collect - //process - self.process_instruction(TimedInstruction{ - time:instruction.time, - instruction:PhysicsInstruction::Internal(instruction.instruction), - }); - //write hash lol - } - } - pub fn run_input_instruction(&mut self,instruction:TimedInstruction){ - self.run_internal_exhaustive(instruction.time); - self.process_instruction(TimedInstruction{ - time:instruction.time, - instruction:PhysicsInstruction::Input(instruction.instruction), - }); - } + //tickless gaming + fn run_internal_exhaustive(&mut self, time_limit: Time) { + //prepare is ommitted - everything is done via instructions. + while let Some(instruction) = self.next_instruction(time_limit) { + //collect + //process + self.process_instruction(TimedInstruction { + time: instruction.time, + instruction: PhysicsInstruction::Internal(instruction.instruction), + }); + //write hash lol + } + } + pub fn run_input_instruction( + &mut self, + instruction: TimedInstruction, + ) { + self.run_internal_exhaustive(instruction.time); + self.process_instruction(TimedInstruction { + time: instruction.time, + instruction: PhysicsInstruction::Input(instruction.instruction), + }); + } } - //this is the one who asks - fn next_instruction_internal(state:&PhysicsState,data:&PhysicsData,time_limit:Time)->Option>{ - //JUST POLLING!!! NO MUTATION - let mut collector = instruction::InstructionCollector::new(time_limit); +//this is the one who asks +fn next_instruction_internal( + state: &PhysicsState, + data: &PhysicsData, + time_limit: Time, +) -> Option> { + //JUST POLLING!!! NO MUTATION + let mut collector = instruction::InstructionCollector::new(time_limit); - collector.collect(state.next_move_instruction()); + collector.collect(state.next_move_instruction()); - //check for collision ends - state.touching.predict_collision_end(&mut collector,&data.models,&data.hitbox_mesh,&state.body,state.time); - //check for collision starts - let mut aabb=aabb::Aabb::default(); - state.body.grow_aabb(&mut aabb,state.time,collector.time()); - aabb.inflate(data.hitbox_mesh.halfsize); - //relative to moving platforms - //let relative_body=&VirtualBody::relative(&Body::default(),&state.body).body(state.time); - let relative_body=&state.body; - data.bvh.the_tester(&aabb,&mut |&convex_mesh_id|{ - //no checks are needed because of the time limits. - let model_mesh=data.models.mesh(convex_mesh_id); - let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,data.hitbox_mesh.transformed_mesh()); - collector.collect(minkowski.predict_collision_in(relative_body,collector.time()) - //temp (?) code to avoid collision loops - .map_or(None,|(face,dt)|{ - let time=relative_body.time+dt.into(); - if time<=state.time{None}else{Some((time,face,dt))}}) - .map(|(time,face,dt)| - TimedInstruction{ - time, - instruction:PhysicsInternalInstruction::CollisionStart( - Collision::new(convex_mesh_id,face), - dt - ) - } - ) - ); - }); - collector.instruction() - } + //check for collision ends + state.touching.predict_collision_end( + &mut collector, + &data.models, + &data.hitbox_mesh, + &state.body, + state.time, + ); + //check for collision starts + let mut aabb = aabb::Aabb::default(); + state + .body + .grow_aabb(&mut aabb, state.time, collector.time()); + aabb.inflate(data.hitbox_mesh.halfsize); + //relative to moving platforms + //let relative_body=&VirtualBody::relative(&Body::default(),&state.body).body(state.time); + let relative_body = &state.body; + data.bvh.the_tester(&aabb, &mut |&convex_mesh_id| { + //no checks are needed because of the time limits. + let model_mesh = data.models.mesh(convex_mesh_id); + let minkowski = model_physics::MinkowskiMesh::minkowski_sum( + model_mesh, + data.hitbox_mesh.transformed_mesh(), + ); + collector.collect( + minkowski + .predict_collision_in(relative_body, collector.time()) + //temp (?) code to avoid collision loops + .map_or(None, |(face, dt)| { + let time = relative_body.time + dt.into(); + if time <= state.time { + None + } else { + Some((time, face, dt)) + } + }) + .map(|(time, face, dt)| TimedInstruction { + time, + instruction: PhysicsInternalInstruction::CollisionStart( + Collision::new(convex_mesh_id, face), + dt, + ), + }), + ); + }); + collector.instruction() +} - -fn contact_normal(models:&PhysicsModels,hitbox_mesh:&HitboxMesh,contact:&ContactCollision)->Planar64Vec3{ - let model_mesh=models.contact_mesh(contact); - let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh()); - // TODO: normalize to i64::MAX>>1 - minkowski.face_nd(contact.face_id).0.fix_1() +fn contact_normal( + models: &PhysicsModels, + hitbox_mesh: &HitboxMesh, + contact: &ContactCollision, +) -> Planar64Vec3 { + let model_mesh = models.contact_mesh(contact); + let minkowski = + model_physics::MinkowskiMesh::minkowski_sum(model_mesh, hitbox_mesh.transformed_mesh()); + // TODO: normalize to i64::MAX>>1 + minkowski.face_nd(contact.face_id).0.fix_1() } fn recalculate_touching( - move_state:&mut MoveState, - body:&mut Body, - touching:&mut TouchingState, - run:&mut run::Run, - mode_state:&mut ModeState, - mode:Option<&gameplay_modes::Mode>, - models:&PhysicsModels, - hitbox_mesh:&HitboxMesh, - bvh:&bvh::BvhNode, - style:&StyleModifiers, - camera:&PhysicsCamera, - input_state:&InputState, - time:Time, -){ - //collision_end all existing contacts - //I would have preferred while let Some(contact)=contacts.pop() - //but there is no such method - while let Some(&contact)=touching.contacts.iter().next(){ - collision_end_contact(move_state,body,touching,models,hitbox_mesh,style,camera,input_state,models.contact_attr(contact.model_id),contact) - } - while let Some(&intersect)=touching.intersects.iter().next(){ - collision_end_intersect(touching,mode,run,models.intersect_attr(intersect.model_id),intersect,time); - } - //find all models in the teleport region - let mut aabb=aabb::Aabb::default(); - aabb.grow(body.position); - aabb.inflate(hitbox_mesh.halfsize); - //relative to moving platforms - //let relative_body=&VirtualBody::relative(&Body::default(),&state.body).body(state.time); - bvh.the_tester(&aabb,&mut |&convex_mesh_id|{ - //no checks are needed because of the time limits. - let model_mesh=models.mesh(convex_mesh_id); - let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh()); - if minkowski.is_point_in_mesh(body.position){ - match convex_mesh_id.model_id{ - //being inside of contact objects is an invalid physics state - //but the physics isn't advanced enough to do anything about it yet - //TODO: PushSolve and search for the closest valid position - PhysicsModelId::Contact(_)=>(), - PhysicsModelId::Intersect(model_id)=> - collision_start_intersect(move_state,body,mode_state,touching,mode,run,models,hitbox_mesh,bvh,style,camera,input_state, - models.intersect_attr(model_id), - IntersectCollision{ - model_id, - submesh_id:convex_mesh_id.submesh_id, - }, - time, - ), - } - } - }); + move_state: &mut MoveState, + body: &mut Body, + touching: &mut TouchingState, + run: &mut run::Run, + mode_state: &mut ModeState, + mode: Option<&gameplay_modes::Mode>, + models: &PhysicsModels, + hitbox_mesh: &HitboxMesh, + bvh: &bvh::BvhNode, + style: &StyleModifiers, + camera: &PhysicsCamera, + input_state: &InputState, + time: Time, +) { + //collision_end all existing contacts + //I would have preferred while let Some(contact)=contacts.pop() + //but there is no such method + while let Some(&contact) = touching.contacts.iter().next() { + collision_end_contact( + move_state, + body, + touching, + models, + hitbox_mesh, + style, + camera, + input_state, + models.contact_attr(contact.model_id), + contact, + ) + } + while let Some(&intersect) = touching.intersects.iter().next() { + collision_end_intersect( + touching, + mode, + run, + models.intersect_attr(intersect.model_id), + intersect, + time, + ); + } + //find all models in the teleport region + let mut aabb = aabb::Aabb::default(); + aabb.grow(body.position); + aabb.inflate(hitbox_mesh.halfsize); + //relative to moving platforms + //let relative_body=&VirtualBody::relative(&Body::default(),&state.body).body(state.time); + bvh.the_tester(&aabb, &mut |&convex_mesh_id| { + //no checks are needed because of the time limits. + let model_mesh = models.mesh(convex_mesh_id); + let minkowski = + model_physics::MinkowskiMesh::minkowski_sum(model_mesh, hitbox_mesh.transformed_mesh()); + if minkowski.is_point_in_mesh(body.position) { + match convex_mesh_id.model_id { + //being inside of contact objects is an invalid physics state + //but the physics isn't advanced enough to do anything about it yet + //TODO: PushSolve and search for the closest valid position + PhysicsModelId::Contact(_) => (), + PhysicsModelId::Intersect(model_id) => collision_start_intersect( + move_state, + body, + mode_state, + touching, + mode, + run, + models, + hitbox_mesh, + bvh, + style, + camera, + input_state, + models.intersect_attr(model_id), + IntersectCollision { + model_id, + submesh_id: convex_mesh_id.submesh_id, + }, + time, + ), + } + } + }); } fn set_position( - point:Planar64Vec3, - move_state:&mut MoveState, - body:&mut Body, - touching:&mut TouchingState, - run:&mut run::Run, - mode_state:&mut ModeState, - mode:Option<&gameplay_modes::Mode>, - models:&PhysicsModels, - hitbox_mesh:&HitboxMesh, - bvh:&bvh::BvhNode, - style:&StyleModifiers, - camera:&PhysicsCamera, - input_state:&InputState, - time:Time, -)->Planar64Vec3{ - //test intersections at new position - //hovering above the surface 0 units is not intersecting. you will fall into it just fine - body.position=point; - //calculate contacts and determine the actual state - recalculate_touching(move_state,body,touching,run,mode_state,mode,models,hitbox_mesh,bvh,style,camera,input_state,time); - point + point: Planar64Vec3, + move_state: &mut MoveState, + body: &mut Body, + touching: &mut TouchingState, + run: &mut run::Run, + mode_state: &mut ModeState, + mode: Option<&gameplay_modes::Mode>, + models: &PhysicsModels, + hitbox_mesh: &HitboxMesh, + bvh: &bvh::BvhNode, + style: &StyleModifiers, + camera: &PhysicsCamera, + input_state: &InputState, + time: Time, +) -> Planar64Vec3 { + //test intersections at new position + //hovering above the surface 0 units is not intersecting. you will fall into it just fine + body.position = point; + //calculate contacts and determine the actual state + recalculate_touching( + move_state, + body, + touching, + run, + mode_state, + mode, + models, + hitbox_mesh, + bvh, + style, + camera, + input_state, + time, + ); + point } -fn set_velocity_cull(body:&mut Body,touching:&mut TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,v:Planar64Vec3)->bool{ - //This is not correct but is better than what I have - let mut culled=false; - touching.contacts.retain(|contact|{ - let n=contact_normal(models,hitbox_mesh,contact); - let r=n.dot(v).is_positive(); - if r{ - culled=true; - println!("set_velocity_cull contact={:?}",contact); - } - !r - }); - set_velocity(body,touching,models,hitbox_mesh,v); - culled +fn set_velocity_cull( + body: &mut Body, + touching: &mut TouchingState, + models: &PhysicsModels, + hitbox_mesh: &HitboxMesh, + v: Planar64Vec3, +) -> bool { + //This is not correct but is better than what I have + let mut culled = false; + touching.contacts.retain(|contact| { + let n = contact_normal(models, hitbox_mesh, contact); + let r = n.dot(v).is_positive(); + if r { + culled = true; + println!("set_velocity_cull contact={:?}", contact); + } + !r + }); + set_velocity(body, touching, models, hitbox_mesh, v); + culled } -fn set_velocity(body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,mut v:Planar64Vec3){ - touching.constrain_velocity(models,hitbox_mesh,&mut v); - body.velocity=v; +fn set_velocity( + body: &mut Body, + touching: &TouchingState, + models: &PhysicsModels, + hitbox_mesh: &HitboxMesh, + mut v: Planar64Vec3, +) { + touching.constrain_velocity(models, hitbox_mesh, &mut v); + body.velocity = v; } -fn set_acceleration_cull(body:&mut Body,touching:&mut TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,a:Planar64Vec3)->bool{ - //This is not correct but is better than what I have - let mut culled=false; - touching.contacts.retain(|contact|{ - let n=contact_normal(models,hitbox_mesh,contact); - let r=n.dot(a).is_positive(); - if r{ - culled=true; - println!("set_acceleration_cull contact={:?}",contact); - } - !r - }); - set_acceleration(body,touching,models,hitbox_mesh,a); - culled +fn set_acceleration_cull( + body: &mut Body, + touching: &mut TouchingState, + models: &PhysicsModels, + hitbox_mesh: &HitboxMesh, + a: Planar64Vec3, +) -> bool { + //This is not correct but is better than what I have + let mut culled = false; + touching.contacts.retain(|contact| { + let n = contact_normal(models, hitbox_mesh, contact); + let r = n.dot(a).is_positive(); + if r { + culled = true; + println!("set_acceleration_cull contact={:?}", contact); + } + !r + }); + set_acceleration(body, touching, models, hitbox_mesh, a); + culled } -fn set_acceleration(body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,mut a:Planar64Vec3){ - touching.constrain_acceleration(models,hitbox_mesh,&mut a); - body.acceleration=a; +fn set_acceleration( + body: &mut Body, + touching: &TouchingState, + models: &PhysicsModels, + hitbox_mesh: &HitboxMesh, + mut a: Planar64Vec3, +) { + touching.constrain_acceleration(models, hitbox_mesh, &mut a); + body.acceleration = a; } fn teleport( - point:Planar64Vec3, - move_state:&mut MoveState, - body:&mut Body, - touching:&mut TouchingState, - run:&mut run::Run, - mode_state:&mut ModeState, - mode:Option<&gameplay_modes::Mode>, - models:&PhysicsModels, - hitbox_mesh:&HitboxMesh, - bvh:&bvh::BvhNode, - style:&StyleModifiers, - camera:&PhysicsCamera, - input_state:&InputState, - time:Time, -){ - set_position(point,move_state,body,touching,run,mode_state,mode,models,hitbox_mesh,bvh,style,camera,input_state,time); - set_acceleration(body,touching,models,hitbox_mesh,style.gravity); + point: Planar64Vec3, + move_state: &mut MoveState, + body: &mut Body, + touching: &mut TouchingState, + run: &mut run::Run, + mode_state: &mut ModeState, + mode: Option<&gameplay_modes::Mode>, + models: &PhysicsModels, + hitbox_mesh: &HitboxMesh, + bvh: &bvh::BvhNode, + style: &StyleModifiers, + camera: &PhysicsCamera, + input_state: &InputState, + time: Time, +) { + set_position( + point, + move_state, + body, + touching, + run, + mode_state, + mode, + models, + hitbox_mesh, + bvh, + style, + camera, + input_state, + time, + ); + set_acceleration(body, touching, models, hitbox_mesh, style.gravity); } -enum TeleportToSpawnError{ - NoModel, +enum TeleportToSpawnError { + NoModel, } fn teleport_to_spawn( - spawn_model_id:ModelId, - move_state:&mut MoveState, - body:&mut Body, - touching:&mut TouchingState, - run:&mut run::Run, - mode_state:&mut ModeState, - mode:&gameplay_modes::Mode, - models:&PhysicsModels, - hitbox_mesh:&HitboxMesh, - bvh:&bvh::BvhNode, - style:&StyleModifiers, - camera:&PhysicsCamera, - input_state:&InputState, - time:Time, -)->Result<(),TeleportToSpawnError>{ - //jump count and checkpoints are always reset on teleport_to_spawn. - //Map makers are expected to use tools to prevent - //multi-boosting on JumpLimit boosters such as spawning into a SetVelocity - mode_state.clear(); - const EPSILON:Planar64=Planar64::raw((1<<32)/16); - let transform=models.get_model_transform(spawn_model_id).ok_or(TeleportToSpawnError::NoModel)?; - //TODO: transform.vertex.matrix3.col(1)+transform.vertex.translation - let point=transform.vertex.transform_point3(vec3::Y).fix_1()+Planar64Vec3::new([Planar64::ZERO,style.hitbox.halfsize.y+EPSILON,Planar64::ZERO]); - teleport(point,move_state,body,touching,run,mode_state,Some(mode),models,hitbox_mesh,bvh,style,camera,input_state,time); - Ok(()) + spawn_model_id: ModelId, + move_state: &mut MoveState, + body: &mut Body, + touching: &mut TouchingState, + run: &mut run::Run, + mode_state: &mut ModeState, + mode: &gameplay_modes::Mode, + models: &PhysicsModels, + hitbox_mesh: &HitboxMesh, + bvh: &bvh::BvhNode, + style: &StyleModifiers, + camera: &PhysicsCamera, + input_state: &InputState, + time: Time, +) -> Result<(), TeleportToSpawnError> { + //jump count and checkpoints are always reset on teleport_to_spawn. + //Map makers are expected to use tools to prevent + //multi-boosting on JumpLimit boosters such as spawning into a SetVelocity + mode_state.clear(); + const EPSILON: Planar64 = Planar64::raw((1 << 32) / 16); + let transform = models + .get_model_transform(spawn_model_id) + .ok_or(TeleportToSpawnError::NoModel)?; + //TODO: transform.vertex.matrix3.col(1)+transform.vertex.translation + let point = transform.vertex.transform_point3(vec3::Y).fix_1() + + Planar64Vec3::new([ + Planar64::ZERO, + style.hitbox.halfsize.y + EPSILON, + Planar64::ZERO, + ]); + teleport( + point, + move_state, + body, + touching, + run, + mode_state, + Some(mode), + models, + hitbox_mesh, + bvh, + style, + camera, + input_state, + time, + ); + Ok(()) } -struct CheckpointCheckOutcome{ - set_stage:Option, - teleport_to_model:Option, +struct CheckpointCheckOutcome { + set_stage: Option, + teleport_to_model: Option, } // stage_element.touch_result(mode,mode_state) fn checkpoint_check( - mode_state:&ModeState, - stage_element:&gameplay_modes::StageElement, - mode:&gameplay_modes::Mode, -)->CheckpointCheckOutcome{ - let current_stage_id=mode_state.get_stage_id(); - let target_stage_id=stage_element.stage_id(); - if current_stage_idif !stage.is_empty(){ - return CheckpointCheckOutcome{ - set_stage:Some(stage_id), - teleport_to_model:Some(stage.spawn()), - }; - }, - //no such stage! set to last existing stage - None=>return CheckpointCheckOutcome{ - set_stage:Some(StageId::new(stage_id.get()-1)), - teleport_to_model:None, - }, - } - }; - //notably you do not get teleported for touching ordered checkpoints in the wrong order within the same stage. - return CheckpointCheckOutcome{ - set_stage:Some(target_stage_id), - teleport_to_model:None, - }; - }else if stage_element.force(){ - //forced stage_element will set the stage_id even if the stage has already been passed - return CheckpointCheckOutcome{ - set_stage:Some(target_stage_id), - teleport_to_model:None, - }; - } - CheckpointCheckOutcome{ - set_stage:None, - teleport_to_model:None, - } + mode_state: &ModeState, + stage_element: &gameplay_modes::StageElement, + mode: &gameplay_modes::Mode, +) -> CheckpointCheckOutcome { + let current_stage_id = mode_state.get_stage_id(); + let target_stage_id = stage_element.stage_id(); + if current_stage_id < target_stage_id { + //checkpoint check + //check if current stage is complete + if let Some(current_stage) = mode.get_stage(current_stage_id) { + if !current_stage.is_complete( + mode_state.ordered_checkpoint_count(), + mode_state.unordered_checkpoint_count(), + ) { + return CheckpointCheckOutcome { + set_stage: None, + teleport_to_model: Some(current_stage.spawn()), + }; + } + } + //check if all between stages have no checkpoints required to pass them + for stage_id in current_stage_id.get() + 1..target_stage_id.get() { + let stage_id = StageId::new(stage_id); + //check if none of the between stages has checkpoints, if they do teleport back to that stage + match mode.get_stage(stage_id) { + Some(stage) => { + if !stage.is_empty() { + return CheckpointCheckOutcome { + set_stage: Some(stage_id), + teleport_to_model: Some(stage.spawn()), + }; + } + } + //no such stage! set to last existing stage + None => { + return CheckpointCheckOutcome { + set_stage: Some(StageId::new(stage_id.get() - 1)), + teleport_to_model: None, + } + } + } + } + //notably you do not get teleported for touching ordered checkpoints in the wrong order within the same stage. + return CheckpointCheckOutcome { + set_stage: Some(target_stage_id), + teleport_to_model: None, + }; + } else if stage_element.force() { + //forced stage_element will set the stage_id even if the stage has already been passed + return CheckpointCheckOutcome { + set_stage: Some(target_stage_id), + teleport_to_model: None, + }; + } + CheckpointCheckOutcome { + set_stage: None, + teleport_to_model: None, + } } fn run_teleport_behaviour( - model_id:ModelId, - wormhole:Option<&gameplay_attributes::Wormhole>, - mode:Option<&gameplay_modes::Mode>, - move_state:&mut MoveState, - body:&mut Body, - touching:&mut TouchingState, - run:&mut run::Run, - mode_state:&mut ModeState, - models:&PhysicsModels, - hitbox_mesh:&HitboxMesh, - bvh:&bvh::BvhNode, - style:&StyleModifiers, - camera:&PhysicsCamera, - input_state:&InputState, - time:Time, -){ - if let Some(mode)=mode{ - if let Some(stage_element)=mode.get_element(model_id){ - if let Some(stage)=mode.get_stage(stage_element.stage_id()){ - let CheckpointCheckOutcome{set_stage,teleport_to_model}=checkpoint_check(mode_state,stage_element,mode); - if let Some(stage_id)=set_stage{ - mode_state.set_stage_id(stage_id); - } - if let Some(model_id)=teleport_to_model{ - let _=teleport_to_spawn(model_id,move_state,body,touching,run,mode_state,mode,models,hitbox_mesh,bvh,style,camera,input_state,time); - return; - } - match stage_element.behaviour(){ - gameplay_modes::StageElementBehaviour::SpawnAt=>(), - gameplay_modes::StageElementBehaviour::Trigger - |gameplay_modes::StageElementBehaviour::Teleport=>if let Some(mode_state_stage)=mode.get_stage(mode_state.get_stage_id()){ - //I guess this is correct behaviour when trying to teleport to a non-existent spawn but it's still weird - let _=teleport_to_spawn(mode_state_stage.spawn(),move_state,body,touching,run,mode_state,mode,models,hitbox_mesh,bvh,style,camera,input_state,time); - return; - }, - gameplay_modes::StageElementBehaviour::Platform=>(), - gameplay_modes::StageElementBehaviour::Check=>(),//this is to run the checkpoint check behaviour without any other side effects - gameplay_modes::StageElementBehaviour::Checkpoint=>{ - //each of these checks if the model is actually a valid respective checkpoint object - //accumulate sequential ordered checkpoints - mode_state.accumulate_ordered_checkpoint(&stage,model_id); - //insert model id in accumulated unordered checkpoints - mode_state.accumulate_unordered_checkpoint(&stage,model_id); - }, - } - } - } - } - if let Some(&gameplay_attributes::Wormhole{destination_model})=wormhole{ - if let (Some(origin),Some(destination))=(models.get_model_transform(model_id),models.get_model_transform(destination_model)){ - let point=body.position-origin.vertex.translation+destination.vertex.translation; - //TODO: camera angles - teleport(point,move_state,body,touching,run,mode_state,mode,models,hitbox_mesh,bvh,style,camera,input_state,time); - } - } + model_id: ModelId, + wormhole: Option<&gameplay_attributes::Wormhole>, + mode: Option<&gameplay_modes::Mode>, + move_state: &mut MoveState, + body: &mut Body, + touching: &mut TouchingState, + run: &mut run::Run, + mode_state: &mut ModeState, + models: &PhysicsModels, + hitbox_mesh: &HitboxMesh, + bvh: &bvh::BvhNode, + style: &StyleModifiers, + camera: &PhysicsCamera, + input_state: &InputState, + time: Time, +) { + if let Some(mode) = mode { + if let Some(stage_element) = mode.get_element(model_id) { + if let Some(stage) = mode.get_stage(stage_element.stage_id()) { + let CheckpointCheckOutcome { + set_stage, + teleport_to_model, + } = checkpoint_check(mode_state, stage_element, mode); + if let Some(stage_id) = set_stage { + mode_state.set_stage_id(stage_id); + } + if let Some(model_id) = teleport_to_model { + let _ = teleport_to_spawn( + model_id, + move_state, + body, + touching, + run, + mode_state, + mode, + models, + hitbox_mesh, + bvh, + style, + camera, + input_state, + time, + ); + return; + } + match stage_element.behaviour() { + gameplay_modes::StageElementBehaviour::SpawnAt => (), + gameplay_modes::StageElementBehaviour::Trigger + | gameplay_modes::StageElementBehaviour::Teleport => { + if let Some(mode_state_stage) = mode.get_stage(mode_state.get_stage_id()) { + //I guess this is correct behaviour when trying to teleport to a non-existent spawn but it's still weird + let _ = teleport_to_spawn( + mode_state_stage.spawn(), + move_state, + body, + touching, + run, + mode_state, + mode, + models, + hitbox_mesh, + bvh, + style, + camera, + input_state, + time, + ); + return; + } + } + gameplay_modes::StageElementBehaviour::Platform => (), + gameplay_modes::StageElementBehaviour::Check => (), //this is to run the checkpoint check behaviour without any other side effects + gameplay_modes::StageElementBehaviour::Checkpoint => { + //each of these checks if the model is actually a valid respective checkpoint object + //accumulate sequential ordered checkpoints + mode_state.accumulate_ordered_checkpoint(&stage, model_id); + //insert model id in accumulated unordered checkpoints + mode_state.accumulate_unordered_checkpoint(&stage, model_id); + } + } + } + } + } + if let Some(&gameplay_attributes::Wormhole { destination_model }) = wormhole { + if let (Some(origin), Some(destination)) = ( + models.get_model_transform(model_id), + models.get_model_transform(destination_model), + ) { + let point = body.position - origin.vertex.translation + destination.vertex.translation; + //TODO: camera angles + teleport( + point, + move_state, + body, + touching, + run, + mode_state, + mode, + models, + hitbox_mesh, + bvh, + style, + camera, + input_state, + time, + ); + } + } } -fn not_spawn_at( - mode:Option<&gameplay_modes::Mode>, - model_id:ModelId, -)->bool{ - if let Some(mode)=mode{ - if let Some(stage_element)=mode.get_element(model_id){ - return stage_element.behaviour()!=gameplay_modes::StageElementBehaviour::SpawnAt; - } - } - true +fn not_spawn_at(mode: Option<&gameplay_modes::Mode>, model_id: ModelId) -> bool { + if let Some(mode) = mode { + if let Some(stage_element) = mode.get_element(model_id) { + return stage_element.behaviour() != gameplay_modes::StageElementBehaviour::SpawnAt; + } + } + true } fn collision_start_contact( - move_state:&mut MoveState, - body:&mut Body, - mode_state:&mut ModeState, - touching:&mut TouchingState, - run:&mut run::Run, - mode:Option<&gameplay_modes::Mode>, - models:&PhysicsModels, - hitbox_mesh:&HitboxMesh, - bvh:&bvh::BvhNode, - style:&StyleModifiers, - camera:&PhysicsCamera, - input_state:&InputState, - attr:&gameplay_attributes::ContactAttributes, - contact:ContactCollision, - time:Time, -){ - let incident_velocity=body.velocity; - //add to touching - touching.insert(Collision::Contact(contact)); - //clip v - set_velocity(body,touching,models,hitbox_mesh,incident_velocity); - let mut allow_jump=true; - let model_id=contact.model_id.into(); - let mut allow_run_teleport_behaviour=not_spawn_at(mode,model_id); - match &attr.contacting.contact_behaviour{ - Some(gameplay_attributes::ContactingBehaviour::Surf)=>println!("I'm surfing!"), - Some(gameplay_attributes::ContactingBehaviour::Cling)=>println!("Unimplemented!"), - &Some(gameplay_attributes::ContactingBehaviour::Elastic(elasticity))=>{ - let reflected_velocity=body.velocity+((body.velocity-incident_velocity)*Planar64::raw(elasticity as i64+1)).fix_1(); - set_velocity(body,touching,models,hitbox_mesh,reflected_velocity); - }, - Some(gameplay_attributes::ContactingBehaviour::Ladder(contacting_ladder))=> - if let Some(ladder_settings)=&style.ladder{ - if contacting_ladder.sticky{ - //kill v - //actually you could do this with a booster attribute :thinking: - //it's a little bit different because maybe you want to chain ladders together - set_velocity(body,touching,models,hitbox_mesh,vec3::ZERO);//model.velocity - } - //ladder walkstate - let (gravity,target_velocity)=ladder_things(ladder_settings,&contact,touching,models,hitbox_mesh,style,camera,input_state); - let walk_state=ContactMoveState::ladder(ladder_settings,body,gravity,target_velocity,contact); - move_state.set_move_state(MoveState::Ladder(walk_state),body,touching,models,hitbox_mesh,style,camera,input_state); - }, - Some(gameplay_attributes::ContactingBehaviour::NoJump)=>allow_jump=false, - None=>if let Some(walk_settings)=&style.walk{ - if walk_settings.is_slope_walkable(contact_normal(models,hitbox_mesh,&contact),vec3::Y){ - allow_run_teleport_behaviour=true; - //ground - let (gravity,target_velocity)=ground_things(walk_settings,&contact,touching,models,hitbox_mesh,style,camera,input_state); - let walk_state=ContactMoveState::ground(walk_settings,body,gravity,target_velocity,contact); - move_state.set_move_state(MoveState::Walk(walk_state),body,touching,models,hitbox_mesh,style,camera,input_state); - } - }, - } - //I love making functions with 10 arguments to dodge the borrow checker - if allow_run_teleport_behaviour{ - run_teleport_behaviour(model_id,attr.general.wormhole.as_ref(),mode,move_state,body,touching,run,mode_state,models,hitbox_mesh,bvh,style,camera,input_state,time); - } - if allow_jump&&style.get_control(Controls::Jump,input_state.controls){ - if let (Some(jump_settings),Some(walk_state))=(&style.jump,move_state.get_walk_state()){ - let mut exceeded_jump_limit=false; - if let Some(mode)=mode{ - if let Some(stage_element)=mode.get_element(model_id){ - if !mode_state.try_increment_jump_count(model_id,stage_element.jump_limit()).is_allowed(){ - exceeded_jump_limit=true; - } - } - } - if exceeded_jump_limit{ - if let Some(mode)=mode{ - if let Some(spawn_model_id)=mode.get_spawn_model_id(mode_state.get_stage_id()){ - let _=teleport_to_spawn(spawn_model_id,move_state,body,touching,run,mode_state,mode,models,hitbox_mesh,bvh,style,camera,input_state,time); - } - } - }else{ - let jump_dir=walk_state.jump_direction.direction(models,hitbox_mesh,&walk_state.contact); - let jumped_velocity=jump_settings.jumped_velocity(style,jump_dir,body.velocity,attr.general.booster.as_ref()); - move_state.cull_velocity(jumped_velocity,body,touching,models,hitbox_mesh,style,camera,input_state); - } - } - } - match &attr.general.trajectory{ - Some(trajectory)=>{ - match trajectory{ - gameplay_attributes::SetTrajectory::AirTime(_)=>todo!(), - gameplay_attributes::SetTrajectory::Height(_)=>todo!(), - gameplay_attributes::SetTrajectory::TargetPointTime { target_point: _, time: _ }=>todo!(), - gameplay_attributes::SetTrajectory::TargetPointSpeed { target_point: _, speed: _, trajectory_choice: _ }=>todo!(), - &gameplay_attributes::SetTrajectory::Velocity(velocity)=>{ - move_state.cull_velocity(velocity,body,touching,models,hitbox_mesh,style,camera,input_state); - }, - gameplay_attributes::SetTrajectory::DotVelocity { direction: _, dot: _ }=>todo!(), - } - }, - None=>(), - } - //doing enum to set the acceleration when surfing - //doing input_and_body to refresh the walk state if you hit a wall while accelerating - move_state.apply_enum_and_input_and_body(body,touching,models,hitbox_mesh,style,camera,input_state); + move_state: &mut MoveState, + body: &mut Body, + mode_state: &mut ModeState, + touching: &mut TouchingState, + run: &mut run::Run, + mode: Option<&gameplay_modes::Mode>, + models: &PhysicsModels, + hitbox_mesh: &HitboxMesh, + bvh: &bvh::BvhNode, + style: &StyleModifiers, + camera: &PhysicsCamera, + input_state: &InputState, + attr: &gameplay_attributes::ContactAttributes, + contact: ContactCollision, + time: Time, +) { + let incident_velocity = body.velocity; + //add to touching + touching.insert(Collision::Contact(contact)); + //clip v + set_velocity(body, touching, models, hitbox_mesh, incident_velocity); + let mut allow_jump = true; + let model_id = contact.model_id.into(); + let mut allow_run_teleport_behaviour = not_spawn_at(mode, model_id); + match &attr.contacting.contact_behaviour { + Some(gameplay_attributes::ContactingBehaviour::Surf) => println!("I'm surfing!"), + Some(gameplay_attributes::ContactingBehaviour::Cling) => println!("Unimplemented!"), + &Some(gameplay_attributes::ContactingBehaviour::Elastic(elasticity)) => { + let reflected_velocity = body.velocity + + ((body.velocity - incident_velocity) * Planar64::raw(elasticity as i64 + 1)) + .fix_1(); + set_velocity(body, touching, models, hitbox_mesh, reflected_velocity); + } + Some(gameplay_attributes::ContactingBehaviour::Ladder(contacting_ladder)) => { + if let Some(ladder_settings) = &style.ladder { + if contacting_ladder.sticky { + //kill v + //actually you could do this with a booster attribute :thinking: + //it's a little bit different because maybe you want to chain ladders together + set_velocity(body, touching, models, hitbox_mesh, vec3::ZERO); + //model.velocity + } + //ladder walkstate + let (gravity, target_velocity) = ladder_things( + ladder_settings, + &contact, + touching, + models, + hitbox_mesh, + style, + camera, + input_state, + ); + let walk_state = ContactMoveState::ladder( + ladder_settings, + body, + gravity, + target_velocity, + contact, + ); + move_state.set_move_state( + MoveState::Ladder(walk_state), + body, + touching, + models, + hitbox_mesh, + style, + camera, + input_state, + ); + } + } + Some(gameplay_attributes::ContactingBehaviour::NoJump) => allow_jump = false, + None => { + if let Some(walk_settings) = &style.walk { + if walk_settings + .is_slope_walkable(contact_normal(models, hitbox_mesh, &contact), vec3::Y) + { + allow_run_teleport_behaviour = true; + //ground + let (gravity, target_velocity) = ground_things( + walk_settings, + &contact, + touching, + models, + hitbox_mesh, + style, + camera, + input_state, + ); + let walk_state = ContactMoveState::ground( + walk_settings, + body, + gravity, + target_velocity, + contact, + ); + move_state.set_move_state( + MoveState::Walk(walk_state), + body, + touching, + models, + hitbox_mesh, + style, + camera, + input_state, + ); + } + } + } + } + //I love making functions with 10 arguments to dodge the borrow checker + if allow_run_teleport_behaviour { + run_teleport_behaviour( + model_id, + attr.general.wormhole.as_ref(), + mode, + move_state, + body, + touching, + run, + mode_state, + models, + hitbox_mesh, + bvh, + style, + camera, + input_state, + time, + ); + } + if allow_jump && style.get_control(Controls::Jump, input_state.controls) { + if let (Some(jump_settings), Some(walk_state)) = (&style.jump, move_state.get_walk_state()) + { + let mut exceeded_jump_limit = false; + if let Some(mode) = mode { + if let Some(stage_element) = mode.get_element(model_id) { + if !mode_state + .try_increment_jump_count(model_id, stage_element.jump_limit()) + .is_allowed() + { + exceeded_jump_limit = true; + } + } + } + if exceeded_jump_limit { + if let Some(mode) = mode { + if let Some(spawn_model_id) = mode.get_spawn_model_id(mode_state.get_stage_id()) + { + let _ = teleport_to_spawn( + spawn_model_id, + move_state, + body, + touching, + run, + mode_state, + mode, + models, + hitbox_mesh, + bvh, + style, + camera, + input_state, + time, + ); + } + } + } else { + let jump_dir = + walk_state + .jump_direction + .direction(models, hitbox_mesh, &walk_state.contact); + let jumped_velocity = jump_settings.jumped_velocity( + style, + jump_dir, + body.velocity, + attr.general.booster.as_ref(), + ); + move_state.cull_velocity( + jumped_velocity, + body, + touching, + models, + hitbox_mesh, + style, + camera, + input_state, + ); + } + } + } + match &attr.general.trajectory { + Some(trajectory) => match trajectory { + gameplay_attributes::SetTrajectory::AirTime(_) => todo!(), + gameplay_attributes::SetTrajectory::Height(_) => todo!(), + gameplay_attributes::SetTrajectory::TargetPointTime { + target_point: _, + time: _, + } => todo!(), + gameplay_attributes::SetTrajectory::TargetPointSpeed { + target_point: _, + speed: _, + trajectory_choice: _, + } => todo!(), + &gameplay_attributes::SetTrajectory::Velocity(velocity) => { + move_state.cull_velocity( + velocity, + body, + touching, + models, + hitbox_mesh, + style, + camera, + input_state, + ); + } + gameplay_attributes::SetTrajectory::DotVelocity { + direction: _, + dot: _, + } => todo!(), + }, + None => (), + } + //doing enum to set the acceleration when surfing + //doing input_and_body to refresh the walk state if you hit a wall while accelerating + move_state.apply_enum_and_input_and_body( + body, + touching, + models, + hitbox_mesh, + style, + camera, + input_state, + ); } fn collision_start_intersect( - move_state:&mut MoveState, - body:&mut Body, - mode_state:&mut ModeState, - touching:&mut TouchingState, - mode:Option<&gameplay_modes::Mode>, - run:&mut run::Run, - models:&PhysicsModels, - hitbox_mesh:&HitboxMesh, - bvh:&bvh::BvhNode, - style:&StyleModifiers, - camera:&PhysicsCamera, - input_state:&InputState, - attr:&gameplay_attributes::IntersectAttributes, - intersect:IntersectCollision, - time:Time, -){ - //I think that setting the velocity to 0 was preventing surface contacts from entering an infinite loop - touching.insert(Collision::Intersect(intersect)); - //insta booster! - if let Some(booster)=&attr.general.booster{ - move_state.cull_velocity(booster.boost(body.velocity),body,touching,models,hitbox_mesh,style,camera,input_state); - } - if let Some(mode)=mode{ - let zone=mode.get_zone(intersect.model_id.into()); - match zone{ - Some(gameplay_modes::Zone::Start)=>{ - println!("@@@@ Starting new run!"); - *run=run::Run::new(); - }, - Some(gameplay_modes::Zone::Finish)=>{ - match run.finish(time){ - Ok(())=>println!("@@@@ Finished run time={}",run.time(time)), - Err(e)=>println!("@@@@ Run Finish error:{e:?}"), - } - }, - Some(gameplay_modes::Zone::Anticheat)=>run.flag(run::FlagReason::Anticheat), - None=>(), - } - } - run_teleport_behaviour(intersect.model_id.into(),attr.general.wormhole.as_ref(),mode,move_state,body,touching,run,mode_state,models,hitbox_mesh,bvh,style,camera,input_state,time); + move_state: &mut MoveState, + body: &mut Body, + mode_state: &mut ModeState, + touching: &mut TouchingState, + mode: Option<&gameplay_modes::Mode>, + run: &mut run::Run, + models: &PhysicsModels, + hitbox_mesh: &HitboxMesh, + bvh: &bvh::BvhNode, + style: &StyleModifiers, + camera: &PhysicsCamera, + input_state: &InputState, + attr: &gameplay_attributes::IntersectAttributes, + intersect: IntersectCollision, + time: Time, +) { + //I think that setting the velocity to 0 was preventing surface contacts from entering an infinite loop + touching.insert(Collision::Intersect(intersect)); + //insta booster! + if let Some(booster) = &attr.general.booster { + move_state.cull_velocity( + booster.boost(body.velocity), + body, + touching, + models, + hitbox_mesh, + style, + camera, + input_state, + ); + } + if let Some(mode) = mode { + let zone = mode.get_zone(intersect.model_id.into()); + match zone { + Some(gameplay_modes::Zone::Start) => { + println!("@@@@ Starting new run!"); + *run = run::Run::new(); + } + Some(gameplay_modes::Zone::Finish) => match run.finish(time) { + Ok(()) => println!("@@@@ Finished run time={}", run.time(time)), + Err(e) => println!("@@@@ Run Finish error:{e:?}"), + }, + Some(gameplay_modes::Zone::Anticheat) => run.flag(run::FlagReason::Anticheat), + None => (), + } + } + run_teleport_behaviour( + intersect.model_id.into(), + attr.general.wormhole.as_ref(), + mode, + move_state, + body, + touching, + run, + mode_state, + models, + hitbox_mesh, + bvh, + style, + camera, + input_state, + time, + ); } fn collision_end_contact( - move_state:&mut MoveState, - body:&mut Body, - touching:&mut TouchingState, - models:&PhysicsModels, - hitbox_mesh:&HitboxMesh, - style:&StyleModifiers, - camera:&PhysicsCamera, - input_state:&InputState, - _attr:&gameplay_attributes::ContactAttributes, - contact:ContactCollision, -){ - touching.remove(&Collision::Contact(contact));//remove contact before calling contact_constrain_acceleration - //check ground - //TODO do better - //this is inner code from move_state.cull_velocity - match move_state.get_walk_state(){ - //did you stop touching the thing you were walking on? - Some(walk_state)=>if walk_state.contact==contact{ - move_state.set_move_state(MoveState::Air,body,touching,models,hitbox_mesh,style,camera,input_state); - }, - None=>move_state.apply_enum_and_body(body,touching,models,hitbox_mesh,style,camera,input_state), - } + move_state: &mut MoveState, + body: &mut Body, + touching: &mut TouchingState, + models: &PhysicsModels, + hitbox_mesh: &HitboxMesh, + style: &StyleModifiers, + camera: &PhysicsCamera, + input_state: &InputState, + _attr: &gameplay_attributes::ContactAttributes, + contact: ContactCollision, +) { + touching.remove(&Collision::Contact(contact)); //remove contact before calling contact_constrain_acceleration + //check ground + //TODO do better + //this is inner code from move_state.cull_velocity + match move_state.get_walk_state() { + //did you stop touching the thing you were walking on? + Some(walk_state) => { + if walk_state.contact == contact { + move_state.set_move_state( + MoveState::Air, + body, + touching, + models, + hitbox_mesh, + style, + camera, + input_state, + ); + } + } + None => move_state.apply_enum_and_body( + body, + touching, + models, + hitbox_mesh, + style, + camera, + input_state, + ), + } } fn collision_end_intersect( - touching:&mut TouchingState, - mode:Option<&gameplay_modes::Mode>, - run:&mut run::Run, - _attr:&gameplay_attributes::IntersectAttributes, - intersect:IntersectCollision, - time:Time, -){ - touching.remove(&Collision::Intersect(intersect)); - if let Some(mode)=mode{ - let zone=mode.get_zone(intersect.model_id.into()); - match zone{ - Some(gameplay_modes::Zone::Start)=>{ - match run.start(time){ - Ok(())=>println!("@@@@ Started run"), - Err(e)=>println!("@@@@ Run Start error:{e:?}"), - } - }, - _=>(), - } - } + touching: &mut TouchingState, + mode: Option<&gameplay_modes::Mode>, + run: &mut run::Run, + _attr: &gameplay_attributes::IntersectAttributes, + intersect: IntersectCollision, + time: Time, +) { + touching.remove(&Collision::Intersect(intersect)); + if let Some(mode) = mode { + let zone = mode.get_zone(intersect.model_id.into()); + match zone { + Some(gameplay_modes::Zone::Start) => match run.start(time) { + Ok(()) => println!("@@@@ Started run"), + Err(e) => println!("@@@@ Run Start error:{e:?}"), + }, + _ => (), + } + } +} +fn atomic_internal_instruction( + state: &mut PhysicsState, + data: &PhysicsData, + ins: TimedInstruction, +) { + state.time = ins.time; + let (should_advance_body, goober_time) = match ins.instruction { + PhysicsInternalInstruction::CollisionStart(_, dt) + | PhysicsInternalInstruction::CollisionEnd(_, dt) => (true, Some(dt)), + PhysicsInternalInstruction::StrafeTick + | PhysicsInternalInstruction::ReachWalkTargetVelocity => (true, None), + }; + if should_advance_body { + match goober_time { + Some(dt) => state.body.advance_time_ratio_dt(dt), + None => state.body.advance_time(state.time), + } + } + match ins.instruction { + PhysicsInternalInstruction::CollisionStart(collision, _) => { + let mode = data.modes.get_mode(state.mode_state.get_mode_id()); + match collision { + Collision::Contact(contact) => collision_start_contact( + &mut state.move_state, + &mut state.body, + &mut state.mode_state, + &mut state.touching, + &mut state.run, + mode, + &data.models, + &data.hitbox_mesh, + &data.bvh, + &state.style, + &state.camera, + &state.input_state, + data.models.contact_attr(contact.model_id), + contact, + state.time, + ), + Collision::Intersect(intersect) => collision_start_intersect( + &mut state.move_state, + &mut state.body, + &mut state.mode_state, + &mut state.touching, + mode, + &mut state.run, + &data.models, + &data.hitbox_mesh, + &data.bvh, + &state.style, + &state.camera, + &state.input_state, + data.models.intersect_attr(intersect.model_id), + intersect, + state.time, + ), + } + } + PhysicsInternalInstruction::CollisionEnd(collision, _) => match collision { + Collision::Contact(contact) => collision_end_contact( + &mut state.move_state, + &mut state.body, + &mut state.touching, + &data.models, + &data.hitbox_mesh, + &state.style, + &state.camera, + &state.input_state, + data.models.contact_attr(contact.model_id), + contact, + ), + Collision::Intersect(intersect) => collision_end_intersect( + &mut state.touching, + data.modes.get_mode(state.mode_state.get_mode_id()), + &mut state.run, + data.models.intersect_attr(intersect.model_id), + intersect, + state.time, + ), + }, + PhysicsInternalInstruction::StrafeTick => { + //TODO make this less huge + if let Some(strafe_settings) = &state.style.strafe { + let controls = state.input_state.controls; + if strafe_settings.activates(controls) { + let masked_controls = strafe_settings.mask(controls); + let control_dir = state.style.get_control_dir(masked_controls); + if control_dir != vec3::ZERO { + let camera_mat = state + .camera + .simulate_move_rotation_y(state.input_state.lerp_delta(state.time).x); + if let Some(ticked_velocity) = strafe_settings.tick_velocity( + state.body.velocity, + (camera_mat * control_dir) + .with_length(Planar64::ONE) + .divide() + .fix_1(), + ) { + //this is wrong but will work ig + //need to note which push planes activate in push solve and keep those + state.cull_velocity(data, ticked_velocity); + } + } + } + } + } + PhysicsInternalInstruction::ReachWalkTargetVelocity => { + match &mut state.move_state { + MoveState::Air | MoveState::Water | MoveState::Fly => { + println!("ReachWalkTargetVelocity fired for non-walking MoveState") + } + MoveState::Walk(walk_state) | MoveState::Ladder(walk_state) => { + match &walk_state.target { + //you are not supposed to reach a walk target which is already reached! + TransientAcceleration::Reached => unreachable!(), + TransientAcceleration::Reachable { + acceleration: _, + time: _, + } => { + //velocity is already handled by advance_time + //we know that the acceleration is precisely zero because the walk target is known to be reachable + //which means that gravity can be fully cancelled + //ignore moving platforms for now + set_acceleration( + &mut state.body, + &state.touching, + &data.models, + &data.hitbox_mesh, + vec3::ZERO, + ); + walk_state.target = TransientAcceleration::Reached; + } + //you are not supposed to reach an unreachable walk target! + TransientAcceleration::Unreachable { acceleration: _ } => unreachable!(), + } + } + } + } + } } -fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction){ - state.time=ins.time; - let (should_advance_body,goober_time)=match ins.instruction{ - PhysicsInternalInstruction::CollisionStart(_,dt) - |PhysicsInternalInstruction::CollisionEnd(_,dt)=>(true,Some(dt)), - PhysicsInternalInstruction::StrafeTick - |PhysicsInternalInstruction::ReachWalkTargetVelocity=>(true,None), - }; - if should_advance_body{ - match goober_time{ - Some(dt)=>state.body.advance_time_ratio_dt(dt), - None=>state.body.advance_time(state.time), - } - } - match ins.instruction{ - PhysicsInternalInstruction::CollisionStart(collision,_)=>{ - let mode=data.modes.get_mode(state.mode_state.get_mode_id()); - match collision{ - Collision::Contact(contact)=>collision_start_contact( - &mut state.move_state,&mut state.body,&mut state.mode_state,&mut state.touching,&mut state.run, - mode, - &data.models,&data.hitbox_mesh,&data.bvh,&state.style,&state.camera,&state.input_state, - data.models.contact_attr(contact.model_id), - contact, - state.time, - ), - Collision::Intersect(intersect)=>collision_start_intersect( - &mut state.move_state,&mut state.body,&mut state.mode_state,&mut state.touching, - mode, - &mut state.run,&data.models,&data.hitbox_mesh,&data.bvh,&state.style,&state.camera,&state.input_state, - data.models.intersect_attr(intersect.model_id), - intersect, - state.time, - ), - } - }, - PhysicsInternalInstruction::CollisionEnd(collision,_)=>match collision{ - Collision::Contact(contact)=>collision_end_contact( - &mut state.move_state,&mut state.body,&mut state.touching,&data.models,&data.hitbox_mesh,&state.style,&state.camera,&state.input_state, - data.models.contact_attr(contact.model_id), - contact - ), - Collision::Intersect(intersect)=>collision_end_intersect( - &mut state.touching, - data.modes.get_mode(state.mode_state.get_mode_id()), - &mut state.run, - data.models.intersect_attr(intersect.model_id), - intersect, - state.time - ), - }, - PhysicsInternalInstruction::StrafeTick=>{ - //TODO make this less huge - if let Some(strafe_settings)=&state.style.strafe{ - let controls=state.input_state.controls; - if strafe_settings.activates(controls){ - let masked_controls=strafe_settings.mask(controls); - let control_dir=state.style.get_control_dir(masked_controls); - if control_dir!=vec3::ZERO{ - let camera_mat=state.camera.simulate_move_rotation_y(state.input_state.lerp_delta(state.time).x); - if let Some(ticked_velocity)=strafe_settings.tick_velocity(state.body.velocity,(camera_mat*control_dir).with_length(Planar64::ONE).divide().fix_1()){ - //this is wrong but will work ig - //need to note which push planes activate in push solve and keep those - state.cull_velocity(data,ticked_velocity); - } - } - } - } - } - PhysicsInternalInstruction::ReachWalkTargetVelocity=>{ - match &mut state.move_state{ - MoveState::Air - |MoveState::Water - |MoveState::Fly - =>println!("ReachWalkTargetVelocity fired for non-walking MoveState"), - MoveState::Walk(walk_state)|MoveState::Ladder(walk_state)=>{ - match &walk_state.target{ - //you are not supposed to reach a walk target which is already reached! - TransientAcceleration::Reached=>unreachable!(), - TransientAcceleration::Reachable{acceleration:_,time:_}=>{ - //velocity is already handled by advance_time - //we know that the acceleration is precisely zero because the walk target is known to be reachable - //which means that gravity can be fully cancelled - //ignore moving platforms for now - set_acceleration(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,vec3::ZERO); - walk_state.target=TransientAcceleration::Reached; - }, - //you are not supposed to reach an unreachable walk target! - TransientAcceleration::Unreachable{acceleration:_}=>unreachable!(), - } - } - } - }, - } - } -fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction){ - state.time=ins.time; - let should_advance_body=match ins.instruction{ - //the body may as well be a quantum wave function - //as far as these instruction are concerned (they don't care where it is) - PhysicsInputInstruction::SetSensitivity(..) - |PhysicsInputInstruction::Reset - |PhysicsInputInstruction::Restart - |PhysicsInputInstruction::Spawn(..) - |PhysicsInputInstruction::SetZoom(..) - |PhysicsInputInstruction::Idle=>false, - //these controls only update the body if you are on the ground - PhysicsInputInstruction::SetNextMouse(..) - |PhysicsInputInstruction::ReplaceMouse(..) - |PhysicsInputInstruction::SetMoveForward(..) - |PhysicsInputInstruction::SetMoveLeft(..) - |PhysicsInputInstruction::SetMoveBack(..) - |PhysicsInputInstruction::SetMoveRight(..) - |PhysicsInputInstruction::SetMoveUp(..) - |PhysicsInputInstruction::SetMoveDown(..) - |PhysicsInputInstruction::SetJump(..)=>{ - match &state.move_state{ - MoveState::Fly - |MoveState::Water - |MoveState::Walk(_) - |MoveState::Ladder(_)=>true, - MoveState::Air=>false, - } - }, - //the body must be updated unconditionally - PhysicsInputInstruction::PracticeFly=>true, - }; - if should_advance_body{ - state.body.advance_time(state.time); - } - //TODO: UNTAB - let mut b_refresh_walk_target=true; - match ins.instruction{ - PhysicsInputInstruction::SetSensitivity(sensitivity)=>state.camera.sensitivity=sensitivity, - PhysicsInputInstruction::SetNextMouse(m)=>{ - state.camera.move_mouse(state.input_state.mouse_delta()); - state.input_state.set_next_mouse(m); - }, - PhysicsInputInstruction::ReplaceMouse(m0,m1)=>{ - state.camera.move_mouse(m0.pos-state.input_state.mouse.pos); - state.input_state.replace_mouse(m0,m1); - }, - PhysicsInputInstruction::SetMoveForward(s)=>state.input_state.set_control(Controls::MoveForward,s), - PhysicsInputInstruction::SetMoveLeft(s)=>state.input_state.set_control(Controls::MoveLeft,s), - PhysicsInputInstruction::SetMoveBack(s)=>state.input_state.set_control(Controls::MoveBackward,s), - PhysicsInputInstruction::SetMoveRight(s)=>state.input_state.set_control(Controls::MoveRight,s), - PhysicsInputInstruction::SetMoveUp(s)=>state.input_state.set_control(Controls::MoveUp,s), - PhysicsInputInstruction::SetMoveDown(s)=>state.input_state.set_control(Controls::MoveDown,s), - PhysicsInputInstruction::SetJump(s)=>{ - state.input_state.set_control(Controls::Jump,s); - if let Some(walk_state)=state.move_state.get_walk_state(){ - if let Some(jump_settings)=&state.style.jump{ - let jump_dir=walk_state.jump_direction.direction(&data.models,&data.hitbox_mesh,&walk_state.contact); - let booster_option=data.models.contact_attr(walk_state.contact.model_id).general.booster.as_ref(); - let jumped_velocity=jump_settings.jumped_velocity(&state.style,jump_dir,state.body.velocity,booster_option); - state.cull_velocity(&data,jumped_velocity); - } - } - b_refresh_walk_target=false; - }, - PhysicsInputInstruction::SetZoom(s)=>{ - state.input_state.set_control(Controls::Zoom,s); - b_refresh_walk_target=false; - }, - PhysicsInputInstruction::Reset=>{ - //totally reset physics state - state.reset_to_default(); - b_refresh_walk_target=false; - }, - PhysicsInputInstruction::Restart=>{ - //teleport to start zone - let mode=data.modes.get_mode(state.mode_state.get_mode_id()); - let spawn_point=mode.and_then(|mode| +fn atomic_input_instruction( + state: &mut PhysicsState, + data: &PhysicsData, + ins: TimedInstruction, +) { + state.time = ins.time; + let should_advance_body = match ins.instruction { + //the body may as well be a quantum wave function + //as far as these instruction are concerned (they don't care where it is) + PhysicsInputInstruction::SetSensitivity(..) + | PhysicsInputInstruction::Reset + | PhysicsInputInstruction::Restart + | PhysicsInputInstruction::Spawn(..) + | PhysicsInputInstruction::SetZoom(..) + | PhysicsInputInstruction::Idle => false, + //these controls only update the body if you are on the ground + PhysicsInputInstruction::SetNextMouse(..) + | PhysicsInputInstruction::ReplaceMouse(..) + | PhysicsInputInstruction::SetMoveForward(..) + | PhysicsInputInstruction::SetMoveLeft(..) + | PhysicsInputInstruction::SetMoveBack(..) + | PhysicsInputInstruction::SetMoveRight(..) + | PhysicsInputInstruction::SetMoveUp(..) + | PhysicsInputInstruction::SetMoveDown(..) + | PhysicsInputInstruction::SetJump(..) => match &state.move_state { + MoveState::Fly | MoveState::Water | MoveState::Walk(_) | MoveState::Ladder(_) => true, + MoveState::Air => false, + }, + //the body must be updated unconditionally + PhysicsInputInstruction::PracticeFly => true, + }; + if should_advance_body { + state.body.advance_time(state.time); + } + //TODO: UNTAB + let mut b_refresh_walk_target = true; + match ins.instruction { + PhysicsInputInstruction::SetSensitivity(sensitivity) => { + state.camera.sensitivity = sensitivity + } + PhysicsInputInstruction::SetNextMouse(m) => { + state.camera.move_mouse(state.input_state.mouse_delta()); + state.input_state.set_next_mouse(m); + } + PhysicsInputInstruction::ReplaceMouse(m0, m1) => { + state + .camera + .move_mouse(m0.pos - state.input_state.mouse.pos); + state.input_state.replace_mouse(m0, m1); + } + PhysicsInputInstruction::SetMoveForward(s) => { + state.input_state.set_control(Controls::MoveForward, s) + } + PhysicsInputInstruction::SetMoveLeft(s) => { + state.input_state.set_control(Controls::MoveLeft, s) + } + PhysicsInputInstruction::SetMoveBack(s) => { + state.input_state.set_control(Controls::MoveBackward, s) + } + PhysicsInputInstruction::SetMoveRight(s) => { + state.input_state.set_control(Controls::MoveRight, s) + } + PhysicsInputInstruction::SetMoveUp(s) => state.input_state.set_control(Controls::MoveUp, s), + PhysicsInputInstruction::SetMoveDown(s) => { + state.input_state.set_control(Controls::MoveDown, s) + } + PhysicsInputInstruction::SetJump(s) => { + state.input_state.set_control(Controls::Jump, s); + if let Some(walk_state) = state.move_state.get_walk_state() { + if let Some(jump_settings) = &state.style.jump { + let jump_dir = walk_state.jump_direction.direction( + &data.models, + &data.hitbox_mesh, + &walk_state.contact, + ); + let booster_option = data + .models + .contact_attr(walk_state.contact.model_id) + .general + .booster + .as_ref(); + let jumped_velocity = jump_settings.jumped_velocity( + &state.style, + jump_dir, + state.body.velocity, + booster_option, + ); + state.cull_velocity(&data, jumped_velocity); + } + } + b_refresh_walk_target = false; + } + PhysicsInputInstruction::SetZoom(s) => { + state.input_state.set_control(Controls::Zoom, s); + b_refresh_walk_target = false; + } + PhysicsInputInstruction::Reset => { + //totally reset physics state + state.reset_to_default(); + b_refresh_walk_target = false; + } + PhysicsInputInstruction::Restart => { + //teleport to start zone + let mode = data.modes.get_mode(state.mode_state.get_mode_id()); + let spawn_point = mode + .and_then(|mode| //TODO: spawn at the bottom of the start zone plus the hitbox size //TODO: set camera andles to face the same way as the start zone data.models.get_model_transform(mode.get_start().into()).map(|transform| transform.vertex.translation - ) - ).unwrap_or(vec3::ZERO); - set_position(spawn_point,&mut state.move_state,&mut state.body,&mut state.touching,&mut state.run,&mut state.mode_state,mode,&data.models,&data.hitbox_mesh,&data.bvh,&state.style,&state.camera,&state.input_state,state.time); - set_velocity(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,vec3::ZERO); - state.set_move_state(data,MoveState::Air); - b_refresh_walk_target=false; - } - PhysicsInputInstruction::Spawn(mode_id,stage_id)=>{ - //spawn at a particular stage - if let Some(mode)=data.modes.get_mode(mode_id){ - if let Some(stage)=mode.get_stage(stage_id){ - let _=teleport_to_spawn( - stage.spawn(), - &mut state.move_state,&mut state.body,&mut state.touching,&mut state.run,&mut state.mode_state, - mode, - &data.models,&data.hitbox_mesh,&data.bvh,&state.style,&state.camera,&state.input_state,state.time - ); - } - } - b_refresh_walk_target=false; - }, - PhysicsInputInstruction::PracticeFly=>{ - match &state.move_state{ - MoveState::Fly=>{ - state.set_move_state(data,MoveState::Air); - }, - _=>{ - state.set_move_state(data,MoveState::Fly); - }, - } - b_refresh_walk_target=false; - }, - PhysicsInputInstruction::Idle=>{ - //literally idle! - b_refresh_walk_target=false; - }, - } - if b_refresh_walk_target{ - state.apply_input_and_body(data); - state.cull_velocity(data,state.body.velocity); - //also check if accelerating away from surface - } + )) + .unwrap_or(vec3::ZERO); + set_position( + spawn_point, + &mut state.move_state, + &mut state.body, + &mut state.touching, + &mut state.run, + &mut state.mode_state, + mode, + &data.models, + &data.hitbox_mesh, + &data.bvh, + &state.style, + &state.camera, + &state.input_state, + state.time, + ); + set_velocity( + &mut state.body, + &state.touching, + &data.models, + &data.hitbox_mesh, + vec3::ZERO, + ); + state.set_move_state(data, MoveState::Air); + b_refresh_walk_target = false; + } + PhysicsInputInstruction::Spawn(mode_id, stage_id) => { + //spawn at a particular stage + if let Some(mode) = data.modes.get_mode(mode_id) { + if let Some(stage) = mode.get_stage(stage_id) { + let _ = teleport_to_spawn( + stage.spawn(), + &mut state.move_state, + &mut state.body, + &mut state.touching, + &mut state.run, + &mut state.mode_state, + mode, + &data.models, + &data.hitbox_mesh, + &data.bvh, + &state.style, + &state.camera, + &state.input_state, + state.time, + ); + } + } + b_refresh_walk_target = false; + } + PhysicsInputInstruction::PracticeFly => { + match &state.move_state { + MoveState::Fly => { + state.set_move_state(data, MoveState::Air); + } + _ => { + state.set_move_state(data, MoveState::Fly); + } + } + b_refresh_walk_target = false; + } + PhysicsInputInstruction::Idle => { + //literally idle! + b_refresh_walk_target = false; + } + } + if b_refresh_walk_target { + state.apply_input_and_body(data); + state.cull_velocity(data, state.body.velocity); + //also check if accelerating away from surface + } } - fn atomic_state_update(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction){ - match &ins.instruction{ - PhysicsInstruction::Input(PhysicsInputInstruction::Idle) - |PhysicsInstruction::Input(PhysicsInputInstruction::SetNextMouse(_)) - |PhysicsInstruction::Input(PhysicsInputInstruction::ReplaceMouse(_,_)) - |PhysicsInstruction::Internal(PhysicsInternalInstruction::StrafeTick) - |PhysicsInstruction::Internal(PhysicsInternalInstruction::ReachWalkTargetVelocity)=>(), - _=>println!("{}|{:?}",ins.time,ins.instruction), - } - if ins.time(), - PhysicsInstruction::Internal(instruction)=>atomic_internal_instruction(state,data,TimedInstruction{time:ins.time,instruction}), - PhysicsInstruction::Input(instruction)=>atomic_input_instruction(state,data,TimedInstruction{time:ins.time,instruction}), - } - } +fn atomic_state_update( + state: &mut PhysicsState, + data: &PhysicsData, + ins: TimedInstruction, +) { + match &ins.instruction { + PhysicsInstruction::Input(PhysicsInputInstruction::Idle) + | PhysicsInstruction::Input(PhysicsInputInstruction::SetNextMouse(_)) + | PhysicsInstruction::Input(PhysicsInputInstruction::ReplaceMouse(_, _)) + | PhysicsInstruction::Internal(PhysicsInternalInstruction::StrafeTick) + | PhysicsInstruction::Internal(PhysicsInternalInstruction::ReachWalkTargetVelocity) => (), + _ => println!("{}|{:?}", ins.time, ins.instruction), + } + if ins.time < state.time { + println!( + "@@@@ Time travel warning! state.time={} ins.time={}\nInstruction={:?}", + state.time, ins.time, ins.instruction + ); + } + //idle is special, it is specifically a no-op to get Internal events to catch up to real time + match ins.instruction { + PhysicsInstruction::Input(PhysicsInputInstruction::Idle) => (), + PhysicsInstruction::Internal(instruction) => atomic_internal_instruction( + state, + data, + TimedInstruction { + time: ins.time, + instruction, + }, + ), + PhysicsInstruction::Input(instruction) => atomic_input_instruction( + state, + data, + TimedInstruction { + time: ins.time, + instruction, + }, + ), + } +} #[cfg(test)] -mod test{ - use strafesnet_common::integer::{vec3::{self,int as int3},mat3}; - use crate::body::VirtualBody; - use super::*; - fn test_collision_axis_aligned(relative_body:Body,expected_collision_time:Option