Compare commits
7 Commits
debug-bug3
...
multi-coll
| Author | SHA1 | Date | |
|---|---|---|---|
|
567ca4b794
|
|||
|
6509bef070
|
|||
|
0fa097a004
|
|||
|
55d4b1d264
|
|||
|
ea28663e95
|
|||
|
bac9be9684
|
|||
|
7e76f3309b
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
|||||||
/target
|
/target
|
||||||
|
.zed
|
||||||
|
|||||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -1677,8 +1677,10 @@ dependencies = [
|
|||||||
name = "integration-testing"
|
name = "integration-testing"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"glam",
|
||||||
"strafesnet_common",
|
"strafesnet_common",
|
||||||
"strafesnet_physics",
|
"strafesnet_physics",
|
||||||
|
"strafesnet_rbx_loader",
|
||||||
"strafesnet_snf",
|
"strafesnet_snf",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,6 @@ impl<T> Body<T>
|
|||||||
self.velocity+(self.acceleration*dt).map(|elem|elem.divide().clamp_1())
|
self.velocity+(self.acceleration*dt).map(|elem|elem.divide().clamp_1())
|
||||||
}
|
}
|
||||||
pub fn advance_time(&mut self,time:Time<T>){
|
pub fn advance_time(&mut self,time:Time<T>){
|
||||||
println!("advance_time");
|
|
||||||
self.position=self.extrapolated_position(time);
|
self.position=self.extrapolated_position(time);
|
||||||
self.velocity=self.extrapolated_velocity(time);
|
self.velocity=self.extrapolated_velocity(time);
|
||||||
self.time=time;
|
self.time=time;
|
||||||
@@ -103,7 +102,6 @@ impl<T> Body<T>
|
|||||||
self.acceleration.map(|elem|(dt*elem).divide().clamp())+self.velocity
|
self.acceleration.map(|elem|(dt*elem).divide().clamp())+self.velocity
|
||||||
}
|
}
|
||||||
pub fn advance_time_ratio_dt(&mut self,dt:crate::model::GigaTime){
|
pub fn advance_time_ratio_dt(&mut self,dt:crate::model::GigaTime){
|
||||||
println!("advance_time_ratio_dt");
|
|
||||||
self.position=self.extrapolated_position_ratio_dt(dt);
|
self.position=self.extrapolated_position_ratio_dt(dt);
|
||||||
self.velocity=self.extrapolated_velocity_ratio_dt(dt);
|
self.velocity=self.extrapolated_velocity_ratio_dt(dt);
|
||||||
self.time+=dt.into();
|
self.time+=dt.into();
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ use crate::physics::{Time,Body};
|
|||||||
|
|
||||||
use core::ops::Bound;
|
use core::ops::Bound;
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum Transition<M:MeshQuery>{
|
enum Transition<M:MeshQuery>{
|
||||||
Miss,
|
Miss,
|
||||||
Next(FEV<M>,GigaTime),
|
Next(FEV<M>,GigaTime),
|
||||||
@@ -77,8 +76,6 @@ impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M>
|
|||||||
M::Face:Copy,
|
M::Face:Copy,
|
||||||
M::Edge:Copy,
|
M::Edge:Copy,
|
||||||
M::Vert:Copy,
|
M::Vert:Copy,
|
||||||
M:std::fmt::Debug,
|
|
||||||
F:std::fmt::Display,
|
|
||||||
F:core::ops::Mul<Fixed<1,32>,Output=Fixed<4,128>>,
|
F:core::ops::Mul<Fixed<1,32>,Output=Fixed<4,128>>,
|
||||||
<F as core::ops::Mul<Fixed<1,32>>>::Output:core::iter::Sum,
|
<F as core::ops::Mul<Fixed<1,32>>>::Output:core::iter::Sum,
|
||||||
M::Offset:core::ops::Sub<<F as std::ops::Mul<Fixed<1,32>>>::Output>,
|
M::Offset:core::ops::Sub<<F as std::ops::Mul<Fixed<1,32>>>::Output>,
|
||||||
@@ -93,15 +90,10 @@ impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M>
|
|||||||
//n=face.normal d=face.dot
|
//n=face.normal d=face.dot
|
||||||
//n.a t^2+n.v t+n.p-d==0
|
//n.a t^2+n.v t+n.p-d==0
|
||||||
let (n,d)=mesh.face_nd(face_id);
|
let (n,d)=mesh.face_nd(face_id);
|
||||||
println!("Face n={} d={}",n,d);
|
|
||||||
//TODO: use higher precision d value?
|
//TODO: use higher precision d value?
|
||||||
//use the mesh transform translation instead of baking it into the d value.
|
//use the mesh transform translation instead of baking it into the d value.
|
||||||
for dt in Fixed::<4,128>::zeroes2((n.dot(body.position)-d)*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){
|
for dt in Fixed::<4,128>::zeroes2((n.dot(body.position)-d)*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){
|
||||||
let low=low(&lower_bound,&dt);
|
if low(&lower_bound,&dt)&&upp(&dt,&upper_bound)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
||||||
let upp=upp(&dt,&upper_bound);
|
|
||||||
let into=n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative();
|
|
||||||
println!("dt={} low={low} upp={upp} into={into}",dt.divide());
|
|
||||||
if low&&upp&&into{
|
|
||||||
upper_bound=Bound::Included(dt);
|
upper_bound=Bound::Included(dt);
|
||||||
best_transition=Transition::Hit(face_id,dt);
|
best_transition=Transition::Hit(face_id,dt);
|
||||||
break;
|
break;
|
||||||
@@ -136,16 +128,10 @@ impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M>
|
|||||||
let face_n=mesh.face_nd(edge_face_id).0;
|
let face_n=mesh.face_nd(edge_face_id).0;
|
||||||
//edge_n gets parity from the order of edge_faces
|
//edge_n gets parity from the order of edge_faces
|
||||||
let n=face_n.cross(edge_n)*((i as i64)*2-1);
|
let n=face_n.cross(edge_n)*((i as i64)*2-1);
|
||||||
let d=n.dot(delta_pos).wrap_4();
|
|
||||||
println!("Edge Face={:?} boundary_n={} boundary_d={}",edge_face_id,n,d>>1);
|
|
||||||
//WARNING yada yada d *2
|
//WARNING yada yada d *2
|
||||||
//wrap for speed
|
//wrap for speed
|
||||||
for dt in Fixed::<4,128>::zeroes2(d,n.dot(body.velocity).wrap_4()*2,n.dot(body.acceleration).wrap_4()){
|
for dt in Fixed::<4,128>::zeroes2(n.dot(delta_pos).wrap_4(),n.dot(body.velocity).wrap_4()*2,n.dot(body.acceleration).wrap_4()){
|
||||||
let low=low(&lower_bound,&dt);
|
if low(&lower_bound,&dt)&&upp(&dt,&upper_bound)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
||||||
let upp=upp(&dt,&upper_bound);
|
|
||||||
let into=n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative();
|
|
||||||
println!("dt={} low={low} upp={upp} into={into}",dt.divide());
|
|
||||||
if low&&upp&&into{
|
|
||||||
upper_bound=Bound::Included(dt);
|
upper_bound=Bound::Included(dt);
|
||||||
best_transition=Transition::Next(FEV::Face(edge_face_id),dt);
|
best_transition=Transition::Next(FEV::Face(edge_face_id),dt);
|
||||||
break;
|
break;
|
||||||
@@ -189,11 +175,8 @@ impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M>
|
|||||||
pub fn crawl(mut self,mesh:&M,relative_body:&Body,lower_bound:Bound<&Time>,upper_bound:Bound<&Time>)->CrawlResult<M>{
|
pub fn crawl(mut self,mesh:&M,relative_body:&Body,lower_bound:Bound<&Time>,upper_bound:Bound<&Time>)->CrawlResult<M>{
|
||||||
let mut lower_bound=lower_bound.map(|&t|into_giga_time(t,relative_body.time));
|
let mut lower_bound=lower_bound.map(|&t|into_giga_time(t,relative_body.time));
|
||||||
let upper_bound=upper_bound.map(|&t|into_giga_time(t,relative_body.time));
|
let upper_bound=upper_bound.map(|&t|into_giga_time(t,relative_body.time));
|
||||||
println!("crawl begin={self:?}");
|
|
||||||
for _ in 0..20{
|
for _ in 0..20{
|
||||||
let transition=self.next_transition(mesh,relative_body,lower_bound,upper_bound);
|
match self.next_transition(mesh,relative_body,lower_bound,upper_bound){
|
||||||
println!("transition={transition:?}");
|
|
||||||
match transition{
|
|
||||||
Transition::Miss=>return CrawlResult::Miss(self),
|
Transition::Miss=>return CrawlResult::Miss(self),
|
||||||
Transition::Next(next_fev,next_time)=>(self,lower_bound)=(next_fev,Bound::Included(next_time)),
|
Transition::Next(next_fev,next_time)=>(self,lower_bound)=(next_fev,Bound::Included(next_time)),
|
||||||
Transition::Hit(face,time)=>return CrawlResult::Hit(face,time),
|
Transition::Hit(face,time)=>return CrawlResult::Hit(face,time),
|
||||||
|
|||||||
@@ -76,9 +76,9 @@ struct Face{
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Vert(Planar64Vec3);
|
struct Vert(Planar64Vec3);
|
||||||
pub trait MeshQuery{
|
pub trait MeshQuery{
|
||||||
type Face:Copy+std::fmt::Debug;
|
type Face:Copy;
|
||||||
type Edge:Copy+DirectedEdge+std::fmt::Debug;
|
type Edge:Copy+DirectedEdge;
|
||||||
type Vert:Copy+std::fmt::Debug;
|
type Vert:Copy;
|
||||||
// Vertex must be Planar64Vec3 because it represents an actual position
|
// Vertex must be Planar64Vec3 because it represents an actual position
|
||||||
type Normal;
|
type Normal;
|
||||||
type Offset;
|
type Offset;
|
||||||
@@ -756,9 +756,7 @@ impl MinkowskiMesh<'_>{
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
pub fn predict_collision_in(&self,relative_body:&Body,range:impl RangeBounds<Time>)->Option<(MinkowskiFace,GigaTime)>{
|
pub fn predict_collision_in(&self,relative_body:&Body,range:impl RangeBounds<Time>)->Option<(MinkowskiFace,GigaTime)>{
|
||||||
println!("@@@BEGIN SETUP@@@");
|
|
||||||
self.closest_fev_not_inside(*relative_body,range.start_bound()).and_then(|fev|{
|
self.closest_fev_not_inside(*relative_body,range.start_bound()).and_then(|fev|{
|
||||||
println!("@@@BEGIN REAL CRAWL@@@");
|
|
||||||
//continue forwards along the body parabola
|
//continue forwards along the body parabola
|
||||||
fev.crawl(self,relative_body,range.start_bound(),range.end_bound()).hit()
|
fev.crawl(self,relative_body,range.start_bound(),range.end_bound()).hit()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -25,10 +25,14 @@ use strafesnet_common::physics::{Instruction,MouseInstruction,ModeInstruction,Mi
|
|||||||
//when the physics asks itself what happens next, this is how it's represented
|
//when the physics asks itself what happens next, this is how it's represented
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum InternalInstruction{
|
pub enum InternalInstruction{
|
||||||
CollisionStart(Collision,model_physics::GigaTime),
|
// begin accepting touch updates
|
||||||
CollisionEnd(Collision,model_physics::GigaTime),
|
OpenMultiCollision(model_physics::GigaTime),
|
||||||
|
// mutliple touch updates
|
||||||
|
CollisionStart(Collision),
|
||||||
|
CollisionEnd(Collision),
|
||||||
|
// confirm there will be no more touch updates and apply the transaction
|
||||||
|
CloseMultiCollision,
|
||||||
StrafeTick,
|
StrafeTick,
|
||||||
// TODO: add GigaTime to ReachWalkTargetVelocity
|
|
||||||
ReachWalkTargetVelocity,
|
ReachWalkTargetVelocity,
|
||||||
// Water,
|
// Water,
|
||||||
}
|
}
|
||||||
@@ -875,6 +879,9 @@ impl PhysicsState{
|
|||||||
..Self::default()
|
..Self::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub const fn body(&self)->&Body{
|
||||||
|
&self.body
|
||||||
|
}
|
||||||
pub fn camera_body(&self)->Body{
|
pub fn camera_body(&self)->Body{
|
||||||
Body{
|
Body{
|
||||||
position:self.body.position+self.style.camera_offset,
|
position:self.body.position+self.style.camera_offset,
|
||||||
@@ -950,8 +957,8 @@ pub struct PhysicsData{
|
|||||||
//cached calculations
|
//cached calculations
|
||||||
hitbox_mesh:HitboxMesh,
|
hitbox_mesh:HitboxMesh,
|
||||||
}
|
}
|
||||||
impl Default for PhysicsData{
|
impl PhysicsData{
|
||||||
fn default()->Self{
|
pub fn empty()->Self{
|
||||||
Self{
|
Self{
|
||||||
bvh:bvh::BvhNode::empty(),
|
bvh:bvh::BvhNode::empty(),
|
||||||
models:Default::default(),
|
models:Default::default(),
|
||||||
@@ -959,47 +966,7 @@ impl Default for PhysicsData{
|
|||||||
hitbox_mesh:StyleModifiers::default().calculate_mesh(),
|
hitbox_mesh:StyleModifiers::default().calculate_mesh(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
pub fn new(map:&map::CompleteMap)->Self{
|
||||||
// the collection of information required to run physics
|
|
||||||
pub struct PhysicsContext<'a>{
|
|
||||||
state:&'a mut PhysicsState,//this captures the entire state of the physics.
|
|
||||||
data:&'a PhysicsData,//data currently loaded into memory which is needded for physics to run, but is not part of the state.
|
|
||||||
}
|
|
||||||
// the physics consumes both Instruction and PhysicsInternalInstruction,
|
|
||||||
// but can only emit PhysicsInternalInstruction
|
|
||||||
impl InstructionConsumer<InternalInstruction> for PhysicsContext<'_>{
|
|
||||||
type Time=Time;
|
|
||||||
fn process_instruction(&mut self,ins:TimedInstruction<InternalInstruction,Time>){
|
|
||||||
atomic_internal_instruction(&mut self.state,&self.data,ins)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl InstructionConsumer<Instruction> for PhysicsContext<'_>{
|
|
||||||
type Time=Time;
|
|
||||||
fn process_instruction(&mut self,ins:TimedInstruction<Instruction,Time>){
|
|
||||||
atomic_input_instruction(&mut self.state,&self.data,ins)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl InstructionEmitter<InternalInstruction> for PhysicsContext<'_>{
|
|
||||||
type Time=Time;
|
|
||||||
//this little next instruction function could cache its return value and invalidate the cached value by watching the State.
|
|
||||||
fn next_instruction(&self,time_limit:Time)->Option<TimedInstruction<InternalInstruction,Time>>{
|
|
||||||
next_instruction_internal(&self.state,&self.data,time_limit)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl PhysicsContext<'_>{
|
|
||||||
pub fn run_input_instruction(
|
|
||||||
state:&mut PhysicsState,
|
|
||||||
data:&PhysicsData,
|
|
||||||
instruction:TimedInstruction<Instruction,Time>
|
|
||||||
){
|
|
||||||
let mut context=PhysicsContext{state,data};
|
|
||||||
context.process_exhaustive(instruction.time);
|
|
||||||
context.process_instruction(instruction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl PhysicsData{
|
|
||||||
/// use with caution, this is the only non-instruction way to mess with physics
|
|
||||||
pub fn generate_models(&mut self,map:&map::CompleteMap){
|
|
||||||
let modes=map.modes.clone().denormalize();
|
let modes=map.modes.clone().denormalize();
|
||||||
let mut used_contact_attributes=Vec::new();
|
let mut used_contact_attributes=Vec::new();
|
||||||
let mut used_intersect_attributes=Vec::new();
|
let mut used_intersect_attributes=Vec::new();
|
||||||
@@ -1126,21 +1093,59 @@ impl PhysicsData{
|
|||||||
(IntersectAttributesId::new(attr_id as u32),attr)
|
(IntersectAttributesId::new(attr_id as u32),attr)
|
||||||
).collect(),
|
).collect(),
|
||||||
};
|
};
|
||||||
self.bvh=bvh;
|
|
||||||
self.models=models;
|
|
||||||
self.modes=modes;
|
|
||||||
//hitbox_mesh is unchanged
|
|
||||||
println!("Physics Objects: {}",model_count);
|
println!("Physics Objects: {}",model_count);
|
||||||
|
Self{
|
||||||
|
hitbox_mesh:StyleModifiers::default().calculate_mesh(),
|
||||||
|
bvh,
|
||||||
|
models,
|
||||||
|
modes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// the collection of information required to run physics
|
||||||
|
pub struct PhysicsContext<'a>{
|
||||||
|
state:&'a mut PhysicsState,//this captures the entire state of the physics.
|
||||||
|
data:&'a PhysicsData,//data currently loaded into memory which is needded for physics to run, but is not part of the state.
|
||||||
|
}
|
||||||
|
// the physics consumes both Instruction and PhysicsInternalInstruction,
|
||||||
|
// but can only emit PhysicsInternalInstruction
|
||||||
|
impl InstructionConsumer<InternalInstruction> for PhysicsContext<'_>{
|
||||||
|
type Time=Time;
|
||||||
|
fn process_instruction(&mut self,ins:TimedInstruction<InternalInstruction,Time>){
|
||||||
|
atomic_internal_instruction(&mut self.state,&self.data,ins)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl InstructionConsumer<Instruction> for PhysicsContext<'_>{
|
||||||
|
type Time=Time;
|
||||||
|
fn process_instruction(&mut self,ins:TimedInstruction<Instruction,Time>){
|
||||||
|
atomic_input_instruction(&mut self.state,&self.data,ins)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl InstructionEmitter<InternalInstruction> for PhysicsContext<'_>{
|
||||||
|
type Time=Time;
|
||||||
|
//this little next instruction function could cache its return value and invalidate the cached value by watching the State.
|
||||||
|
fn next_instruction(&self,time_limit:Time)->Option<TimedInstruction<InternalInstruction,Time>>{
|
||||||
|
next_instruction_internal(&self.state,&self.data,time_limit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl PhysicsContext<'_>{
|
||||||
|
pub fn run_input_instruction(
|
||||||
|
state:&mut PhysicsState,
|
||||||
|
data:&PhysicsData,
|
||||||
|
instruction:TimedInstruction<Instruction,Time>
|
||||||
|
){
|
||||||
|
let mut context=PhysicsContext{state,data};
|
||||||
|
context.process_exhaustive(instruction.time);
|
||||||
|
context.process_instruction(instruction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//this is the one who asks
|
//this is the one who asks
|
||||||
fn next_instruction_internal(state:&PhysicsState,data:&PhysicsData,time_limit:Time)->Option<TimedInstruction<InternalInstruction,Time>>{
|
fn next_instruction_internal(state:&PhysicsState,data:&PhysicsData,time_limit:Time)->Option<TimedInstruction<InternalInstruction,Time>>{
|
||||||
println!("==== next_instruction_internal ====");
|
|
||||||
//JUST POLLING!!! NO MUTATION
|
//JUST POLLING!!! NO MUTATION
|
||||||
let mut collector=instruction::InstructionCollector::new(time_limit);
|
let mut collector=instruction::InstructionCollector::new(time_limit);
|
||||||
|
|
||||||
// collector.collect(state.next_move_instruction());
|
collector.collect(state.next_move_instruction());
|
||||||
|
|
||||||
//check for collision ends
|
//check for collision ends
|
||||||
state.touching.predict_collision_end(&mut collector,&data.models,&data.hitbox_mesh,&state.body,state.time);
|
state.touching.predict_collision_end(&mut collector,&data.models,&data.hitbox_mesh,&state.body,state.time);
|
||||||
@@ -1152,7 +1157,6 @@ impl PhysicsData{
|
|||||||
//let relative_body=state.body.relative_to(&Body::ZERO);
|
//let relative_body=state.body.relative_to(&Body::ZERO);
|
||||||
let relative_body=&state.body;
|
let relative_body=&state.body;
|
||||||
data.bvh.sample_aabb(&aabb,&mut |&convex_mesh_id|{
|
data.bvh.sample_aabb(&aabb,&mut |&convex_mesh_id|{
|
||||||
println!("Sampling object id={:?}",convex_mesh_id.model_id);
|
|
||||||
//no checks are needed because of the time limits.
|
//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());
|
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,data.hitbox_mesh.transformed_mesh());
|
||||||
@@ -1268,7 +1272,6 @@ fn set_velocity_cull(body:&mut Body,touching:&mut TouchingState,models:&PhysicsM
|
|||||||
let n=contact_normal(models,hitbox_mesh,contact);
|
let n=contact_normal(models,hitbox_mesh,contact);
|
||||||
let r=n.dot(v).is_positive();
|
let r=n.dot(v).is_positive();
|
||||||
if r{
|
if r{
|
||||||
println!("culled {:?}",contact.model_id);
|
|
||||||
culled=true;
|
culled=true;
|
||||||
}
|
}
|
||||||
!r
|
!r
|
||||||
@@ -1637,7 +1640,6 @@ fn collision_end_contact(
|
|||||||
_attr:&gameplay_attributes::ContactAttributes,
|
_attr:&gameplay_attributes::ContactAttributes,
|
||||||
contact:ContactCollision,
|
contact:ContactCollision,
|
||||||
){
|
){
|
||||||
println!("collision_end {:?}",contact.model_id);
|
|
||||||
touching.remove(&Collision::Contact(contact));//remove contact before calling contact_constrain_acceleration
|
touching.remove(&Collision::Contact(contact));//remove contact before calling contact_constrain_acceleration
|
||||||
//check ground
|
//check ground
|
||||||
//TODO do better
|
//TODO do better
|
||||||
@@ -1685,16 +1687,18 @@ fn collision_end_intersect(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction<InternalInstruction,Time>){
|
fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction<InternalInstruction,Time>){
|
||||||
println!("\n==== atomic_internal_instruction ====");
|
|
||||||
state.time=ins.time;
|
state.time=ins.time;
|
||||||
match ins.instruction{
|
let (should_advance_body,goober_time)=match ins.instruction{
|
||||||
// collisions advance the body precisely
|
|
||||||
InternalInstruction::CollisionStart(_,dt)
|
InternalInstruction::CollisionStart(_,dt)
|
||||||
|InternalInstruction::CollisionEnd(_,dt)=>state.body.advance_time_ratio_dt(dt),
|
|InternalInstruction::CollisionEnd(_,dt)=>(true,Some(dt)),
|
||||||
// this advances imprecisely
|
InternalInstruction::StrafeTick
|
||||||
InternalInstruction::ReachWalkTargetVelocity=>state.body.advance_time(state.time),
|
|InternalInstruction::ReachWalkTargetVelocity=>(true,None),
|
||||||
// strafe tick decides for itself whether to advance the body.
|
};
|
||||||
InternalInstruction::StrafeTick=>(),
|
if should_advance_body{
|
||||||
|
match goober_time{
|
||||||
|
Some(dt)=>state.body.advance_time_ratio_dt(dt),
|
||||||
|
None=>state.body.advance_time(state.time),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
match ins.instruction{
|
match ins.instruction{
|
||||||
InternalInstruction::CollisionStart(collision,_)=>{
|
InternalInstruction::CollisionStart(collision,_)=>{
|
||||||
@@ -1741,8 +1745,6 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim
|
|||||||
let masked_controls=strafe_settings.mask(controls);
|
let masked_controls=strafe_settings.mask(controls);
|
||||||
let control_dir=state.style.get_control_dir(masked_controls);
|
let control_dir=state.style.get_control_dir(masked_controls);
|
||||||
if control_dir!=vec3::ZERO{
|
if control_dir!=vec3::ZERO{
|
||||||
// manually advance time
|
|
||||||
state.body.advance_time(state.time);
|
|
||||||
let camera_mat=state.camera.simulate_move_rotation_y(state.input_state.lerp_delta(state.time).x);
|
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()){
|
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
|
//this is wrong but will work ig
|
||||||
@@ -2129,4 +2131,115 @@ mod test{
|
|||||||
Time::ZERO
|
Time::ZERO
|
||||||
),None);
|
),None);
|
||||||
}
|
}
|
||||||
|
// overlap edges by 1 epsilon
|
||||||
|
#[test]
|
||||||
|
fn almost_miss_north(){
|
||||||
|
test_collision_axis_aligned(Body::new(
|
||||||
|
(int3(0,10,-7)>>1)+vec3::raw_xyz(0,0,1),
|
||||||
|
int3(0,-1,0),
|
||||||
|
vec3::ZERO,
|
||||||
|
Time::ZERO
|
||||||
|
),Some(Time::from_secs(2)))
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn almost_miss_east(){
|
||||||
|
test_collision_axis_aligned(Body::new(
|
||||||
|
(int3(7,10,0)>>1)+vec3::raw_xyz(-1,0,0),
|
||||||
|
int3(0,-1,0),
|
||||||
|
vec3::ZERO,
|
||||||
|
Time::ZERO
|
||||||
|
),Some(Time::from_secs(2)))
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn almost_miss_south(){
|
||||||
|
test_collision_axis_aligned(Body::new(
|
||||||
|
(int3(0,10,7)>>1)+vec3::raw_xyz(0,0,-1),
|
||||||
|
int3(0,-1,0),
|
||||||
|
vec3::ZERO,
|
||||||
|
Time::ZERO
|
||||||
|
),Some(Time::from_secs(2)))
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn almost_miss_west(){
|
||||||
|
test_collision_axis_aligned(Body::new(
|
||||||
|
(int3(-7,10,0)>>1)+vec3::raw_xyz(1,0,0),
|
||||||
|
int3(0,-1,0),
|
||||||
|
vec3::ZERO,
|
||||||
|
Time::ZERO
|
||||||
|
),Some(Time::from_secs(2)))
|
||||||
|
}
|
||||||
|
// exactly miss edges
|
||||||
|
#[test]
|
||||||
|
fn exact_miss_north(){
|
||||||
|
test_collision_axis_aligned(Body::new(
|
||||||
|
int3(0,10,-7)>>1,
|
||||||
|
int3(0,-1,0),
|
||||||
|
vec3::ZERO,
|
||||||
|
Time::ZERO
|
||||||
|
),None)
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn exact_miss_east(){
|
||||||
|
test_collision_axis_aligned(Body::new(
|
||||||
|
int3(7,10,0)>>1,
|
||||||
|
int3(0,-1,0),
|
||||||
|
vec3::ZERO,
|
||||||
|
Time::ZERO
|
||||||
|
),None)
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn exact_miss_south(){
|
||||||
|
test_collision_axis_aligned(Body::new(
|
||||||
|
int3(0,10,7)>>1,
|
||||||
|
int3(0,-1,0),
|
||||||
|
vec3::ZERO,
|
||||||
|
Time::ZERO
|
||||||
|
),None)
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn exact_miss_west(){
|
||||||
|
test_collision_axis_aligned(Body::new(
|
||||||
|
int3(-7,10,0)>>1,
|
||||||
|
int3(0,-1,0),
|
||||||
|
vec3::ZERO,
|
||||||
|
Time::ZERO
|
||||||
|
),None)
|
||||||
|
}
|
||||||
|
// miss edges by 1 epsilon
|
||||||
|
#[test]
|
||||||
|
fn narrow_miss_north(){
|
||||||
|
test_collision_axis_aligned(Body::new(
|
||||||
|
(int3(0,10,-7)>>1)-vec3::raw_xyz(0,0,1),
|
||||||
|
int3(0,-1,0),
|
||||||
|
vec3::ZERO,
|
||||||
|
Time::ZERO
|
||||||
|
),None)
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn narrow_miss_east(){
|
||||||
|
test_collision_axis_aligned(Body::new(
|
||||||
|
(int3(7,10,0)>>1)-vec3::raw_xyz(-1,0,0),
|
||||||
|
int3(0,-1,0),
|
||||||
|
vec3::ZERO,
|
||||||
|
Time::ZERO
|
||||||
|
),None)
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn narrow_miss_south(){
|
||||||
|
test_collision_axis_aligned(Body::new(
|
||||||
|
(int3(0,10,7)>>1)-vec3::raw_xyz(0,0,-1),
|
||||||
|
int3(0,-1,0),
|
||||||
|
vec3::ZERO,
|
||||||
|
Time::ZERO
|
||||||
|
),None)
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn narrow_miss_west(){
|
||||||
|
test_collision_axis_aligned(Body::new(
|
||||||
|
(int3(-7,10,0)>>1)-vec3::raw_xyz(1,0,0),
|
||||||
|
int3(0,-1,0),
|
||||||
|
vec3::ZERO,
|
||||||
|
Time::ZERO
|
||||||
|
),None)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ impl Session{
|
|||||||
user_settings,
|
user_settings,
|
||||||
directories,
|
directories,
|
||||||
mouse_interpolator:MouseInterpolator::new(),
|
mouse_interpolator:MouseInterpolator::new(),
|
||||||
geometry_shared:Default::default(),
|
geometry_shared:PhysicsData::empty(),
|
||||||
simulation,
|
simulation,
|
||||||
view_state:ViewState::Play,
|
view_state:ViewState::Play,
|
||||||
recording:Default::default(),
|
recording:Default::default(),
|
||||||
@@ -184,7 +184,7 @@ impl Session{
|
|||||||
}
|
}
|
||||||
fn change_map(&mut self,map:&strafesnet_common::map::CompleteMap){
|
fn change_map(&mut self,map:&strafesnet_common::map::CompleteMap){
|
||||||
self.simulation.physics.clear();
|
self.simulation.physics.clear();
|
||||||
self.geometry_shared.generate_models(map);
|
self.geometry_shared=PhysicsData::new(map);
|
||||||
}
|
}
|
||||||
pub fn get_frame_state(&self,time:SessionTime)->Option<FrameState>{
|
pub fn get_frame_state(&self,time:SessionTime)->Option<FrameState>{
|
||||||
match &self.view_state{
|
match &self.view_state{
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ version = "0.1.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
glam = "0.30.0"
|
||||||
strafesnet_common = { path = "../lib/common", registry = "strafesnet" }
|
strafesnet_common = { path = "../lib/common", registry = "strafesnet" }
|
||||||
strafesnet_physics = { path = "../engine/physics", registry = "strafesnet" }
|
strafesnet_physics = { path = "../engine/physics", registry = "strafesnet" }
|
||||||
strafesnet_snf = { path = "../lib/snf", registry = "strafesnet" }
|
strafesnet_snf = { path = "../lib/snf", registry = "strafesnet" }
|
||||||
|
# this is just for the primitive constructor
|
||||||
|
strafesnet_rbx_loader = { path = "../lib/rbx_loader", registry = "strafesnet" }
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ mod util;
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test_scenes;
|
||||||
|
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
@@ -29,9 +31,8 @@ fn run_replay()->Result<(),ReplayError>{
|
|||||||
let bot=strafesnet_snf::read_bot(data)?.read_all()?;
|
let bot=strafesnet_snf::read_bot(data)?.read_all()?;
|
||||||
|
|
||||||
// create recording
|
// create recording
|
||||||
let mut physics_data=PhysicsData::default();
|
|
||||||
println!("generating models..");
|
println!("generating models..");
|
||||||
physics_data.generate_models(&map);
|
let physics_data=PhysicsData::new(&map);
|
||||||
println!("simulating...");
|
println!("simulating...");
|
||||||
let mut physics=PhysicsState::default();
|
let mut physics=PhysicsState::default();
|
||||||
for ins in bot.instructions{
|
for ins in bot.instructions{
|
||||||
@@ -139,9 +140,8 @@ fn test_determinism()->Result<(),ReplayError>{
|
|||||||
let data=read_entire_file("../tools/bhop_maps/5692113331.snfm")?;
|
let data=read_entire_file("../tools/bhop_maps/5692113331.snfm")?;
|
||||||
let map=strafesnet_snf::read_map(data)?.into_complete_map()?;
|
let map=strafesnet_snf::read_map(data)?.into_complete_map()?;
|
||||||
|
|
||||||
let mut physics_data=PhysicsData::default();
|
|
||||||
println!("generating models..");
|
println!("generating models..");
|
||||||
physics_data.generate_models(&map);
|
let physics_data=PhysicsData::new(&map);
|
||||||
|
|
||||||
let (send,recv)=std::sync::mpsc::channel();
|
let (send,recv)=std::sync::mpsc::channel();
|
||||||
|
|
||||||
|
|||||||
91
integration-testing/src/test_scenes.rs
Normal file
91
integration-testing/src/test_scenes.rs
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
use strafesnet_physics::physics::{PhysicsData,PhysicsState,PhysicsContext};
|
||||||
|
use strafesnet_common::gameplay_modes::NormalizedModes;
|
||||||
|
use strafesnet_common::gameplay_attributes::{CollisionAttributes,CollisionAttributesId};
|
||||||
|
use strafesnet_common::integer::{vec3,mat3,Planar64Affine3,Time};
|
||||||
|
use strafesnet_common::model::{Mesh,Model,MeshId,ModelId,RenderConfigId};
|
||||||
|
use strafesnet_common::map::CompleteMap;
|
||||||
|
use strafesnet_common::physics::Instruction;
|
||||||
|
use strafesnet_common::instruction::TimedInstruction;
|
||||||
|
use strafesnet_rbx_loader::primitives::{unit_cube,CubeFaceDescription};
|
||||||
|
|
||||||
|
struct TestSceneBuilder{
|
||||||
|
meshes:Vec<Mesh>,
|
||||||
|
models:Vec<Model>,
|
||||||
|
}
|
||||||
|
impl TestSceneBuilder{
|
||||||
|
fn new()->Self{
|
||||||
|
Self{
|
||||||
|
meshes:Vec::new(),
|
||||||
|
models:Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn push_mesh(&mut self,mesh:Mesh)->MeshId{
|
||||||
|
let mesh_id=self.meshes.len();
|
||||||
|
self.meshes.push(mesh);
|
||||||
|
MeshId::new(mesh_id as u32)
|
||||||
|
}
|
||||||
|
fn push_mesh_instance(&mut self,mesh:MeshId,transform:Planar64Affine3)->ModelId{
|
||||||
|
let model=Model{
|
||||||
|
mesh,
|
||||||
|
attributes:CollisionAttributesId::new(0),
|
||||||
|
color:glam::Vec4::ONE,
|
||||||
|
transform,
|
||||||
|
};
|
||||||
|
let model_id=self.models.len();
|
||||||
|
self.models.push(model);
|
||||||
|
ModelId::new(model_id as u32)
|
||||||
|
}
|
||||||
|
fn build(self)->PhysicsData{
|
||||||
|
let modes=NormalizedModes::new(Vec::new());
|
||||||
|
let attributes=vec![CollisionAttributes::contact_default()];
|
||||||
|
let meshes=self.meshes;
|
||||||
|
let models=self.models;
|
||||||
|
let textures=Vec::new();
|
||||||
|
let render_configs=Vec::new();
|
||||||
|
PhysicsData::new(&CompleteMap{
|
||||||
|
modes,
|
||||||
|
attributes,
|
||||||
|
meshes,
|
||||||
|
models,
|
||||||
|
textures,
|
||||||
|
render_configs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_scene()->PhysicsData{
|
||||||
|
let mut builder=TestSceneBuilder::new();
|
||||||
|
let cube_face_description=CubeFaceDescription::new(Default::default(),RenderConfigId::new(0));
|
||||||
|
let mesh=builder.push_mesh(unit_cube(cube_face_description));
|
||||||
|
// place two 5x5x5 cubes.
|
||||||
|
builder.push_mesh_instance(mesh,Planar64Affine3::new(
|
||||||
|
mat3::from_diagonal(vec3::int(5,5,5)>>1),
|
||||||
|
vec3::int(0,0,0)
|
||||||
|
));
|
||||||
|
builder.push_mesh_instance(mesh,Planar64Affine3::new(
|
||||||
|
mat3::from_diagonal(vec3::int(5,5,5)>>1),
|
||||||
|
vec3::int(5,-5,0)
|
||||||
|
));
|
||||||
|
builder.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn simultaneous_collision(){
|
||||||
|
let physics_data=test_scene();
|
||||||
|
let body=strafesnet_physics::physics::Body::new(
|
||||||
|
vec3::int(5+1,1,0),
|
||||||
|
vec3::int(-1,-1,0),
|
||||||
|
vec3::int(0,0,0),
|
||||||
|
Time::ZERO,
|
||||||
|
);
|
||||||
|
let mut physics=PhysicsState::new_with_body(body);
|
||||||
|
PhysicsContext::run_input_instruction(&mut physics,&physics_data,TimedInstruction{
|
||||||
|
time:Time::from_secs(2),
|
||||||
|
instruction:Instruction::Idle,
|
||||||
|
});
|
||||||
|
let body=physics.body();
|
||||||
|
assert_eq!(body.position,vec3::int(5,0,0));
|
||||||
|
assert_eq!(body.velocity,vec3::int(0,0,0));
|
||||||
|
assert_eq!(body.acceleration,vec3::int(0,0,0));
|
||||||
|
assert_eq!(body.time,Time::ONE_SECOND);
|
||||||
|
}
|
||||||
@@ -10,9 +10,8 @@ fn physics_bug_2()->Result<(),ReplayError>{
|
|||||||
let map=strafesnet_snf::read_map(data)?.into_complete_map()?;
|
let map=strafesnet_snf::read_map(data)?.into_complete_map()?;
|
||||||
|
|
||||||
// create recording
|
// create recording
|
||||||
let mut physics_data=PhysicsData::default();
|
|
||||||
println!("generating models..");
|
println!("generating models..");
|
||||||
physics_data.generate_models(&map);
|
let physics_data=PhysicsData::new(&map);
|
||||||
println!("simulating...");
|
println!("simulating...");
|
||||||
|
|
||||||
//teleport to bug
|
//teleport to bug
|
||||||
@@ -45,9 +44,8 @@ fn physics_bug_3()->Result<(),ReplayError>{
|
|||||||
let map=strafesnet_snf::read_map(data)?.into_complete_map()?;
|
let map=strafesnet_snf::read_map(data)?.into_complete_map()?;
|
||||||
|
|
||||||
// create recording
|
// create recording
|
||||||
let mut physics_data=PhysicsData::default();
|
|
||||||
println!("generating models..");
|
println!("generating models..");
|
||||||
physics_data.generate_models(&map);
|
let physics_data=PhysicsData::new(&map);
|
||||||
println!("simulating...");
|
println!("simulating...");
|
||||||
|
|
||||||
//teleport to bug
|
//teleport to bug
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ use crate::integer::{vec3,Planar64Vec3};
|
|||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Aabb{
|
pub struct Aabb{
|
||||||
|
// min is inclusive
|
||||||
min:Planar64Vec3,
|
min:Planar64Vec3,
|
||||||
|
// max is not inclusive
|
||||||
max:Planar64Vec3,
|
max:Planar64Vec3,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +45,7 @@ impl Aabb{
|
|||||||
}
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn contains(&self,point:Planar64Vec3)->bool{
|
pub fn contains(&self,point:Planar64Vec3)->bool{
|
||||||
let bvec=self.min.lt(point)&point.lt(self.max);
|
let bvec=self.min.le(point)&point.lt(self.max);
|
||||||
bvec.all()
|
bvec.all()
|
||||||
}
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ mod mesh;
|
|||||||
mod error;
|
mod error;
|
||||||
mod union;
|
mod union;
|
||||||
pub mod loader;
|
pub mod loader;
|
||||||
mod primitives;
|
pub mod primitives;
|
||||||
|
|
||||||
pub mod data{
|
pub mod data{
|
||||||
pub struct RobloxMeshBytes(Vec<u8>);
|
pub struct RobloxMeshBytes(Vec<u8>);
|
||||||
|
|||||||
Reference in New Issue
Block a user