diff --git a/Cargo.lock b/Cargo.lock index 0f857d8..0932d7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -105,9 +105,9 @@ checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a" [[package]] name = "arrayvec" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "as-raw-xcb-connection" @@ -245,6 +245,12 @@ dependencies = [ "objc2", ] +[[package]] +name = "bnum" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50202def95bf36cb7d1d7a7962cea1c36a3f8ad42425e5d2b71d7acb8041b5b8" + [[package]] name = "bstr" version = "1.10.0" @@ -624,6 +630,18 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "835a3dc7d1ec9e75e2b5fb4ba75396837112d2060b03f7d43bc1897c7f7211da" +[[package]] +name = "fixed_wide" +version = "0.1.0" +source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" +checksum = "7a8d6e10c51c9df39ead915c62288afbc41d13e00368e526037e530ee5c58e13" +dependencies = [ + "arrayvec", + "bnum", + "paste", + "ratio_ops", +] + [[package]] name = "foreign-types" version = "0.5.0" @@ -942,6 +960,17 @@ dependencies = [ "redox_syscall 0.4.1", ] +[[package]] +name = "linear_ops" +version = "0.1.0" +source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" +checksum = "b2e6977ac24f47086d8a7a2d4ae1c720e86dfdc8407cf5e34c18bfa01053c456" +dependencies = [ + "fixed_wide", + "paste", + "ratio_ops", +] + [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -1609,6 +1638,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8a99fddc9f0ba0a85884b8d14e3592853e787d581ca1816c91349b10e4eeab" +[[package]] +name = "ratio_ops" +version = "0.1.0" +source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" +checksum = "01239195d6afe0509e7e3511b716c0540251dfe7ece0a9a5a27116afb766c42c" + [[package]] name = "raw-window-handle" version = "0.6.2" @@ -1955,7 +1990,7 @@ dependencies = [ "parking_lot", "pollster", "strafesnet_bsp_loader", - "strafesnet_common", + "strafesnet_common 0.5.0", "strafesnet_deferred_loader", "strafesnet_rbx_loader", "strafesnet_snf", @@ -1970,7 +2005,7 @@ source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" checksum = "35ee2c534efa039ad17ca41893ba1d75fafff014076353ac676c73fc808b9e44" dependencies = [ "glam", - "strafesnet_common", + "strafesnet_common 0.4.1", "vbsp", "vmdl", ] @@ -1987,6 +2022,21 @@ dependencies = [ "id", ] +[[package]] +name = "strafesnet_common" +version = "0.5.0" +source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" +checksum = "d8fcc44793ae84a1d80882f367980913292241c94eb87584de4010bdad4a918d" +dependencies = [ + "arrayvec", + "bitflags 2.6.0", + "fixed_wide", + "glam", + "id", + "linear_ops", + "ratio_ops", +] + [[package]] name = "strafesnet_deferred_loader" version = "0.3.3" @@ -1994,7 +2044,7 @@ source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" checksum = "596aba6d2747818781336ad95a1ee496e37f70052fd625a299fc7a555a6938d4" dependencies = [ "lazy-regex", - "strafesnet_common", + "strafesnet_common 0.4.1", "vbsp", ] @@ -2013,18 +2063,18 @@ dependencies = [ "rbx_reflection_database", "rbx_xml", "roblox_emulator", - "strafesnet_common", + "strafesnet_common 0.4.1", ] [[package]] name = "strafesnet_snf" -version = "0.1.3" +version = "0.2.0" source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" -checksum = "a9ae481152d0389be29967e1d5f0377498df8ff9638175d56cd8e2c2e6982bfa" +checksum = "c6e8856d79c29bd5687b08bc1653370f7e242c84d5c06afa8629bd3e00c433bf" dependencies = [ "binrw 0.14.0", "id", - "strafesnet_common", + "strafesnet_common 0.5.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 1b8fbdf..cb34835 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,10 +23,10 @@ id = { version = "0.1.0", registry = "strafesnet" } parking_lot = "0.12.1" pollster = "0.3.0" strafesnet_bsp_loader = { version = "0.1.3", registry = "strafesnet", optional = true } -strafesnet_common = { version = "0.4.0", registry = "strafesnet" } +strafesnet_common = { version = "0.5.0", registry = "strafesnet" } strafesnet_deferred_loader = { version = "0.3.1", features = ["legacy"], registry = "strafesnet", optional = true } strafesnet_rbx_loader = { version = "0.3.7", registry = "strafesnet", optional = true } -strafesnet_snf = { version = "0.1.2", registry = "strafesnet", optional = true } +strafesnet_snf = { version = "0.2.0", registry = "strafesnet", optional = true } wgpu = "22.0.0" winit = "0.30.4" diff --git a/src/face_crawler.rs b/src/face_crawler.rs index 27df013..4fc22d9 100644 --- a/src/face_crawler.rs +++ b/src/face_crawler.rs @@ -1,32 +1,33 @@ use crate::physics::Body; -use crate::model_physics::{FEV,MeshQuery,DirectedEdge}; -use strafesnet_common::integer::{Time,Planar64}; -use strafesnet_common::zeroes::zeroes2; +use crate::model_physics::{GigaTime,FEV,MeshQuery,DirectedEdge,MinkowskiMesh,MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert}; +use strafesnet_common::integer::{Time,Fixed,Ratio}; +#[derive(Debug)] enum Transition{ Miss, - Next(FEV,Time), - Hit(F,Time), + Next(FEV,GigaTime), + Hit(F,GigaTime), } - fn next_transition(fev:&FEV,time:Time,mesh:&impl MeshQuery,body:&Body,time_limit:Time)->Transition{ +type MinkowskiFEV=FEV; +type MinkowskiTransition=Transition; + + fn next_transition(fev:&MinkowskiFEV,body_time:GigaTime,mesh:&MinkowskiMesh,body:&Body,mut best_time:GigaTime)->MinkowskiTransition{ //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_time=time_limit; - let mut best_transtition=Transition::Miss; + let mut best_transition=MinkowskiTransition::Miss; match fev{ - &FEV::::Face(face_id)=>{ + &MinkowskiFEV::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 t in zeroes2((n.dot(body.position)-d)*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){ - let t=body.time+Time::from(t); - if time<=t&&t::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=MinkowskiTransition::Hit(face_id,dt); break; } } @@ -36,18 +37,18 @@ enum Transition{ 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! - for t in zeroes2(n.dot(body.position*2-(mesh.vert(verts[0])+mesh.vert(verts[1]))),n.dot(body.velocity)*2,n.dot(body.acceleration)){ - let t=body.time+Time::from(t); - if time<=t&&t::Edge(directed_edge_id.as_undirected()),t); + //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=MinkowskiTransition::Next(MinkowskiFEV::Edge(directed_edge_id.as_undirected()),dt); break; } } } //if none: }, - &FEV::::Edge(edge_id)=>{ + &MinkowskiFEV::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); @@ -57,11 +58,10 @@ enum Transition{ //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 t in zeroes2(n.dot(delta_pos),n.dot(body.velocity)*2,n.dot(body.acceleration)){ - let t=body.time+Time::from(t); - if time<=t&&t::Face(edge_face_id),t); + 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=MinkowskiTransition::Next(MinkowskiFEV::Face(edge_face_id),dt); break; } } @@ -70,27 +70,27 @@ enum Transition{ 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 t in zeroes2((n.dot(body.position-mesh.vert(vert_id)))*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){ - let t=body.time+Time::from(t); - if time<=t&&t::Vert(vert_id),t); + 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=MinkowskiTransition::Next(MinkowskiFEV::Vert(vert_id),dt); break; } } } //if none: }, - &FEV::::Vert(vert_id)=>{ + &MinkowskiFEV::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 t in zeroes2((n.dot(body.position-mesh.vert(vert_id)))*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){ - let t=body.time+Time::from(t); - if time<=t&&t::Edge(directed_edge_id.as_undirected()),t); + 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=MinkowskiTransition::Next(MinkowskiFEV::Edge(directed_edge_id.as_undirected()),dt); break; } } @@ -98,18 +98,26 @@ enum Transition{ //if none: }, } - best_transtition + best_transition } pub enum CrawlResult{ Miss(FEV), - Hit(F,Time), + Hit(F,GigaTime), } -pub fn crawl_fev(mut fev:FEV,mesh:&impl MeshQuery,relative_body:&Body,start_time:Time,time_limit:Time)->CrawlResult{ - let mut time=start_time; +type MinkowskiCrawlResult=CrawlResult; +pub fn crawl_fev(mut fev:MinkowskiFEV,mesh:&MinkowskiMesh,relative_body:&Body,start_time:Time,time_limit:Time)->MinkowskiCrawlResult{ + 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 next_transition(&fev,time,mesh,relative_body,time_limit){ + match next_transition(&fev,body_time,mesh,relative_body,time_limit){ Transition::Miss=>return CrawlResult::Miss(fev), - Transition::Next(next_fev,next_time)=>(fev,time)=(next_fev,next_time), + Transition::Next(next_fev,next_time)=>(fev,body_time)=(next_fev,next_time), Transition::Hit(face,time)=>return CrawlResult::Hit(face,time), } } diff --git a/src/graphics.rs b/src/graphics.rs index 2af4816..6a13b11 100644 --- a/src/graphics.rs +++ b/src/graphics.rs @@ -219,7 +219,7 @@ impl GraphicsState{ //wow let instance=GraphicsModelOwned{ transform:model.transform.into(), - normal_transform:Into::::into(model.transform.matrix3).inverse().transpose(), + 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 @@ -238,9 +238,9 @@ impl GraphicsState{ //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|*Into::::into(v).as_ref()).collect(), + 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|*Into::::into(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, @@ -890,7 +890,7 @@ impl GraphicsState{ // update rotation let camera_uniforms=self.camera.to_uniform_data( - frame_state.body.extrapolated_position(frame_state.time).into(), + 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 diff --git a/src/model_physics.rs b/src/model_physics.rs index 0815e4f..243c473 100644 --- a/src/model_physics.rs +++ b/src/model_physics.rs @@ -1,15 +1,15 @@ use std::borrow::{Borrow,Cow}; use std::collections::{HashSet,HashMap}; +use strafesnet_common::integer::vec3::Vector3; use strafesnet_common::model::{self,MeshId,PolygonIter}; -use strafesnet_common::zeroes; -use strafesnet_common::integer::{self,Planar64,Planar64Vec3}; +use strafesnet_common::integer::{self,vec3,Fixed,Planar64,Planar64Vec3,Ratio}; pub trait UndirectedEdge{ type DirectedEdge:Copy+DirectedEdge; fn as_directed(&self,parity:bool)->Self::DirectedEdge; } pub trait DirectedEdge{ - type UndirectedEdge:Copy+UndirectedEdge; + type UndirectedEdge:Copy+std::fmt::Debug+UndirectedEdge; fn as_undirected(&self)->Self::UndirectedEdge; fn parity(&self)->bool; //this is stupid but may work fine @@ -50,6 +50,7 @@ impl DirectedEdge for SubmeshDirectedEdgeId{ } //Vertex <-> Edge <-> Face -> Collide +#[derive(Debug)] pub enum FEV{ Face(F), Edge(E::UndirectedEdge), @@ -64,6 +65,9 @@ struct Face{ } struct Vert(Planar64Vec3); pub trait MeshQuery{ + // Vertex must be Planar64Vec3 because it represents an actual position + type Normal; + type Offset; fn edge_n(&self,edge_id:EDGE::UndirectedEdge)->Planar64Vec3{ let verts=self.edge_verts(edge_id); self.vert(verts[1].clone())-self.vert(verts[0].clone()) @@ -73,7 +77,7 @@ pub trait MeshQuery{ (self.vert(verts[1].clone())-self.vert(verts[0].clone()))*((directed_edge_id.parity() as i64)*2-1) } fn vert(&self,vert_id:VERT)->Planar64Vec3; - fn face_nd(&self,face_id:FACE)->(Planar64Vec3,Planar64); + fn face_nd(&self,face_id:FACE)->(Self::Normal,Self::Offset); fn face_edges(&self,face_id:FACE)->Cow>; fn edge_faces(&self,edge_id:EDGE::UndirectedEdge)->Cow<[FACE;2]>; fn edge_verts(&self,edge_id:EDGE::UndirectedEdge)->Cow<[VERT;2]>; @@ -137,22 +141,22 @@ impl PhysicsMesh{ //go go gadget debug print mesh let data=PhysicsMeshData{ faces:vec![ - Face{normal:Planar64Vec3::raw_xyz( 4294967296, 0, 0),dot:Planar64::raw(4294967296)}, - Face{normal:Planar64Vec3::raw_xyz( 0, 4294967296, 0),dot:Planar64::raw(4294967296)}, - Face{normal:Planar64Vec3::raw_xyz( 0, 0, 4294967296),dot:Planar64::raw(4294967296)}, - Face{normal:Planar64Vec3::raw_xyz(-4294967296, 0, 0),dot:Planar64::raw(4294967296)}, - Face{normal:Planar64Vec3::raw_xyz( 0,-4294967296, 0),dot:Planar64::raw(4294967296)}, - Face{normal:Planar64Vec3::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)}, + 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(Planar64Vec3::raw_xyz( 4294967296,-4294967296,-4294967296)), - Vert(Planar64Vec3::raw_xyz( 4294967296, 4294967296,-4294967296)), - Vert(Planar64Vec3::raw_xyz( 4294967296, 4294967296, 4294967296)), - Vert(Planar64Vec3::raw_xyz( 4294967296,-4294967296, 4294967296)), - Vert(Planar64Vec3::raw_xyz(-4294967296, 4294967296,-4294967296)), - Vert(Planar64Vec3::raw_xyz(-4294967296, 4294967296, 4294967296)), - Vert(Planar64Vec3::raw_xyz(-4294967296,-4294967296, 4294967296)), - Vert(Planar64Vec3::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)), + Vert(vec3::raw_xyz(-4294967296,-4294967296,-4294967296)) ] }; let mesh_topology=PhysicsMeshTopology{ @@ -330,7 +334,7 @@ impl TryFrom<&model::Mesh> for PhysicsMesh{ 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=Planar64Vec3::ZERO; + 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); @@ -341,11 +345,11 @@ impl TryFrom<&model::Mesh> for PhysicsMesh{ //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+=Planar64Vec3::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()), - ); + 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); @@ -362,14 +366,16 @@ impl TryFrom<&model::Mesh> for PhysicsMesh{ //return directed_edge_id edge_id.as_directed(is_sorted) }).collect(); - //choose precision loss randomly idk - normal=normal/len as i64; - let mut dot=Planar64::ZERO; + 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,dot:dot/len as i64}; + 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=>{ @@ -416,6 +422,8 @@ pub struct PhysicsMeshView<'a>{ topology:&'a PhysicsMeshTopology, } impl MeshQuery for PhysicsMeshView<'_>{ + 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) @@ -444,14 +452,14 @@ impl MeshQuery for PhysicsMes pub struct PhysicsMeshTransform{ pub vertex:integer::Planar64Affine3, - pub normal:integer::Planar64Mat3, - pub det:Planar64, + pub normal:integer::mat3::Matrix3>, + pub det:Fixed<3,96>, } impl PhysicsMeshTransform{ - pub const fn new(transform:integer::Planar64Affine3)->Self{ + pub fn new(transform:integer::Planar64Affine3)->Self{ Self{ - normal:transform.matrix3.inverse_times_det().transpose(), - det:transform.matrix3.determinant(), + normal:transform.matrix3.adjugate().transpose(), + det:transform.matrix3.det(), vertex:transform, } } @@ -471,33 +479,33 @@ impl TransformedMesh<'_>{ transform, } } - pub fn verts<'a>(&'a self)->impl Iterator+'a{ + 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{ - let mut best_dot=Planar64::MIN; - let mut best_vert=SubmeshVertId(0); //this happens to be well-defined. there are no virtual virtices - for (i,vert_id) in self.view.topology.verts.iter().enumerate(){ - let p=self.transform.vertex.transform_point3(self.view.data.verts[vert_id.get() as usize].0); - let d=dir.dot(p); - if best_dot for TransformedMesh<'_>{ - fn face_nd(&self,face_id:SubmeshFaceId)->(Planar64Vec3,Planar64){ + 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+transformed_n.dot(self.transform.vertex.translation)/self.transform.det; - (transformed_n/self.transform.det,transformed_d) + 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)) + self.transform.vertex.transform_point3(self.view.vert(vert_id)).fix_1() } #[inline] fn face_edges(&self,face_id:SubmeshFaceId)->Cow>{ @@ -525,11 +533,11 @@ impl MeshQuery for Transforme //(face,vertex) //(edge,edge) //(vertex,face) -#[derive(Clone,Copy)] +#[derive(Clone,Copy,Debug)] pub enum MinkowskiVert{ VertVert(SubmeshVertId,SubmeshVertId), } -#[derive(Clone,Copy)] +#[derive(Clone,Copy,Debug)] pub enum MinkowskiEdge{ VertEdge(SubmeshVertId,SubmeshEdgeId), EdgeVert(SubmeshEdgeId,SubmeshVertId), @@ -544,7 +552,7 @@ impl UndirectedEdge for MinkowskiEdge{ } } } -#[derive(Clone,Copy)] +#[derive(Clone,Copy,Debug)] pub enum MinkowskiDirectedEdge{ VertEdge(SubmeshVertId,SubmeshDirectedEdgeId), EdgeVert(SubmeshDirectedEdgeId,SubmeshVertId), @@ -565,7 +573,7 @@ impl DirectedEdge for MinkowskiDirectedEdge{ } } } -#[derive(Debug,Clone,Copy,Hash,Eq,PartialEq)] +#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq)] pub enum MinkowskiFace{ VertFace(SubmeshVertId,SubmeshFaceId), EdgeEdge(SubmeshEdgeId,SubmeshEdgeId,bool), @@ -581,6 +589,7 @@ pub struct MinkowskiMesh<'a>{ } //infinity fev algorithm state transition +#[derive(Debug)] enum Transition{ Done,//found closest vert, no edges are better Vert(MinkowskiVert),//transition to vert @@ -590,6 +599,8 @@ enum EV{ Edge(MinkowskiEdge), } +pub type GigaTime=Ratio,Fixed<4,128>>; + impl MinkowskiMesh<'_>{ pub fn minkowski_sum<'a>(mesh0:TransformedMesh<'a>,mesh1:TransformedMesh<'a>)->MinkowskiMesh<'a>{ MinkowskiMesh{ @@ -600,7 +611,7 @@ impl MinkowskiMesh<'_>{ 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 Planar64,infinity_dir:Planar64Vec3,point:Planar64Vec3)->Transition{ + 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); @@ -610,7 +621,7 @@ impl MinkowskiMesh<'_>{ 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 zeroes::zeroes1(edge_n.dot(diff),edge_n.dot(infinity_dir)).len()==0{ + 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); @@ -620,21 +631,21 @@ impl MinkowskiMesh<'_>{ } best_transition } - fn final_ev(&self,vert_id:MinkowskiVert,best_distance_squared:&mut Planar64,infinity_dir:Planar64Vec3,point:Planar64Vec3)->EV{ + 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 - let d=edge_n.dot(diff); - if zeroes::zeroes1(d,edge_n.dot(infinity_dir)).len()==0{ + 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 Planar64::ZERO<=d&&d<=edge_nn{ + if !d.is_negative()&&d<=edge_nn{ let distance_squared={ let c=diff.cross(edge_n); - c.dot(c)/edge_nn + (c.dot(c)/edge_nn).divide().fix_2() }; if distance_squared<=*best_distance_squared{ best_transition=EV::Edge(directed_edge_id.as_undirected()); @@ -680,7 +691,7 @@ impl MinkowskiMesh<'_>{ 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)<=Planar64::ZERO&&zeroes::zeroes1(boundary_d,boundary_n.dot(infinity_dir)*2).len()==0{ + 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); } @@ -694,15 +705,16 @@ impl MinkowskiMesh<'_>{ 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=Planar64Vec3::ZERO; + infinity_body.acceleration=vec3::ZERO; //crawl in from negative infinity along a tangent line to get the closest fev - match crate::face_crawler::crawl_fev(infinity_fev,self,&infinity_body,integer::Time::MIN,infinity_body.time){ + // TODO: change crawl_fev args to delta time? Optional values? + match crate::face_crawler::crawl_fev(infinity_fev,self,&infinity_body,integer::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:&crate::physics::Body,time_limit:integer::Time)->Option<(MinkowskiFace,integer::Time)>{ + pub fn predict_collision_in(&self,relative_body:&crate::physics::Body,time_limit:integer::Time)->Option<(MinkowskiFace,GigaTime)>{ self.closest_fev_not_inside(relative_body.clone()).map_or(None,|fev|{ //continue forwards along the body parabola match crate::face_crawler::crawl_fev(fev,self,relative_body,relative_body.time,time_limit){ @@ -711,7 +723,7 @@ impl MinkowskiMesh<'_>{ } }) } - pub fn predict_collision_out(&self,relative_body:&crate::physics::Body,time_limit:integer::Time)->Option<(MinkowskiFace,integer::Time)>{ + pub fn predict_collision_out(&self,relative_body:&crate::physics::Body,time_limit:integer::Time)->Option<(MinkowskiFace,GigaTime)>{ //create an extrapolated body at time_limit let infinity_body=crate::physics::Body::new( relative_body.extrapolated_position(time_limit), @@ -727,10 +739,13 @@ impl MinkowskiMesh<'_>{ } }) } - pub fn predict_collision_face_out(&self,relative_body:&crate::physics::Body,time_limit:integer::Time,contact_face_id:MinkowskiFace)->Option<(MinkowskiEdge,integer::Time)>{ + pub fn predict_collision_face_out(&self,relative_body:&crate::physics::Body,time_limit:integer::Time,contact_face_id:MinkowskiFace)->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=time_limit; + 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(){ @@ -740,10 +755,10 @@ impl MinkowskiMesh<'_>{ 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 - for t in zeroes::zeroes2((n.dot(relative_body.position))*2-d,n.dot(relative_body.velocity)*2,n.dot(relative_body.acceleration)){ - let t=relative_body.time+integer::Time::from(t); - if relative_body.time::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::ONE).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; } @@ -751,15 +766,15 @@ impl MinkowskiMesh<'_>{ } best_edge.map(|e|(e.as_undirected(),best_time)) } - fn infinity_in(&self,infinity_body:crate::physics::Body)->Option<(MinkowskiFace,integer::Time)>{ + fn infinity_in(&self,infinity_body:crate::physics::Body)->Option<(MinkowskiFace,GigaTime)>{ let infinity_fev=self.infinity_fev(-infinity_body.velocity,infinity_body.position); - match crate::face_crawler::crawl_fev(infinity_fev,self,&infinity_body,integer::Time::MIN,infinity_body.time){ + match crate::face_crawler::crawl_fev(infinity_fev,self,&infinity_body,integer::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=crate::physics::Body::new(point,Planar64Vec3::Y,Planar64Vec3::ZERO,integer::Time::ZERO); + let infinity_body=crate::physics::Body::new(point,vec3::Y,vec3::ZERO,integer::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) @@ -770,7 +785,9 @@ impl MinkowskiMesh<'_>{ } } impl MeshQuery for MinkowskiMesh<'_>{ - fn face_nd(&self,face_id:MinkowskiFace)->(Planar64Vec3,Planar64){ + type Normal=Vector3>; + type Offset=Fixed<4,128>; + 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); @@ -784,7 +801,7 @@ impl MeshQuery for MinkowskiM 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),(e0d-e1d)*(parity as i64*2-1)) + ((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); @@ -833,17 +850,18 @@ impl MeshQuery for MinkowskiM 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=Planar64::ZERO; + 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 for MinkowskiM 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=Planar64::ZERO; + 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 for MinkowskiM //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 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)); + 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)); @@ -930,7 +949,7 @@ impl MeshQuery for MinkowskiM 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)); + 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)); @@ -945,7 +964,7 @@ impl MeshQuery for MinkowskiM } } -fn is_empty_volume(normals:Vec)->bool{ +fn is_empty_volume(normals:Vec>>)->bool{ let len=normals.len(); for i in 0..len-1{ for j in i+1..len{ @@ -953,9 +972,10 @@ fn is_empty_volume(normals:Vec)->bool{ let mut d_comp=None; for k in 0..len{ if k!=i&&k!=j{ - let d=n.dot(normals[k]); + let d=n.dot(normals[k]).is_negative(); if let Some(comp)=&d_comp{ - if *comp*d)->bool{ #[test] fn test_is_empty_volume(){ - assert!(!is_empty_volume([Planar64Vec3::X,Planar64Vec3::Y,Planar64Vec3::Z].to_vec())); - assert!(is_empty_volume([Planar64Vec3::X,Planar64Vec3::Y,Planar64Vec3::Z,Planar64Vec3::NEG_X].to_vec())); + 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] diff --git a/src/physics.rs b/src/physics.rs index 4a78d37..eab5889 100644 --- a/src/physics.rs +++ b/src/physics.rs @@ -11,7 +11,7 @@ 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,Time,Planar64,Planar64Vec3,Planar64Mat3,Angle32,Ratio64Vec2}; +use strafesnet_common::integer::{self,vec3,mat3,Time,Planar64,Planar64Vec3,Planar64Mat3,Angle32,Ratio64Vec2}; use gameplay::ModeState; //external influence @@ -22,8 +22,8 @@ use strafesnet_common::physics::Instruction as PhysicsInputInstruction; //when the physics asks itself what happens next, this is how it's represented #[derive(Debug)] enum PhysicsInternalInstruction{ - CollisionStart(Collision), - CollisionEnd(Collision), + CollisionStart(Collision,model_physics::GigaTime), + CollisionEnd(Collision,model_physics::GigaTime), StrafeTick, ReachWalkTargetVelocity, // Water, @@ -36,7 +36,7 @@ enum PhysicsInstruction{ Input(PhysicsInputInstruction), } -#[derive(Clone,Copy,Debug,Default,Hash)] +#[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 @@ -124,13 +124,13 @@ struct ContactMoveState{ } impl TransientAcceleration{ fn with_target_diff(target_diff:Planar64Vec3,accel:Planar64,time:Time)->Self{ - if target_diff==Planar64Vec3::ZERO{ + if target_diff==vec3::ZERO{ TransientAcceleration::Reached }else{ //normal friction acceleration is clippedAcceleration.dot(normal)*friction TransientAcceleration::Reachable{ - acceleration:target_diff.with_length(accel), - time:time+Time::from(target_diff.length()/accel) + acceleration:target_diff.with_length(accel).divide().fix_1(), + time:time+Time::from((target_diff.length()/accel).divide().fix_1()) } } } @@ -147,7 +147,7 @@ impl TransientAcceleration{ } fn acceleration(&self)->Planar64Vec3{ match self{ - TransientAcceleration::Reached=>Planar64Vec3::ZERO, + TransientAcceleration::Reached=>vec3::ZERO, &TransientAcceleration::Reachable{acceleration,time:_}=>acceleration, &TransientAcceleration::Unreachable{acceleration}=>acceleration, } @@ -158,7 +158,7 @@ impl ContactMoveState{ Self{ target:TransientAcceleration::ground(walk_settings,body,gravity,target_velocity), contact, - jump_direction:JumpDirection::Exactly(Planar64Vec3::Y), + jump_direction:JumpDirection::Exactly(vec3::Y), } } fn ladder(ladder_settings:&gameplay_style::LadderSettings,body:&Body,gravity:Planar64Vec3,target_velocity:Planar64Vec3,contact:ContactCollision)->Self{ @@ -296,7 +296,7 @@ impl PhysicsCamera{ 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); - Planar64Mat3::from_rotation_yx(ax,ay) + mat3::from_rotation_yx(ax,ay) } fn rotation(&self)->Planar64Mat3{ self.get_rotation(self.clamped_mouse_pos) @@ -306,7 +306,7 @@ impl PhysicsCamera{ } fn get_rotation_y(&self,mouse_pos_x:i32)->Planar64Mat3{ let ax=-self.sensitivity.x.mul_int(mouse_pos_x as i64); - Planar64Mat3::from_rotation_y(Angle32::wrap_from_i64(ax)) + mat3::from_rotation_y(Angle32::wrap_from_i64(ax)) } fn rotation_y(&self)->Planar64Mat3{ self.get_rotation_y(self.clamped_mouse_pos.x) @@ -410,10 +410,10 @@ impl HitboxMesh{ let transform=PhysicsMeshTransform::new(transform); let transformed_mesh=TransformedMesh::new(mesh.complete_mesh_view(),&transform); for vert in transformed_mesh.verts(){ - aabb.grow(vert); + aabb.grow(vert.fix_1()); } Self{ - halfsize:aabb.size()/2, + halfsize:aabb.size()>>1, mesh, transform, } @@ -438,7 +438,7 @@ impl StyleHelper for StyleModifiers{ fn get_control_dir(&self,controls:Controls)->Planar64Vec3{ //don't get fancy just do it - let mut control_dir:Planar64Vec3 = Planar64Vec3::ZERO; + 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){ @@ -463,19 +463,22 @@ impl StyleHelper for StyleModifiers{ } fn get_y_control_dir(&self,camera:&PhysicsCamera,controls:Controls)->Planar64Vec3{ - camera.rotation_y()*self.get_control_dir(controls) + (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) + (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(Planar64Mat3::from_diagonal(self.hitbox.halfsize),Planar64Vec3::ZERO); + let transform=integer::Planar64Affine3::new( + mat3::from_diagonal(self.hitbox.halfsize), + vec3::ZERO + ); HitboxMesh::new(mesh,transform) } } @@ -491,7 +494,7 @@ 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=Planar64Vec3::ZERO, + MoveState::Fly=>body.acceleration=vec3::ZERO, MoveState::Air=>{ //calculate base acceleration let a=touching.base_acceleration(models,style,camera,input_state); @@ -766,9 +769,9 @@ impl TouchingState{ //TODO: trey push solve for contact in &self.contacts{ let n=contact_normal(models,hitbox_mesh,contact); - let d=n.dot128(*velocity); - if d<0{ - *velocity-=n*Planar64::raw(((d<<32)/n.dot128(n)) as i64); + let d=n.dot(*velocity); + if d.is_negative(){ + *velocity-=(n*d/n.length_squared()).divide().fix_1(); } } } @@ -776,23 +779,24 @@ impl TouchingState{ //TODO: trey push solve for contact in &self.contacts{ let n=contact_normal(models,hitbox_mesh,contact); - let d=n.dot128(*acceleration); - if d<0{ - *acceleration-=n*Planar64::raw(((d<<32)/n.dot128(n)) as i64); + let d=n.dot(*acceleration); + if d.is_negative(){ + *acceleration-=(n*d/n.length_squared()).divide().fix_1(); } } } fn predict_collision_end(&self,collector:&mut instruction::InstructionCollector,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,body:&Body,time:Time){ - let relative_body=VirtualBody::relative(&Body::default(),body).body(time); + let relative_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, + time:relative_body.time+time.into(), instruction:PhysicsInternalInstruction::CollisionEnd( - Collision::Contact(*contact) + Collision::Contact(*contact), + time ), } })); @@ -803,9 +807,10 @@ impl TouchingState{ 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, + time:relative_body.time+time.into(), instruction:PhysicsInternalInstruction::CollisionEnd( - Collision::Intersect(*intersect) + Collision::Intersect(*intersect), + time ), } })); @@ -814,6 +819,7 @@ impl TouchingState{ } impl Body{ + 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, @@ -824,20 +830,64 @@ impl Body{ } pub fn extrapolated_position(&self,time:Time)->Planar64Vec3{ let dt=time-self.time; - self.position+self.velocity*dt+self.acceleration*(dt*dt/2) + 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 + 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) + .map(|elem|elem.divide().fix())+self.velocity + } + pub fn advance_time_ratio_dt(&mut self,dt: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==Planar64Vec3::ZERO{ - if self.acceleration==Planar64Vec3::ZERO{ + if self.velocity==vec3::ZERO{ + if self.acceleration==vec3::ZERO{ None }else{ Some(self.acceleration) @@ -851,22 +901,22 @@ impl Body{ aabb.grow(self.extrapolated_position(t1)); //v+a*t==0 //goober code - if self.acceleration.x()!=Planar64::ZERO{ - let t=Time::from(-self.velocity.x()/self.acceleration.x()); - if t0Self{ Self{ - body:Body::new(Planar64Vec3::int(0,50,0),Planar64Vec3::int(0,0,0),Planar64Vec3::int(0,-100,0),Time::ZERO), + 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(), @@ -1154,7 +1204,7 @@ impl PhysicsContext{ let mut aabb=aabb::Aabb::default(); let transformed_mesh=TransformedMesh::new(view,transform); for v in transformed_mesh.verts(){ - aabb.grow(v); + aabb.grow(v.fix_1()); } (ConvexMeshId{ model_id, @@ -1229,12 +1279,15 @@ impl PhysicsContext{ 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,time)|if time<=state.time{None}else{Some((face,time))}) - .map(|(face,time)| + .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) + Collision::new(convex_mesh_id,face), + dt ) } ) @@ -1247,7 +1300,8 @@ impl PhysicsContext{ 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()); - minkowski.face_nd(contact.face_id).0 + // TODO: normalize to i64::MAX>>1 + minkowski.face_nd(contact.face_id).0.fix_1() } fn recalculate_touching( @@ -1331,12 +1385,12 @@ fn set_velocity_cull(body:&mut Body,touching:&mut TouchingState,models:&PhysicsM let mut culled=false; touching.contacts.retain(|contact|{ let n=contact_normal(models,hitbox_mesh,contact); - let r=n.dot(v)<=Planar64::ZERO; - if !r{ + let r=n.dot(v).is_positive(); + if r{ culled=true; println!("set_velocity_cull contact={:?}",contact); } - r + !r }); set_velocity(body,touching,models,hitbox_mesh,v); culled @@ -1350,12 +1404,12 @@ fn set_acceleration_cull(body:&mut Body,touching:&mut TouchingState,models:&Phys let mut culled=false; touching.contacts.retain(|contact|{ let n=contact_normal(models,hitbox_mesh,contact); - let r=n.dot(a)<=Planar64::ZERO; - if !r{ + let r=n.dot(a).is_positive(); + if r{ culled=true; println!("set_acceleration_cull contact={:?}",contact); } - r + !r }); set_acceleration(body,touching,models,hitbox_mesh,a); culled @@ -1403,8 +1457,10 @@ fn teleport_to_spawn( input_state:&InputState, time:Time, )->Result<(),TeleportToSpawnError>{ + const EPSILON:Planar64=Planar64::raw((1<<32)/16); let transform=models.get_model_transform(stage.spawn()).ok_or(TeleportToSpawnError::NoModel)?; - let point=transform.vertex.transform_point3(Planar64Vec3::Y)+Planar64Vec3::Y*(style.hitbox.halfsize.y()+Planar64::ONE/16); + //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(()) } @@ -1525,7 +1581,7 @@ fn collision_start_contact( 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); + 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))=> @@ -1534,7 +1590,7 @@ fn collision_start_contact( //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,Planar64Vec3::ZERO);//model.velocity + 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); @@ -1543,7 +1599,7 @@ fn collision_start_contact( }, Some(gameplay_attributes::ContactingBehaviour::NoJump)=>todo!("nyi"), None=>if let Some(walk_settings)=&style.walk{ - if walk_settings.is_slope_walkable(contact_normal(models,hitbox_mesh,&contact),Planar64Vec3::Y){ + if walk_settings.is_slope_walkable(contact_normal(models,hitbox_mesh,&contact),vec3::Y){ //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); @@ -1671,17 +1727,20 @@ fn collision_end_intersect( } fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction){ state.time=ins.time; - let should_advance_body=match ins.instruction{ - PhysicsInternalInstruction::CollisionStart(_) - |PhysicsInternalInstruction::CollisionEnd(_) - |PhysicsInternalInstruction::StrafeTick - |PhysicsInternalInstruction::ReachWalkTargetVelocity=>true, + 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{ - state.body.advance_time(state.time); + 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)=>{ + PhysicsInternalInstruction::CollisionStart(collision,_)=>{ let mode=data.modes.get_mode(state.mode_state.get_mode_id()); match collision{ Collision::Contact(contact)=>collision_start_contact( @@ -1702,7 +1761,7 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim ), } }, - PhysicsInternalInstruction::CollisionEnd(collision)=>match collision{ + 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), @@ -1724,9 +1783,9 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim 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!=Planar64Vec3::ZERO{ + 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)){ + 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); @@ -1750,7 +1809,7 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim //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,Planar64Vec3::ZERO); + 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! @@ -1845,9 +1904,9 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI data.models.get_model_transform(mode.get_start().into()).map(|transform| transform.vertex.translation ) - ).unwrap_or(Planar64Vec3::ZERO); + ).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,Planar64Vec3::ZERO); + 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; } @@ -1898,7 +1957,7 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI _=>println!("{}|{:?}",ins.time,ins.instruction), } if ins.time){ - let h0=HitboxMesh::new(PhysicsMesh::unit_cube(),integer::Planar64Affine3::new(Planar64Mat3::from_diagonal(Planar64Vec3::int(5,1,5)/2),Planar64Vec3::ZERO)); + let h0=HitboxMesh::new(PhysicsMesh::unit_cube(),integer::Planar64Affine3::new(mat3::from_diagonal(int3(5,1,5)>>1),vec3::ZERO)); let h1=StyleModifiers::roblox_bhop().calculate_mesh(); let hitbox_mesh=h1.transformed_mesh(); let platform_mesh=h0.transformed_mesh(); let minkowski=model_physics::MinkowskiMesh::minkowski_sum(platform_mesh,hitbox_mesh); - let collision=minkowski.predict_collision_in(&relative_body,Time::MAX); - assert_eq!(collision.map(|tup|tup.1),expected_collision_time,"Incorrect time of collision"); + let collision=minkowski.predict_collision_in(&relative_body,Time::from_secs(10)); + assert_eq!(collision.map(|tup|relative_body.time+tup.1.into()),expected_collision_time,"Incorrect time of collision"); } fn test_collision_rotated(relative_body:Body,expected_collision_time:Option