Compare commits

..

25 Commits

Author SHA1 Message Date
41a02e7b3c notes 2025-11-27 15:07:24 -08:00
4637891a21 euclidean point 2025-11-27 15:06:18 -08:00
0ea353b27d common: fixed_wide: min max 2025-11-24 13:04:44 -08:00
99706079d9 common: fixed_wide: add mul_sign div_sign 2025-11-24 13:04:44 -08:00
730c5fb7dd common: integer: generic zero 2025-11-22 08:47:16 -08:00
d1b61bb997 push_solve: remove epsilon 2025-11-21 10:52:34 -08:00
0343ad19cf MeshQuery::hint_point returns any point inside the mesh 2025-11-20 10:59:08 -08:00
43210b1417 less access to TouchingState private fields 2025-11-19 13:39:07 -08:00
e9d28cf15f document jank 2025-11-19 13:15:31 -08:00
452bac4049 change collision_end_contact & collision_end_intersect fn signatures 2025-11-19 10:57:44 -08:00
48aad78f59 change contact_normal function signature to reduce copies 2025-11-19 10:20:33 -08:00
d45a42f5aa change ContactCollision struct layout
Match TouchingState contacts HashMap K,V layout to try to get lucky with compiler optimization.
2025-11-19 10:20:33 -08:00
c219fec3bc specialize touching member access 2025-11-19 10:08:40 -08:00
2a05d50abb check touching before testing collision 2025-11-19 10:08:40 -08:00
fbb047f8d4 combine call chain 2025-11-19 09:01:51 -08:00
c4d837a552 Fix infinite loop with intersects when allowing 0s collisions 2025-11-19 09:01:51 -08:00
a08bd44789 Generic ConvexMeshId 2025-11-19 09:01:51 -08:00
4ae5359046 rename not_spawn_at to is_not_spawn_at 2025-11-19 09:01:27 -08:00
15ecaf602a deep match 2025-11-18 12:29:46 -08:00
1e0511a7ba remove intermediate allocation 2025-11-18 12:23:05 -08:00
a9e4705d89 remove (some) fixed point implicit conversion
They may be convenient, but they cannot be done at compile-time.
TODO: remove more of them i.e. impl_multiplicative_operator
2025-11-18 11:53:52 -08:00
98069859b5 Gracefully handle 0 acceleration for walking targets 2025-11-18 19:47:04 +00:00
64d3996fa9 use From instead of Into 2025-11-18 11:46:32 -08:00
49c0c16e35 Use a From implementation instead of manual conversion
If the contacts and intersects map ever change in the future to not be 1:1 with gaps but instead something else, this guarantees that this implicit use of the relationship will flag at a compiler level
2025-11-18 19:25:44 +00:00
255bed4803 Ensure the PhysicsData's bvh respects the original model ordering
There's no importance in worrying about the core HashMap ordering since it's not used as an iterator except for outside of this very function for bvh purposes
2025-11-18 19:25:44 +00:00
14 changed files with 452 additions and 424 deletions

View File

@@ -33,7 +33,7 @@ impl<T:Copy> std::ops::Neg for &Body<T>{
impl<T> Body<T>
where Time<T>:Copy,
{
pub const ZERO:Self=Self::new(vec3::ZERO,vec3::ZERO,vec3::ZERO,Time::ZERO);
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<T>)->Self{
Self{
position,
@@ -107,8 +107,8 @@ impl<T> Body<T>
self.time+=dt.into();
}
pub fn infinity_dir(&self)->Option<Planar64Vec3>{
if self.velocity==vec3::ZERO{
if self.acceleration==vec3::ZERO{
if self.velocity==vec3::zero(){
if self.acceleration==vec3::zero(){
None
}else{
Some(self.acceleration)

View File

@@ -90,16 +90,8 @@ pub trait MeshQuery{
let &[v0,v1]=self.edge_verts(directed_edge_id.as_undirected()).as_ref();
(self.vert(v1)-self.vert(v0))*((directed_edge_id.parity() as i64)*2-1)
}
/// Returns an iterator over the vertices in the direction of the directed edges.
/// Intended to be used to find adjacent vertices:
/// `self.directed_verts(self.vert_edges(vert_id).as_ref())`
/// TODO: rewrite this function as `adjacent_vertices`
fn directed_verts(&self,edges:&[Self::Edge])->impl Iterator<Item=Self::Vert>{
edges.iter().map(|e|{
let edge_verts=self.edge_verts(e.as_undirected());
edge_verts.as_ref()[e.parity() as usize]
})
}
/// This must return a point inside the mesh.
fn hint_point(&self)->Planar64Vec3;
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)->impl AsRef<[Self::Edge]>;
@@ -457,13 +449,15 @@ impl MeshQuery for PhysicsMeshView<'_>{
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)
}
fn hint_point(&self)->Planar64Vec3{
// invariant: meshes always encompass the origin
vec3::zero()
}
//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
}
/// Directed edges going clockwise when looking in the direction of the face normal.
/// (Edit this documentation if this is wrong!)
fn face_edges(&self,face_id:SubmeshFaceId)->impl AsRef<[SubmeshDirectedEdgeId]>{
self.topology.face_topology[face_id.get() as usize].edges.as_slice()
}
@@ -544,6 +538,9 @@ impl MeshQuery for TransformedMesh<'_>{
// wrap for speed
self.transform.vertex.transform_point3(self.view.vert(vert_id)).wrap_1()
}
fn hint_point(&self)->Planar64Vec3{
self.transform.vertex.translation
}
#[inline]
fn face_edges(&self,face_id:SubmeshFaceId)->impl AsRef<[SubmeshDirectedEdgeId]>{
self.view.face_edges(face_id)
@@ -626,6 +623,17 @@ pub struct MinkowskiMesh<'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 EV{
Vert(MinkowskiVert),
Edge(MinkowskiEdge),
}
pub type GigaTime=Ratio<Fixed<4,128>,Fixed<4,128>>;
pub fn into_giga_time(time:Time,relative_to:Time)->GigaTime{
let r=(time-relative_to).to_ratio();
@@ -642,195 +650,122 @@ impl MinkowskiMesh<'_>{
fn farthest_vert(&self,dir:Planar64Vec3)->MinkowskiVert{
MinkowskiVert::VertVert(self.mesh0.farthest_vert(dir),self.mesh1.farthest_vert(-dir))
}
fn closest_fev_not_inside(&self,relative_position:Planar64Vec3)->Option<FEV<MinkowskiMesh<'_>>>{
// Make a fast guess as to what the closest point will be.
let MinkowskiVert::VertVert(mut v0,mut v1)=self.farthest_vert(relative_position);
// TODO: alternate naive vertex searches to improve the robustness
// in the "tall bipyramid" failure case
let mut m0v=self.mesh0.vert(v0);
let mut m1v=self.mesh1.vert(v1);
let mut best_distance_squared={
let diff=relative_position+m1v-m0v;
diff.dot(diff)
};
let mut v0e=self.mesh0.vert_edges(v0);
let mut v1e=self.mesh1.vert_edges(v1);
let mut v0e_ref=v0e.as_ref();
let mut v1e_ref=v1e.as_ref();
// repeatedly check adjacent vertex permutations to see if they are closer
loop{
let mut best_v0=None;
let mut best_v1=None;
// check vertices adjacent to v1 against v0
for m1_test_vert in self.mesh1.directed_verts(v1e_ref){
let m1v_test=self.mesh1.vert(m1_test_vert);
let diff=relative_position+m1v_test-m0v;
let d=diff.dot(diff);
if d<best_distance_squared{
best_distance_squared=d;
best_v0=None;
best_v1=Some(m1_test_vert);
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).as_ref(){
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.as_ref()[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;
}
}
// check vertices adjacent to v0 against v1
for m0_test_vert in self.mesh0.directed_verts(v0e_ref){
let m0v_test=self.mesh0.vert(m0_test_vert);
let diff=relative_position+m1v-m0v_test;
let d=diff.dot(diff);
if d<best_distance_squared{
best_distance_squared=d;
best_v0=Some(m0_test_vert);
best_v1=None;
}
}
// check permutations of adjacent vertices
for m0_test_vert in self.mesh0.directed_verts(v0e_ref){
let m0v_test=self.mesh0.vert(m0_test_vert);
for m1_test_vert in self.mesh1.directed_verts(v1e_ref){
let m1v_test=self.mesh1.vert(m1_test_vert);
let diff=relative_position+m1v_test-m0v_test;
let d=diff.dot(diff);
if d<best_distance_squared{
best_distance_squared=d;
best_v0=Some(m0_test_vert);
best_v1=Some(m1_test_vert);
}
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).as_ref(){
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);
//wrap for speed
(c.dot(c)/edge_nn).divide().wrap_2()
};
if distance_squared<=*best_distance_squared{
best_transition=EV::Edge(directed_edge_id.as_undirected());
*best_distance_squared=distance_squared;
}
}
}
// end condition
let v0_changed=match best_v0{
Some(new_v0)=>{
v0=new_v0;
m0v=self.mesh0.vert(v0);
v0e=self.mesh0.vert_edges(v0);
v0e_ref=v0e.as_ref();
true
},
None=>false,
};
let v1_changed=match best_v1{
Some(new_v1)=>{
v1=new_v1;
m1v=self.mesh1.vert(v1);
v1e=self.mesh1.vert_edges(v1);
v1e_ref=v1e.as_ref();
true
},
None=>false,
};
// if neither vertex changes, we found the closest two vertices.
if !(v0_changed&&v1_changed){
break;
}
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,
}
}
// now you have the two closest vertices
let mut best_fev=FEV::Vert(MinkowskiVert::VertVert(v0,v1));
// ==== FEV::Edge ====
// test VertEdges
for &e1 in v1e_ref{
let edge_verts=self.mesh1.edge_verts(e1.as_undirected());
let &[ev0_id,ev1_id]=edge_verts.as_ref();
let (ev0,ev1)=(self.mesh1.vert(ev0_id),self.mesh1.vert(ev1_id));
let edge_n=ev1-ev0;
// use relative coordinates to make including relative_position easier
let diff=relative_position+m0v-ev0;
let d=diff.dot(edge_n);
// is test point between edge vertices
let edge_nn=edge_n.dot(edge_n);
if d.is_positive()&&d<edge_nn{
let distance_squared={
let c=diff.cross(edge_n);
//wrap for speed
(c.dot(c)/edge_nn).divide().wrap_2()
}
/// 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::<MinkowskiMesh<'_>>{
//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).as_ref();
self.vert(v0)+self.vert(v1)
};
if distance_squared<best_distance_squared{
best_distance_squared=distance_squared;
best_fev=FEV::Edge(MinkowskiEdge::VertEdge(v0,e1.as_undirected()))
for (i,&face_id) in self.edge_faces(edge_id).as_ref().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)
},
}
// test EdgeVerts
for &e0 in v0e_ref{
let edge_verts=self.mesh0.edge_verts(e0.as_undirected());
let &[ev0_id,ev1_id]=edge_verts.as_ref();
let (ev0,ev1)=(self.mesh0.vert(ev0_id),self.mesh0.vert(ev1_id));
let edge_n=ev1-ev0;
// use relative coordinates to make including relative_position easier
let diff=m1v-relative_position-ev0;
let d=diff.dot(edge_n);
// is test point between edge vertices
let edge_nn=edge_n.dot(edge_n);
if d.is_positive()&&d<edge_nn{
let distance_squared={
let c=diff.cross(edge_n);
//wrap for speed
(c.dot(c)/edge_nn).divide().wrap_2()
};
if distance_squared<best_distance_squared{
best_distance_squared=distance_squared;
best_fev=FEV::Edge(MinkowskiEdge::EdgeVert(e0.as_undirected(),v1))
}
}
}
// ==== FEV::Face ====
// test VertFaces
'outer: for &f1 in self.mesh1.vert_faces(v1).as_ref(){
let (n,d)=self.mesh1.face_nd(f1);
// Test the face's voronoi column
for &e1 in self.mesh1.face_edges(f1).as_ref(){
let edge_n=self.mesh1.directed_edge_n(e1);
let boundary_n=n.cross(edge_n);
let &[ev0_id,ev1_id]=self.mesh1.edge_verts(e1.as_undirected()).as_ref();
let (ev0,ev1)=(self.mesh1.vert(ev0_id),self.mesh1.vert(ev1_id));
let diff=(relative_position+m0v)*2-(ev0+ev1);
if boundary_n.dot(diff).is_negative(){
// The test point is outside the face's voronoi column.
continue 'outer;
}
}
// Calculate distance
let d=n.dot(relative_position+m0v)-d;
// Wrap for speed
let distance_squared=(d*d).wrap_2();
if distance_squared<best_distance_squared{
best_distance_squared=distance_squared;
best_fev=FEV::Face(MinkowskiFace::VertFace(v0,f1));
}
}
// test FaceVerts
'outer: for &f0 in self.mesh0.vert_faces(v0).as_ref(){
let (n,d)=self.mesh0.face_nd(f0);
// Test the face's voronoi column
for &e0 in self.mesh0.face_edges(f0).as_ref(){
let edge_n=self.mesh0.directed_edge_n(e0);
let boundary_n=n.cross(edge_n);
let &[ev0_id,ev1_id]=self.mesh0.edge_verts(e0.as_undirected()).as_ref();
let (ev0,ev1)=(self.mesh0.vert(ev0_id),self.mesh0.vert(ev1_id));
let diff=(m1v-relative_position)*2-(ev0+ev1);
if boundary_n.dot(diff).is_negative(){
// The test point is outside the face's voronoi column.
continue 'outer;
}
}
// Calculate distance
let d=n.dot(relative_position+m0v)-d;
// Wrap for speed
let distance_squared=(d*d).wrap_2();
if distance_squared<best_distance_squared{
best_distance_squared=distance_squared;
best_fev=FEV::Face(MinkowskiFace::FaceVert(f0,v1));
}
}
// test EdgeEdges
Some(best_fev)
}
// TODO: fundamentally improve this algorithm.
// All it needs to do is find the closest point on the mesh
// and return the FEV which the point resides on.
//
// What it actually does is use the above functions to trace a ray in from infinity,
// crawling the closest point along the mesh surface until the ray reaches
// the starting point to discover the final FEV.
//
// The actual collision prediction probably does a single test
// and then immediately returns with 0 FEV transitions on average,
// because of the strict time_limit constraint.
//
// Most of the calculation time is just calculating the starting point
// for the "actual" crawling algorithm below (predict_collision_{in|out}).
fn closest_fev_not_inside(&self,mut infinity_body:Body,start_time:Bound<&Time>)->Option<FEV<MinkowskiMesh<'_>>>{
infinity_body.infinity_dir().and_then(|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
infinity_fev.crawl(self,&infinity_body,Bound::Unbounded,start_time).miss()
})
}
pub fn predict_collision_in(&self,relative_body:&Body,range:impl RangeBounds<Time>)->Option<(MinkowskiFace,GigaTime)>{
self.closest_fev_not_inside(relative_body.position).and_then(|fev|{
self.closest_fev_not_inside(*relative_body,range.start_bound()).and_then(|fev|{
//continue forwards along the body parabola
fev.crawl(self,relative_body,range.start_bound(),range.end_bound()).hit()
})
@@ -839,9 +774,10 @@ impl MinkowskiMesh<'_>{
let (lower_bound,upper_bound)=(range.start_bound(),range.end_bound());
// swap and negate bounds to do a time inversion
let (lower_bound,upper_bound)=(upper_bound.map(|&t|-t),lower_bound.map(|&t|-t));
self.closest_fev_not_inside(relative_body.position).and_then(|fev|{
let infinity_body=-relative_body;
self.closest_fev_not_inside(infinity_body,lower_bound.as_ref()).and_then(|fev|{
//continue backwards along the body parabola
fev.crawl(self,&-relative_body,lower_bound.as_ref(),upper_bound.as_ref()).hit()
fev.crawl(self,&infinity_body,lower_bound.as_ref(),upper_bound.as_ref()).hit()
//no need to test -time<time_limit because of the first step
.map(|(face,time)|(face,-time))
})
@@ -874,10 +810,20 @@ impl MinkowskiMesh<'_>{
}
best_edge
}
fn infinity_in(&self,infinity_body:Body)->Option<(MinkowskiFace,GigaTime)>{
let infinity_fev=self.infinity_fev(-infinity_body.velocity,infinity_body.position);
// Bound::Included means that the surface of the mesh is included in the mesh
infinity_fev.crawl(self,&infinity_body,Bound::Unbounded,Bound::Included(&infinity_body.time)).hit()
}
pub fn is_point_in_mesh(&self,point:Planar64Vec3)->bool{
// TODO
println!("Unimplemented is_point_in_mesh called! {point}");
false
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<'_>{
@@ -916,6 +862,10 @@ impl MeshQuery for MinkowskiMesh<'_>{
},
}
}
fn hint_point(&self)->Planar64Vec3{
self.mesh1.transform.vertex.translation-
self.mesh0.transform.vertex.translation
}
fn face_edges(&self,face_id:MinkowskiFace)->impl AsRef<[MinkowskiDirectedEdge]>{
match face_id{
MinkowskiFace::VertFace(v0,f1)=>{

View File

@@ -87,7 +87,7 @@ enum JumpDirection{
impl JumpDirection{
fn direction(&self,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,contact:&ContactCollision)->Planar64Vec3{
match self{
JumpDirection::FromContactNormal=>contact_normal(models,hitbox_mesh,contact),
JumpDirection::FromContactNormal=>contact_normal(models,hitbox_mesh,&contact.convex_mesh_id,contact.face_id),
&JumpDirection::Exactly(dir)=>dir,
}
}
@@ -100,7 +100,6 @@ enum TransientAcceleration{
time:Time,
},
//walk target will never be reached
#[expect(dead_code)]
Unreachable{
acceleration:Planar64Vec3,
}
@@ -114,8 +113,12 @@ struct ContactMoveState{
}
impl TransientAcceleration{
fn with_target_diff(target_diff:Planar64Vec3,accel:Planar64,time:Time)->Self{
if target_diff==vec3::ZERO{
if target_diff==vec3::zero(){
TransientAcceleration::Reached
}else if accel==Planar64::ZERO{
TransientAcceleration::Unreachable{
acceleration:vec3::zero()
}
}else{
//normal friction acceleration is clippedAcceleration.dot(normal)*friction
TransientAcceleration::Reachable{
@@ -137,7 +140,7 @@ impl TransientAcceleration{
}
fn acceleration(&self)->Planar64Vec3{
match self{
TransientAcceleration::Reached=>vec3::ZERO,
TransientAcceleration::Reached=>vec3::zero(),
&TransientAcceleration::Reachable{acceleration,time:_}=>acceleration,
&TransientAcceleration::Unreachable{acceleration}=>acceleration,
}
@@ -160,7 +163,7 @@ impl ContactMoveState{
}
}
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 normal=contact_normal(models,hitbox_mesh,&contact.convex_mesh_id,contact.face_id);
let gravity=touching.base_acceleration(models,style,camera,input_state);
let control_dir=style.get_y_control_dir(camera,input_state.controls);
let target_velocity=walk_settings.get_walk_target_velocity(control_dir,normal);
@@ -168,7 +171,7 @@ fn ground_things(walk_settings:&gameplay_style::WalkSettings,contact:&ContactCol
(gravity,target_velocity_clipped)
}
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 normal=contact_normal(models,hitbox_mesh,&contact.convex_mesh_id,contact.face_id);
let gravity=touching.base_acceleration(models,style,camera,input_state);
let control_dir=style.get_y_control_dir(camera,input_state.controls);
let target_velocity=ladder_settings.get_ladder_target_velocity(control_dir,normal);
@@ -193,7 +196,7 @@ impl PhysicsModels{
self.contact_attributes.clear();
self.intersect_attributes.clear();
}
fn mesh(&self,convex_mesh_id:ConvexMeshId)->TransformedMesh<'_>{
fn mesh(&self,convex_mesh_id:ConvexMeshId<PhysicsModelId>)->TransformedMesh<'_>{
let (mesh_id,transform)=match convex_mesh_id.model_id{
PhysicsModelId::Contact(model_id)=>{
let model=&self.contact_models[&model_id];
@@ -210,25 +213,25 @@ impl PhysicsModels{
)
}
//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];
fn contact_mesh(&self,convex_mesh_id:&ConvexMeshId<ContactModelId>)->TransformedMesh<'_>{
let model=&self.contact_models[&convex_mesh_id.model_id];
TransformedMesh::new(
self.meshes[&model.mesh_id].submesh_view(contact.submesh_id),
self.meshes[&model.mesh_id].submesh_view(convex_mesh_id.submesh_id),
&model.transform
)
}
fn intersect_mesh(&self,intersect:&IntersectCollision)->TransformedMesh<'_>{
let model=&self.intersect_models[&intersect.model_id];
fn intersect_mesh(&self,convex_mesh_id:&ConvexMeshId<IntersectModelId>)->TransformedMesh<'_>{
let model=&self.intersect_models[&convex_mesh_id.model_id];
TransformedMesh::new(
self.meshes[&model.mesh_id].submesh_view(intersect.submesh_id),
self.meshes[&model.mesh_id].submesh_view(convex_mesh_id.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())){
match self.contact_models.get(&model_id.into()){
Some(model)=>Some(&model.transform),
None=>self.intersect_models.get(&IntersectModelId::new(model_id.get()))
None=>self.intersect_models.get(&model_id.into())
.map(|model|&model.transform),
}
}
@@ -440,7 +443,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=vec3::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){
@@ -479,7 +482,7 @@ impl StyleHelper for StyleModifiers{
};
let transform=integer::Planar64Affine3::new(
mat3::from_diagonal(self.hitbox.halfsize),
vec3::ZERO
vec3::zero()
);
HitboxMesh::new(mesh,transform)
}
@@ -497,7 +500,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=vec3::ZERO,
MoveState::Fly=>body.acceleration=vec3::zero(),
MoveState::Air=>{
//calculate base acceleration
let a=touching.base_acceleration(models,style,camera,input_state);
@@ -611,7 +614,7 @@ impl MoveState{
// TODO: unduplicate this code
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){
Some(walk_state)=>if !touching.contains_contact(&walk_state.contact.convex_mesh_id){
self.set_move_state(MoveState::Air,body,touching,models,hitbox_mesh,style,camera,input_state);
}else{
// stopped touching something else while walking
@@ -642,9 +645,9 @@ impl TryFrom<&gameplay_attributes::CollisionAttributes> for PhysicsCollisionAttr
}
#[derive(Clone,Copy,Hash,id::Id,Eq,PartialEq)]
struct ContactAttributesId(u32);
impl Into<CollisionAttributesId> for ContactAttributesId{
fn into(self)->CollisionAttributesId{
CollisionAttributesId::new(self.0)
impl From<ContactAttributesId> for CollisionAttributesId{
fn from(value:ContactAttributesId)->CollisionAttributesId{
CollisionAttributesId::new(value.0)
}
}
impl From<CollisionAttributesId> for ContactAttributesId{
@@ -654,9 +657,9 @@ impl From<CollisionAttributesId> for ContactAttributesId{
}
#[derive(Clone,Copy,Hash,id::Id,Eq,PartialEq)]
struct IntersectAttributesId(u32);
impl Into<CollisionAttributesId> for IntersectAttributesId{
fn into(self)->CollisionAttributesId{
CollisionAttributesId::new(self.0)
impl From<IntersectAttributesId> for CollisionAttributesId{
fn from(value:IntersectAttributesId)->CollisionAttributesId{
CollisionAttributesId::new(value.0)
}
}
impl From<CollisionAttributesId> for IntersectAttributesId{
@@ -666,16 +669,26 @@ impl From<CollisionAttributesId> for IntersectAttributesId{
}
#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)]
struct ContactModelId(u32);
impl Into<ModelId> for ContactModelId{
fn into(self)->ModelId{
ModelId::new(self.get())
impl From<ContactModelId> for ModelId{
fn from(value:ContactModelId)->ModelId{
ModelId::new(value.get())
}
}
impl From<ModelId> for ContactModelId{
fn from(other: ModelId)->Self{
Self::new(other.get())
}
}
#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)]
struct IntersectModelId(u32);
impl Into<ModelId> for IntersectModelId{
fn into(self)->ModelId{
ModelId::new(self.get())
impl From<IntersectModelId> for ModelId{
fn from(value:IntersectModelId)->ModelId{
ModelId::new(value.get())
}
}
impl From<ModelId> for IntersectModelId{
fn from(other: ModelId)->Self{
Self::new(other.get())
}
}
#[derive(Debug,Clone,Copy,Hash,Eq,PartialEq)]
@@ -683,9 +696,9 @@ enum PhysicsModelId{
Contact(ContactModelId),
Intersect(IntersectModelId),
}
impl Into<ModelId> for PhysicsModelId{
fn into(self)->ModelId{
ModelId::new(match self{
impl From<PhysicsModelId> for ModelId{
fn from(value:PhysicsModelId)->ModelId{
ModelId::new(match value{
PhysicsModelId::Contact(model_id)=>model_id.get(),
PhysicsModelId::Intersect(model_id)=>model_id.get(),
})
@@ -693,10 +706,18 @@ impl Into<ModelId> for PhysicsModelId{
}
//unique physics meshes indexed by this
#[derive(Debug,Clone,Copy,Eq,Hash,PartialEq)]
struct ConvexMeshId{
model_id:PhysicsModelId,
struct ConvexMeshId<Id>{
model_id:Id,
submesh_id:PhysicsSubmeshId,
}
impl<Id> ConvexMeshId<Id>{
fn map<NewId>(self,model_id:NewId)->ConvexMeshId<NewId>{
ConvexMeshId{
model_id,
submesh_id:self.submesh_id,
}
}
}
struct ContactModel{
mesh_id:PhysicsMeshId,
attr_id:ContactAttributesId,
@@ -710,14 +731,12 @@ struct IntersectModel{
#[derive(Debug,Clone,Copy,Eq,Hash,PartialEq)]
pub struct ContactCollision{
convex_mesh_id:ConvexMeshId<ContactModelId>,
face_id:model_physics::MinkowskiFace,
model_id:ContactModelId,
submesh_id:PhysicsSubmeshId,
}
#[derive(Debug,Clone,Copy,Eq,Hash,PartialEq)]
pub struct IntersectCollision{
model_id:IntersectModelId,
submesh_id:PhysicsSubmeshId,
convex_mesh_id:ConvexMeshId<IntersectModelId>,
}
#[derive(Debug,Clone,Eq,Hash,PartialEq)]
pub enum Collision{
@@ -725,33 +744,47 @@ pub enum Collision{
Intersect(IntersectCollision),
}
impl Collision{
const fn new(convex_mesh_id:ConvexMeshId,face_id:model_physics::MinkowskiFace)->Self{
fn new(convex_mesh_id:ConvexMeshId<PhysicsModelId>,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}),
PhysicsModelId::Contact(model_id)=>Collision::Contact(ContactCollision{convex_mesh_id:convex_mesh_id.map(model_id),face_id}),
PhysicsModelId::Intersect(model_id)=>Collision::Intersect(IntersectCollision{convex_mesh_id:convex_mesh_id.map(model_id)}),
}
}
}
#[derive(Clone,Debug,Default)]
struct TouchingState{
contacts:HashSet<ContactCollision>,
intersects:HashSet<IntersectCollision>,
// This is kind of jank, it's a ContactCollision
// but split over the Key and Value of the HashMap.
contacts:HashMap<ConvexMeshId<ContactModelId>,model_physics::MinkowskiFace>,
intersects:HashSet<ConvexMeshId<IntersectModelId>>,
}
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 insert_contact(&mut self,contact:ContactCollision)->Option<model_physics::MinkowskiFace>{
self.contacts.insert(contact.convex_mesh_id,contact.face_id)
}
fn remove(&mut self,collision:&Collision)->bool{
match collision{
Collision::Contact(collision)=>self.contacts.remove(collision),
Collision::Intersect(collision)=>self.intersects.remove(collision),
fn insert_intersect(&mut self,intersect:IntersectCollision)->bool{
self.intersects.insert(intersect.convex_mesh_id)
}
fn remove_contact(&mut self,convex_mesh_id:&ConvexMeshId<ContactModelId>)->Option<model_physics::MinkowskiFace>{
self.contacts.remove(convex_mesh_id)
}
fn remove_intersect(&mut self,convex_mesh_id:&ConvexMeshId<IntersectModelId>)->bool{
self.intersects.remove(convex_mesh_id)
}
fn contains_contact(&self,convex_mesh_id:&ConvexMeshId<ContactModelId>)->bool{
self.contacts.contains_key(convex_mesh_id)
}
fn contains_intersect(&self,convex_mesh_id:&ConvexMeshId<IntersectModelId>)->bool{
self.intersects.contains(convex_mesh_id)
}
fn contains(&self,convex_mesh_id:&ConvexMeshId<PhysicsModelId>)->bool{
match convex_mesh_id.model_id{
PhysicsModelId::Contact(contact_model_id)=>self.contains_contact(&convex_mesh_id.map(contact_model_id)),
PhysicsModelId::Intersect(intersect_model_id)=>self.contains_intersect(&convex_mesh_id.map(intersect_model_id)),
}
}
fn base_acceleration(&self,models:&PhysicsModels,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState)->Planar64Vec3{
@@ -760,7 +793,7 @@ impl TouchingState{
a+=rocket_settings.acceleration(style.get_propulsion_control_dir(camera,input_state.controls));
}
//add accelerators
for contact in &self.contacts{
for contact in self.contacts.keys(){
if let Some(accelerator)=&models.contact_attr(contact.model_id).general.accelerator{
a+=accelerator.acceleration;
}
@@ -774,10 +807,10 @@ impl TouchingState{
a
}
fn constrain_velocity(&self,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,velocity:Planar64Vec3)->Planar64Vec3{
let contacts:Vec<_>=self.contacts.iter().map(|contact|{
let n=contact_normal(models,hitbox_mesh,contact);
let contacts:Vec<_>=self.contacts.iter().map(|(convex_mesh_id,face_id)|{
let n=contact_normal(models,hitbox_mesh,convex_mesh_id,*face_id);
crate::push_solve::Contact{
position:vec3::ZERO,
position:vec3::zero(),
velocity:n,
normal:n,
}
@@ -785,10 +818,10 @@ impl TouchingState{
crate::push_solve::push_solve(&contacts,velocity)
}
fn constrain_acceleration(&self,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,acceleration:Planar64Vec3)->Planar64Vec3{
let contacts:Vec<_>=self.contacts.iter().map(|contact|{
let n=contact_normal(models,hitbox_mesh,contact);
let contacts:Vec<_>=self.contacts.iter().map(|(convex_mesh_id,face_id)|{
let n=contact_normal(models,hitbox_mesh,convex_mesh_id,*face_id);
crate::push_solve::Contact{
position:vec3::ZERO,
position:vec3::zero(),
velocity:n,
normal:n,
}
@@ -798,29 +831,29 @@ impl TouchingState{
fn predict_collision_end(&self,collector:&mut instruction::InstructionCollector<InternalInstruction,Time>,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,body:&Body,start_time:Time){
// let relative_body=body.relative_to(&Body::ZERO);
let relative_body=body;
for contact in &self.contacts{
for (convex_mesh_id,face_id) in &self.contacts{
//detect face slide off
let model_mesh=models.contact_mesh(contact);
let model_mesh=models.contact_mesh(convex_mesh_id);
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh());
collector.collect(minkowski.predict_collision_face_out(&relative_body,start_time..collector.time(),contact.face_id).map(|(_face,time)|{
collector.collect(minkowski.predict_collision_face_out(&relative_body,start_time..collector.time(),*face_id).map(|(_face,time)|{
TimedInstruction{
time:relative_body.time+time.into(),
instruction:InternalInstruction::CollisionEnd(
Collision::Contact(*contact),
Collision::Contact(ContactCollision{face_id:*face_id,convex_mesh_id:*convex_mesh_id}),
time
),
}
}));
}
for intersect in &self.intersects{
for convex_mesh_id in &self.intersects{
//detect model collision in reverse
let model_mesh=models.intersect_mesh(intersect);
let model_mesh=models.intersect_mesh(convex_mesh_id);
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh());
collector.collect(minkowski.predict_collision_out(&relative_body,start_time..collector.time()).map(|(_face,time)|{
TimedInstruction{
time:relative_body.time+time.into(),
instruction:InternalInstruction::CollisionEnd(
Collision::Intersect(*intersect),
Collision::Intersect(IntersectCollision{convex_mesh_id:*convex_mesh_id}),
time
),
}
@@ -947,7 +980,7 @@ impl PhysicsState{
// shared geometry for simulations
pub struct PhysicsData{
//permanent map data
bvh:bvh::BvhNode<ConvexMeshId>,
bvh:bvh::BvhNode<ConvexMeshId<PhysicsModelId>>,
//transient map/environment data (open world loads/unloads parts of this data)
models:PhysicsModels,
//semi-transient data
@@ -1033,14 +1066,16 @@ impl PhysicsData{
let transform=PhysicsMeshTransform::new(model.transform);
match attr_id{
PhysicsAttributesId::Contact(attr_id)=>{
contact_models.insert(ContactModelId::new(model_id as u32),ContactModel{
let contact_model_id=ContactModelId::new(model_id as u32);
contact_models.insert(contact_model_id,ContactModel{
mesh_id,
attr_id,
transform,
});
},
PhysicsAttributesId::Intersect(attr_id)=>{
intersect_models.insert(IntersectModelId::new(model_id as u32),IntersectModel{
let intersect_model_id=IntersectModelId::new(model_id as u32);
intersect_models.insert(intersect_model_id,IntersectModel{
mesh_id,
attr_id,
transform,
@@ -1054,12 +1089,23 @@ impl PhysicsData{
(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)
).chain(intersect_models.iter().map(|(&model_id,model)|
(PhysicsModelId::Intersect(model_id),&model.mesh_id,&model.transform)
))
// use the map models iteration order to ensure that the
// order that the models are passed into bvh::generate_bvh is consistent
map.models.iter().enumerate().filter_map(|(model_id,model)|{
match map.attributes.get(model.attributes.get() as usize){
None|Some(gameplay_attributes::CollisionAttributes::Decoration)=>None,
Some(gameplay_attributes::CollisionAttributes::Contact(_))=>{
let model_id=ContactModelId::new(model_id as u32);
let model=contact_models.get(&model_id)?;
Some((PhysicsModelId::Contact(model_id),&model.mesh_id,&model.transform))
},
Some(gameplay_attributes::CollisionAttributes::Intersect(_))=>{
let model_id=IntersectModelId::new(model_id as u32);
let model=intersect_models.get(&model_id)?;
Some((PhysicsModelId::Intersect(model_id),&model.mesh_id,&model.transform))
},
}
})
.flat_map(|(model_id,mesh_id,transform)|{
meshes[mesh_id].submesh_views()
.enumerate().map(move|(submesh_id,view)|{
@@ -1161,21 +1207,19 @@ impl<'a> PhysicsContext<'a>{
//relative to moving platforms
//let relative_body=state.body.relative_to(&Body::ZERO);
let relative_body=&state.body;
data.bvh.sample_aabb(&aabb,&mut |&convex_mesh_id|{
data.bvh.sample_aabb(&aabb,&mut |convex_mesh_id|{
if state.touching.contains(convex_mesh_id){
return;
}
//no checks are needed because of the time limits.
let model_mesh=data.models.mesh(convex_mesh_id);
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,state.time..collector.time())
//temp (?) code to avoid collision loops
.and_then(|(face,dt)|{
// this must be rounded to avoid the infinite loop when hitting the start zone
let time=relative_body.time+dt.into();
(state.time<time).then_some((time,face,dt))
}).map(|(time,face,dt)|
.map(|(face,dt)|
TimedInstruction{
time,
time:relative_body.time+dt.into(),
instruction:InternalInstruction::CollisionStart(
Collision::new(convex_mesh_id,face),
Collision::new(*convex_mesh_id,face),
dt
)
}
@@ -1186,12 +1230,17 @@ impl<'a> PhysicsContext<'a>{
}
fn contact_normal(models:&PhysicsModels,hitbox_mesh:&HitboxMesh,contact:&ContactCollision)->Planar64Vec3{
let model_mesh=models.contact_mesh(contact);
fn contact_normal(
models:&PhysicsModels,
hitbox_mesh:&HitboxMesh,
convex_mesh_id:&ConvexMeshId<ContactModelId>,
face_id:model_physics::MinkowskiFace,
)->Planar64Vec3{
let model_mesh=models.contact_mesh(convex_mesh_id);
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh());
// TODO: normalize to i64::MAX>>1
// wrap for speed
minkowski.face_nd(contact.face_id).0.wrap_1()
minkowski.face_nd(face_id).0.wrap_1()
}
fn recalculate_touching(
@@ -1203,7 +1252,7 @@ fn recalculate_touching(
mode:Option<&gameplay_modes::Mode>,
models:&PhysicsModels,
hitbox_mesh:&HitboxMesh,
bvh:&bvh::BvhNode<ConvexMeshId>,
bvh:&bvh::BvhNode<ConvexMeshId<PhysicsModelId>>,
style:&StyleModifiers,
camera:&PhysicsCamera,
input_state:&InputState,
@@ -1212,11 +1261,11 @@ fn recalculate_touching(
//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((&convex_mesh_id,_face_id))=touching.contacts.iter().next(){
collision_end_contact(move_state,body,touching,models,hitbox_mesh,style,camera,input_state,models.contact_attr(convex_mesh_id.model_id),&convex_mesh_id)
}
while let Some(&intersect)=touching.intersects.iter().next(){
collision_end_intersect(move_state,body,touching,models,hitbox_mesh,style,camera,input_state,mode,run,models.intersect_attr(intersect.model_id),intersect,time);
while let Some(&convex_mesh_id)=touching.intersects.iter().next(){
collision_end_intersect(move_state,body,touching,models,hitbox_mesh,style,camera,input_state,mode,run,models.intersect_attr(convex_mesh_id.model_id),&convex_mesh_id,time);
}
//find all models in the teleport region
let mut aabb=aabb::Aabb::default();
@@ -1238,8 +1287,7 @@ fn recalculate_touching(
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,
convex_mesh_id:convex_mesh_id.map(model_id),
},
time,
),
@@ -1257,7 +1305,7 @@ fn set_position(
mode:Option<&gameplay_modes::Mode>,
models:&PhysicsModels,
hitbox_mesh:&HitboxMesh,
bvh:&bvh::BvhNode<ConvexMeshId>,
bvh:&bvh::BvhNode<ConvexMeshId<PhysicsModelId>>,
style:&StyleModifiers,
camera:&PhysicsCamera,
input_state:&InputState,
@@ -1273,8 +1321,8 @@ fn set_position(
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);
touching.contacts.retain(|convex_mesh_id,face_id|{
let n=contact_normal(models,hitbox_mesh,convex_mesh_id,*face_id);
let r=n.dot(v).is_positive();
if r{
culled=true;
@@ -1291,8 +1339,8 @@ fn set_velocity(body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hit
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);
touching.contacts.retain(|convex_mesh_id,face_id|{
let n=contact_normal(models,hitbox_mesh,convex_mesh_id,*face_id);
let r=n.dot(a).is_positive();
if r{
culled=true;
@@ -1316,7 +1364,7 @@ fn teleport(
mode:Option<&gameplay_modes::Mode>,
models:&PhysicsModels,
hitbox_mesh:&HitboxMesh,
bvh:&bvh::BvhNode<ConvexMeshId>,
bvh:&bvh::BvhNode<ConvexMeshId<PhysicsModelId>>,
style:&StyleModifiers,
camera:&PhysicsCamera,
input_state:&InputState,
@@ -1338,7 +1386,7 @@ fn teleport_to_spawn(
mode:&gameplay_modes::Mode,
models:&PhysicsModels,
hitbox_mesh:&HitboxMesh,
bvh:&bvh::BvhNode<ConvexMeshId>,
bvh:&bvh::BvhNode<ConvexMeshId<PhysicsModelId>>,
style:&StyleModifiers,
camera:&PhysicsCamera,
input_state:&InputState,
@@ -1427,7 +1475,7 @@ fn run_teleport_behaviour(
mode_state:&mut ModeState,
models:&PhysicsModels,
hitbox_mesh:&HitboxMesh,
bvh:&bvh::BvhNode<ConvexMeshId>,
bvh:&bvh::BvhNode<ConvexMeshId<PhysicsModelId>>,
style:&StyleModifiers,
camera:&PhysicsCamera,
input_state:&InputState,
@@ -1474,7 +1522,7 @@ fn run_teleport_behaviour(
}
}
fn not_spawn_at(
fn is_not_spawn_at(
mode:Option<&gameplay_modes::Mode>,
model_id:ModelId,
)->bool{
@@ -1495,7 +1543,7 @@ fn collision_start_contact(
mode:Option<&gameplay_modes::Mode>,
models:&PhysicsModels,
hitbox_mesh:&HitboxMesh,
bvh:&bvh::BvhNode<ConvexMeshId>,
bvh:&bvh::BvhNode<ConvexMeshId<PhysicsModelId>>,
style:&StyleModifiers,
camera:&PhysicsCamera,
input_state:&InputState,
@@ -1505,12 +1553,12 @@ fn collision_start_contact(
){
let incident_velocity=body.velocity;
//add to touching
touching.insert(Collision::Contact(contact));
touching.insert_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);
let model_id=contact.convex_mesh_id.model_id.into();
let mut allow_run_teleport_behaviour=is_not_spawn_at(mode,model_id);
match &attr.contacting.contact_behaviour{
Some(gameplay_attributes::ContactingBehaviour::Surf)=>(),
Some(gameplay_attributes::ContactingBehaviour::Cling)=>println!("Unimplemented!"),
@@ -1524,7 +1572,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,vec3::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);
@@ -1533,7 +1581,7 @@ fn collision_start_contact(
},
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){
if walk_settings.is_slope_walkable(contact_normal(models,hitbox_mesh,&contact.convex_mesh_id,contact.face_id),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);
@@ -1598,7 +1646,7 @@ fn collision_start_intersect(
run:&mut run::Run,
models:&PhysicsModels,
hitbox_mesh:&HitboxMesh,
bvh:&bvh::BvhNode<ConvexMeshId>,
bvh:&bvh::BvhNode<ConvexMeshId<PhysicsModelId>>,
style:&StyleModifiers,
camera:&PhysicsCamera,
input_state:&InputState,
@@ -1607,13 +1655,13 @@ fn collision_start_intersect(
time:Time,
){
//I think that setting the velocity to 0 was preventing surface contacts from entering an infinite loop
touching.insert(Collision::Intersect(intersect));
touching.insert_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());
let zone=mode.get_zone(intersect.convex_mesh_id.model_id.into());
match zone{
Some(gameplay_modes::Zone::Start)=>{
println!("@@@@ Starting new run!");
@@ -1630,7 +1678,7 @@ fn collision_start_intersect(
}
}
move_state.apply_enum_and_body(body,touching,models,hitbox_mesh,style,camera,input_state);
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);
run_teleport_behaviour(intersect.convex_mesh_id.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(
@@ -1643,15 +1691,17 @@ fn collision_end_contact(
camera:&PhysicsCamera,
input_state:&InputState,
_attr:&gameplay_attributes::ContactAttributes,
contact:ContactCollision,
convex_mesh_id:&ConvexMeshId<ContactModelId>,
){
touching.remove(&Collision::Contact(contact));//remove contact before calling contact_constrain_acceleration
touching.remove_contact(convex_mesh_id);//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{
// This does not check the face! Is that a bad thing? It should be
// impossible to stop touching a different face than you started touching...
Some(walk_state)=>if &walk_state.contact.convex_mesh_id==convex_mesh_id{
move_state.set_move_state(MoveState::Air,body,touching,models,hitbox_mesh,style,camera,input_state);
}else{
// stopped touching something else while walking
@@ -1673,13 +1723,13 @@ fn collision_end_intersect(
mode:Option<&gameplay_modes::Mode>,
run:&mut run::Run,
_attr:&gameplay_attributes::IntersectAttributes,
intersect:IntersectCollision,
convex_mesh_id:&ConvexMeshId<IntersectModelId>,
time:Time,
){
touching.remove(&Collision::Intersect(intersect));
touching.remove_intersect(convex_mesh_id);
move_state.apply_enum_and_body(body,touching,models,hitbox_mesh,style,camera,input_state);
if let Some(mode)=mode{
let zone=mode.get_zone(intersect.model_id.into());
let zone=mode.get_zone(convex_mesh_id.model_id.into());
match zone{
Some(gameplay_modes::Zone::Start)=>{
match run.start(time){
@@ -1713,7 +1763,7 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim
&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),
data.models.contact_attr(contact.convex_mesh_id.model_id),
contact,
state.time,
),
@@ -1721,7 +1771,7 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim
&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),
data.models.intersect_attr(intersect.convex_mesh_id.model_id),
intersect,
state.time,
),
@@ -1730,15 +1780,15 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim
InternalInstruction::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
data.models.contact_attr(contact.convex_mesh_id.model_id),
&contact.convex_mesh_id
),
Collision::Intersect(intersect)=>collision_end_intersect(
&mut state.move_state,&mut state.body,&mut state.touching,&data.models,&data.hitbox_mesh,&state.style,&state.camera,&state.input_state,
data.modes.get_mode(state.mode_state.get_mode_id()),
&mut state.run,
data.models.intersect_attr(intersect.model_id),
intersect,
data.models.intersect_attr(intersect.convex_mesh_id.model_id),
&intersect.convex_mesh_id,
state.time
),
},
@@ -1749,7 +1799,7 @@ 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!=vec3::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).divide().wrap_1()){
//this is wrong but will work ig
@@ -1772,7 +1822,7 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim
//which means that gravity can be fully cancelled
//ignore moving platforms for now
let target=core::mem::replace(&mut walk_state.target,TransientAcceleration::Reached);
set_acceleration(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,vec3::ZERO);
set_acceleration(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,vec3::zero());
// check what the target was to see if it was invalid
match target{
//you are not supposed to reach a walk target which is already reached!
@@ -1836,7 +1886,7 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
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 booster_option=data.models.contact_attr(walk_state.contact.convex_mesh_id.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);
}
@@ -1854,7 +1904,7 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
},
Instruction::Mode(ModeInstruction::Restart(mode_id))=>{
//teleport to mode start zone
let mut spawn_point=vec3::ZERO;
let mut spawn_point=vec3::zero();
let mode=data.modes.get_mode(mode_id);
if let Some(mode)=mode{
// set style
@@ -1867,7 +1917,7 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
}
}
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);
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;
}
@@ -1917,7 +1967,7 @@ mod test{
use strafesnet_common::integer::{vec3::{self,int as int3},mat3};
use super::*;
fn test_collision_axis_aligned(relative_body:Body,expected_collision_time:Option<Time>){
let h0=HitboxMesh::new(PhysicsMesh::unit_cube(),integer::Planar64Affine3::new(mat3::from_diagonal(int3(5,1,5)>>1),vec3::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();
@@ -1933,7 +1983,7 @@ mod test{
int3(0,1,0)>>1,
int3(-1,0,5)>>1,
]),
vec3::ZERO
vec3::zero()
),
);
let h1=StyleModifiers::roblox_bhop().calculate_mesh();
@@ -1952,7 +2002,7 @@ mod test{
test_collision(Body::new(
int3(0,5,0),
int3(0,-1,0),
vec3::ZERO,
vec3::zero(),
Time::ZERO
),Some(Time::from_secs(2)));
}
@@ -1961,7 +2011,7 @@ mod test{
test_collision(Body::new(
int3(0,5,0),
int3(0,-1,0)+(vec3::X>>32),
vec3::ZERO,
vec3::zero(),
Time::ZERO
),Some(Time::from_secs(2)));
}
@@ -1970,7 +2020,7 @@ mod test{
test_collision(Body::new(
int3(3,5,0),
int3(0,-1,0),
vec3::ZERO,
vec3::zero(),
Time::ZERO
),Some(Time::from_secs(2)));
}
@@ -1979,7 +2029,7 @@ mod test{
test_collision(Body::new(
int3(0,5,3),
int3(0,-1,0),
vec3::ZERO,
vec3::zero(),
Time::ZERO
),Some(Time::from_secs(2)));
}
@@ -1988,7 +2038,7 @@ mod test{
test_collision(Body::new(
int3(-3,5,0),
int3(0,-1,0),
vec3::ZERO,
vec3::zero(),
Time::ZERO
),Some(Time::from_secs(2)));
}
@@ -1997,7 +2047,7 @@ mod test{
test_collision(Body::new(
int3(0,5,-3),
int3(0,-1,0),
vec3::ZERO,
vec3::zero(),
Time::ZERO
),Some(Time::from_secs(2)));
}
@@ -2114,7 +2164,7 @@ mod test{
test_collision(Body::new(
int3(0,5,0),
int3(1,-64,2)>>6,// /64
vec3::ZERO,
vec3::zero(),
Time::ZERO
),Some(Time::from_secs(2)));
}
@@ -2130,7 +2180,7 @@ mod test{
#[test]
fn already_inside_hit_nothing(){
test_collision(Body::new(
vec3::ZERO,
vec3::zero(),
int3(1,0,0),
int3(0,1,0),
Time::ZERO
@@ -2142,7 +2192,7 @@ mod test{
test_collision_axis_aligned(Body::new(
(int3(0,10,-7)>>1)+vec3::raw_xyz(0,0,1),
int3(0,-1,0),
vec3::ZERO,
vec3::zero(),
Time::ZERO
),Some(Time::from_secs(2)))
}
@@ -2151,7 +2201,7 @@ mod test{
test_collision_axis_aligned(Body::new(
(int3(7,10,0)>>1)+vec3::raw_xyz(-1,0,0),
int3(0,-1,0),
vec3::ZERO,
vec3::zero(),
Time::ZERO
),Some(Time::from_secs(2)))
}
@@ -2160,7 +2210,7 @@ mod test{
test_collision_axis_aligned(Body::new(
(int3(0,10,7)>>1)+vec3::raw_xyz(0,0,-1),
int3(0,-1,0),
vec3::ZERO,
vec3::zero(),
Time::ZERO
),Some(Time::from_secs(2)))
}
@@ -2169,7 +2219,7 @@ mod test{
test_collision_axis_aligned(Body::new(
(int3(-7,10,0)>>1)+vec3::raw_xyz(1,0,0),
int3(0,-1,0),
vec3::ZERO,
vec3::zero(),
Time::ZERO
),Some(Time::from_secs(2)))
}
@@ -2179,7 +2229,7 @@ mod test{
test_collision_axis_aligned(Body::new(
int3(0,10,-7)>>1,
int3(0,-1,0),
vec3::ZERO,
vec3::zero(),
Time::ZERO
),None)
}
@@ -2188,7 +2238,7 @@ mod test{
test_collision_axis_aligned(Body::new(
int3(7,10,0)>>1,
int3(0,-1,0),
vec3::ZERO,
vec3::zero(),
Time::ZERO
),None)
}
@@ -2197,7 +2247,7 @@ mod test{
test_collision_axis_aligned(Body::new(
int3(0,10,7)>>1,
int3(0,-1,0),
vec3::ZERO,
vec3::zero(),
Time::ZERO
),None)
}
@@ -2206,7 +2256,7 @@ mod test{
test_collision_axis_aligned(Body::new(
int3(-7,10,0)>>1,
int3(0,-1,0),
vec3::ZERO,
vec3::zero(),
Time::ZERO
),None)
}
@@ -2216,7 +2266,7 @@ mod test{
test_collision_axis_aligned(Body::new(
(int3(0,10,-7)>>1)-vec3::raw_xyz(0,0,1),
int3(0,-1,0),
vec3::ZERO,
vec3::zero(),
Time::ZERO
),None)
}
@@ -2225,7 +2275,7 @@ mod test{
test_collision_axis_aligned(Body::new(
(int3(7,10,0)>>1)-vec3::raw_xyz(-1,0,0),
int3(0,-1,0),
vec3::ZERO,
vec3::zero(),
Time::ZERO
),None)
}
@@ -2234,7 +2284,7 @@ mod test{
test_collision_axis_aligned(Body::new(
(int3(0,10,7)>>1)-vec3::raw_xyz(0,0,-1),
int3(0,-1,0),
vec3::ZERO,
vec3::zero(),
Time::ZERO
),None)
}
@@ -2243,7 +2293,7 @@ mod test{
test_collision_axis_aligned(Body::new(
(int3(-7,10,0)>>1)-vec3::raw_xyz(1,0,0),
int3(0,-1,0),
vec3::ZERO,
vec3::zero(),
Time::ZERO
),None)
}

View File

@@ -39,20 +39,18 @@ impl Contact{
//note that this is horrible with fixed point arithmetic
fn solve1(c0:&Contact)->Option<Ratio<Vector3<Fixed<3,96>>,Fixed<2,64>>>{
const EPSILON:Fixed<2,64>=Fixed::from_bits(Fixed::<2,64>::ONE.to_bits().shr(10));
let det=c0.normal.dot(c0.velocity);
if det.abs()<EPSILON{
if det.abs()==Fixed::ZERO{
return None;
}
let d0=c0.normal.dot(c0.position);
Some(c0.normal*d0/det)
}
fn solve2(c0:&Contact,c1:&Contact)->Option<Ratio<Vector3<Fixed<5,160>>,Fixed<4,128>>>{
const EPSILON:Fixed<4,128>=Fixed::from_bits(Fixed::<4,128>::ONE.to_bits().shr(10));
let u0_u1=c0.velocity.cross(c1.velocity);
let n0_n1=c0.normal.cross(c1.normal);
let det=u0_u1.dot(n0_n1);
if det.abs()<EPSILON{
if det.abs()==Fixed::ZERO{
return None;
}
let d0=c0.normal.dot(c0.position);
@@ -60,10 +58,9 @@ fn solve2(c0:&Contact,c1:&Contact)->Option<Ratio<Vector3<Fixed<5,160>>,Fixed<4,1
Some((c1.normal.cross(u0_u1)*d0+u0_u1.cross(c0.normal)*d1)/det)
}
fn solve3(c0:&Contact,c1:&Contact,c2:&Contact)->Option<Ratio<Vector3<Fixed<4,128>>,Fixed<3,96>>>{
const EPSILON:Fixed<3,96>=Fixed::from_bits(Fixed::<3,96>::ONE.to_bits().shr(10));
let n0_n1=c0.normal.cross(c1.normal);
let det=c2.normal.dot(n0_n1);
if det.abs()<EPSILON{
if det.abs()==Fixed::ZERO{
return None;
}
let d0=c0.normal.dot(c0.position);
@@ -149,7 +146,7 @@ fn is_space_enclosed_4(
}
const fn get_push_ray_0(point:Planar64Vec3)->Ray{
Ray{origin:point,direction:vec3::ZERO}
Ray{origin:point,direction:vec3::zero()}
}
fn get_push_ray_1(point:Planar64Vec3,c0:&Contact)->Option<Ray>{
//wrap for speed
@@ -321,13 +318,13 @@ mod tests{
fn test_push_solve(){
let contacts=vec![
Contact{
position:vec3::ZERO,
position:vec3::zero(),
velocity:vec3::Y,
normal:vec3::Y,
}
];
assert_eq!(
vec3::ZERO,
vec3::zero(),
push_solve(&contacts,vec3::NEG_Y)
);
}

View File

@@ -77,7 +77,7 @@ fn simultaneous_collision(){
Time::ZERO,
);
let mut physics=PhysicsState::new_with_body(body);
physics.style_mut().gravity=vec3::ZERO;
physics.style_mut().gravity=vec3::zero();
let mut phys_iter=PhysicsContext::iter_internal(&mut physics,&physics_data,Time::from_secs(2))
.filter(|ins|!matches!(ins.instruction,InternalInstruction::StrafeTick));
// the order that they hit does matter, but we aren't currently worrying about that.
@@ -101,7 +101,7 @@ fn bug_3(){
Time::ZERO,
);
let mut physics=PhysicsState::new_with_body(body);
physics.style_mut().gravity=vec3::ZERO;
physics.style_mut().gravity=vec3::zero();
let mut phys_iter=PhysicsContext::iter_internal(&mut physics,&physics_data,Time::from_secs(3))
.filter(|ins|!matches!(ins.instruction,InternalInstruction::StrafeTick));
// touch side of part at 0,0,0

View File

@@ -210,7 +210,7 @@ pub fn faces_to_mesh(faces:Vec<Vec<integer::Planar64Vec3>>)->model::Mesh{
let color=mb.acquire_color_id(glam::Vec4::ONE);
let tex=mb.acquire_tex_id(glam::Vec2::ZERO);
// normals are ignored by physics
let normal=mb.acquire_normal_id(integer::vec3::ZERO);
let normal=mb.acquire_normal_id(integer::vec3::zero());
let polygon_list=faces.into_iter().map(|face|{
face.into_iter().map(|pos|{

View File

@@ -105,7 +105,7 @@ pub fn convert<'a>(
water:Some(attr::IntersectingWater{
viscosity:integer::Planar64::ONE,
density:integer::Planar64::ONE,
velocity:integer::vec3::ZERO,
velocity:integer::vec3::zero(),
}),
},
general:attr::GeneralAttributes::default(),
@@ -295,7 +295,7 @@ pub fn convert<'a>(
attributes,
transform:integer::Planar64Affine3::new(
integer::mat3::identity(),
integer::vec3::ZERO,
integer::vec3::zero(),
),
color:glam::Vec4::ONE,
});

View File

@@ -0,0 +1,45 @@
/// Euclidean point.
/// Newtype of ideal point.
/// Euclidean points can be offset by ideal points to create a new euclidean point.
/// Two euclidean points cannot be added together.
/// Subtracting two euclidean points gives an ideal point.
// TODO: Affine*Point translates but Affine*Vector does not translate
pub struct Point<P>(P);
use core::ops::Add;
use core::ops::Sub;
use core::ops::AddAssign;
use core::ops::SubAssign;
// Offset euclidean point by ideal point
impl<P:Add> Add<P> for Point<P>{
type Output=Point<P::Output>;
fn add(self,rhs:P)->Self::Output{
Point(self.0+rhs)
}
}
impl<P:Sub> Sub<P> for Point<P>{
type Output=Point<P::Output>;
fn sub(self,rhs:P)->Self::Output{
Point(self.0-rhs)
}
}
impl<P:AddAssign> AddAssign<P> for Point<P>{
fn add_assign(&mut self,rhs:P){
self.0+=rhs;
}
}
impl<P:SubAssign> SubAssign<P> for Point<P>{
fn sub_assign(&mut self,rhs:P){
self.0-=rhs;
}
}
// Extract ideal point from euclidean point
impl<P:Sub> Sub for Point<P>{
type Output=P::Output;
fn sub(self,rhs:Point<P>)->Self::Output{
self.0-rhs.0
}
}

View File

@@ -319,7 +319,7 @@ impl WalkSettings{
self.accelerate.accel.min((-gravity.y*friction).clamp_1())
}
pub fn get_walk_target_velocity(&self,control_dir:Planar64Vec3,normal:Planar64Vec3)->Planar64Vec3{
if control_dir==crate::integer::vec3::ZERO{
if control_dir==crate::integer::vec3::zero(){
return control_dir;
}
let nn=normal.length_squared();
@@ -329,13 +329,13 @@ impl WalkSettings{
let dd=d*d;
if dd<nnmm{
let cr=normal.cross(control_dir);
if cr==crate::integer::vec3::ZERO_2{
crate::integer::vec3::ZERO
if cr==crate::integer::vec3::zero(){
crate::integer::vec3::zero()
}else{
(cr.cross(normal)*self.accelerate.topspeed/((nn*(nnmm-dd)).sqrt())).divide().clamp_1()
}
}else{
crate::integer::vec3::ZERO
crate::integer::vec3::zero()
}
}
pub fn is_slope_walkable(&self,normal:Planar64Vec3,up:Planar64Vec3)->bool{
@@ -360,7 +360,7 @@ impl LadderSettings{
self.accelerate.accel
}
pub fn get_ladder_target_velocity(&self,mut control_dir:Planar64Vec3,normal:Planar64Vec3)->Planar64Vec3{
if control_dir==crate::integer::vec3::ZERO{
if control_dir==crate::integer::vec3::zero(){
return control_dir;
}
let nn=normal.length_squared();
@@ -382,13 +382,13 @@ impl LadderSettings{
//- fix the underlying issue
if dd<nnmm{
let cr=normal.cross(control_dir);
if cr==crate::integer::vec3::ZERO_2{
crate::integer::vec3::ZERO
if cr==crate::integer::vec3::zero(){
crate::integer::vec3::zero()
}else{
(cr.cross(normal)*self.accelerate.topspeed/((nn*(nnmm-dd)).sqrt())).divide().clamp_1()
}
}else{
crate::integer::vec3::ZERO
crate::integer::vec3::zero()
}
}
}

View File

@@ -561,12 +561,6 @@ pub mod vec3{
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:Vector3<Fixed::<2,64>>=Vector3::new([Fixed::<2,64>::ZERO;3]);
pub const ZERO_3:Vector3<Fixed::<3,96>>=Vector3::new([Fixed::<3,96>::ZERO;3]);
pub const ZERO_4:Vector3<Fixed::<4,128>>=Vector3::new([Fixed::<4,128>::ZERO;3]);
pub const ZERO_5:Vector3<Fixed::<5,160>>=Vector3::new([Fixed::<5,160>::ZERO;3]);
pub const ZERO_6:Vector3<Fixed::<6,192>>=Vector3::new([Fixed::<6,192>::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]);
@@ -575,6 +569,10 @@ pub mod vec3{
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]);
// TODO: use #![feature(generic_const_items)] when stabilized https://github.com/rust-lang/rust/issues/113521
pub const fn zero<const N:usize,const F:usize>()->Vector3<Fixed<N,F>>{
Vector3::new([Fixed::ZERO;3])
}
#[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)])
@@ -663,7 +661,7 @@ pub struct Planar64Affine3{
pub translation:Planar64Vec3,
}
impl Planar64Affine3{
pub const IDENTITY:Self=Self::new(mat3::identity(),vec3::ZERO);
pub const IDENTITY:Self=Self::new(mat3::identity(),vec3::zero());
#[inline]
pub const fn new(matrix3:Planar64Mat3,translation:Planar64Vec3)->Self{
Self{matrix3,translation}

View File

@@ -5,6 +5,7 @@ pub mod run;
pub mod aabb;
pub mod model;
pub mod mouse;
pub mod euclidean_point;
pub mod timer;
pub mod integer;
pub mod physics;

View File

@@ -70,6 +70,34 @@ impl<const N:usize,const F:usize> Fixed<N,F>{
pub const fn midpoint(self,other:Self)->Self{
Self::from_bits(self.bits.midpoint(other.bits))
}
#[inline]
pub const fn min(self,other:Self)->Self{
Self::from_bits(self.bits.min(other.bits))
}
#[inline]
pub const fn max(self,other:Self)->Self{
Self::from_bits(self.bits.max(other.bits))
}
/// return the result of self*sign(other)
#[inline]
pub const fn mul_sign<const N1:usize,const F1:usize>(self,other:Fixed<N1,F1>)->Self{
if other.is_negative(){
Self::from_bits(self.bits.neg())
}else if other.is_zero(){
Fixed::ZERO
}else{
self
}
}
/// return the result of self/sign(other) (divide by zero does not change the sign)
#[inline]
pub const fn div_sign<const N1:usize,const F1:usize>(self,other:Fixed<N1,F1>)->Self{
if other.is_negative(){
Self::from_bits(self.bits.neg())
}else{
self
}
}
}
impl<const F:usize> Fixed<1,F>{
/// My old code called this function everywhere so let's provide it
@@ -101,28 +129,6 @@ impl_from!(
i8,i16,i32,i64,i128,isize
);
impl<const N:usize,const F:usize,T> PartialEq<T> for Fixed<N,F>
where
T:Copy,
BInt::<N>:From<T>,
{
#[inline]
fn eq(&self,&other:&T)->bool{
self.bits.eq(&other.into())
}
}
impl<const N:usize,const F:usize,T> PartialOrd<T> for Fixed<N,F>
where
T:Copy,
BInt::<N>:From<T>,
{
#[inline]
fn partial_cmp(&self,&other:&T)->Option<std::cmp::Ordering>{
self.bits.partial_cmp(&other.into())
}
}
impl<const N:usize,const F:usize> std::ops::Neg for Fixed<N,F>{
type Output=Self;
#[inline]
@@ -328,16 +334,6 @@ macro_rules! impl_additive_operator {
self.$method(other)
}
}
impl<const N:usize,const F:usize,U> core::ops::$trait<U> for $struct<N,F>
where
BInt::<N>:From<U>,
{
type Output = $output;
#[inline]
fn $method(self, other: U) -> Self::Output {
Self::from_bits(self.bits.$method(BInt::<N>::from(other).shl(F as u32)))
}
}
};
}
macro_rules! impl_additive_assign_operator {
@@ -348,15 +344,6 @@ macro_rules! impl_additive_assign_operator {
self.bits.$method(other.bits);
}
}
impl<const N:usize,const F:usize,U> core::ops::$trait<U> for $struct<N,F>
where
BInt::<N>:From<U>,
{
#[inline]
fn $method(&mut self, other: U) {
self.bits.$method(BInt::<N>::from(other).shl(F as u32));
}
}
};
}

View File

@@ -251,7 +251,7 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:mode
}
}
//need some way to skip this
if allow_booster&&velocity!=vec3::ZERO{
if allow_booster&&velocity!=vec3::zero(){
general.booster=Some(attr::Booster::Velocity(velocity));
}
Ok(match force_can_collide{

View File

@@ -250,7 +250,7 @@ pub fn convert(
// generate a unit cube as default physics
let pos_list=CUBE_DEFAULT_VERTICES.map(|pos|mb.acquire_pos_id(pos>>1));
let tex=mb.acquire_tex_id(glam::Vec2::ZERO);
let normal=mb.acquire_normal_id(vec3::ZERO);
let normal=mb.acquire_normal_id(vec3::zero());
let color=mb.acquire_color_id(glam::Vec4::ONE);
let polygon_group=PolygonGroup::PolygonList(PolygonList::new(CUBE_DEFAULT_POLYS.map(|poly|poly.map(|[pos_id,_]|
mb.acquire_vertex_id(IndexedVertex{pos:pos_list[pos_id as usize],tex,normal,color})