Compare commits
1 Commits
multi-coll
...
bedbug
| Author | SHA1 | Date | |
|---|---|---|---|
|
673602516f
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1 @@
|
||||
/target
|
||||
.zed
|
||||
|
||||
29
.zed/debug.json
Normal file
29
.zed/debug.json
Normal file
@@ -0,0 +1,29 @@
|
||||
// Project-local debug tasks
|
||||
//
|
||||
// For more documentation on how to configure debug tasks,
|
||||
// see: https://zed.dev/docs/debugger
|
||||
[
|
||||
{
|
||||
"label": "Strafe Client",
|
||||
"adapter": "CodeLLDB",
|
||||
"program": "target/debug/strafe-client",
|
||||
"args": [
|
||||
"tools/bhop_maps/5692113331.snfm"
|
||||
],
|
||||
"request": "launch",
|
||||
"build": {
|
||||
"command": "cargo",
|
||||
"args": ["build","-p strafe-client"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Integration Testing",
|
||||
"adapter": "CodeLLDB",
|
||||
"program": "target/release/integration-testing",
|
||||
"request": "launch",
|
||||
"build": {
|
||||
"command": "cargo",
|
||||
"args": ["test","-p integration-testing"]
|
||||
}
|
||||
}
|
||||
]
|
||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -1677,10 +1677,8 @@ dependencies = [
|
||||
name = "integration-testing"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"glam",
|
||||
"strafesnet_common",
|
||||
"strafesnet_physics",
|
||||
"strafesnet_rbx_loader",
|
||||
"strafesnet_snf",
|
||||
]
|
||||
|
||||
|
||||
@@ -25,13 +25,8 @@ use strafesnet_common::physics::{Instruction,MouseInstruction,ModeInstruction,Mi
|
||||
//when the physics asks itself what happens next, this is how it's represented
|
||||
#[derive(Debug)]
|
||||
pub enum InternalInstruction{
|
||||
// begin accepting touch updates
|
||||
OpenMultiCollision(model_physics::GigaTime),
|
||||
// mutliple touch updates
|
||||
CollisionStart(Collision),
|
||||
CollisionEnd(Collision),
|
||||
// confirm there will be no more touch updates and apply the transaction
|
||||
CloseMultiCollision,
|
||||
CollisionStart(Collision,model_physics::GigaTime),
|
||||
CollisionEnd(Collision,model_physics::GigaTime),
|
||||
StrafeTick,
|
||||
ReachWalkTargetVelocity,
|
||||
// Water,
|
||||
@@ -879,9 +874,6 @@ impl PhysicsState{
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
pub const fn body(&self)->&Body{
|
||||
&self.body
|
||||
}
|
||||
pub fn camera_body(&self)->Body{
|
||||
Body{
|
||||
position:self.body.position+self.style.camera_offset,
|
||||
@@ -957,8 +949,8 @@ pub struct PhysicsData{
|
||||
//cached calculations
|
||||
hitbox_mesh:HitboxMesh,
|
||||
}
|
||||
impl PhysicsData{
|
||||
pub fn empty()->Self{
|
||||
impl Default for PhysicsData{
|
||||
fn default()->Self{
|
||||
Self{
|
||||
bvh:bvh::BvhNode::empty(),
|
||||
models:Default::default(),
|
||||
@@ -966,7 +958,47 @@ impl PhysicsData{
|
||||
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 mut used_contact_attributes=Vec::new();
|
||||
let mut used_intersect_attributes=Vec::new();
|
||||
@@ -1093,50 +1125,11 @@ impl PhysicsData{
|
||||
(IntersectAttributesId::new(attr_id as u32),attr)
|
||||
).collect(),
|
||||
};
|
||||
self.bvh=bvh;
|
||||
self.models=models;
|
||||
self.modes=modes;
|
||||
//hitbox_mesh is unchanged
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2131,115 +2124,4 @@ mod test{
|
||||
Time::ZERO
|
||||
),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,
|
||||
directories,
|
||||
mouse_interpolator:MouseInterpolator::new(),
|
||||
geometry_shared:PhysicsData::empty(),
|
||||
geometry_shared:Default::default(),
|
||||
simulation,
|
||||
view_state:ViewState::Play,
|
||||
recording:Default::default(),
|
||||
@@ -184,7 +184,7 @@ impl Session{
|
||||
}
|
||||
fn change_map(&mut self,map:&strafesnet_common::map::CompleteMap){
|
||||
self.simulation.physics.clear();
|
||||
self.geometry_shared=PhysicsData::new(map);
|
||||
self.geometry_shared.generate_models(map);
|
||||
}
|
||||
pub fn get_frame_state(&self,time:SessionTime)->Option<FrameState>{
|
||||
match &self.view_state{
|
||||
|
||||
@@ -4,9 +4,6 @@ version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
glam = "0.30.0"
|
||||
strafesnet_common = { path = "../lib/common", registry = "strafesnet" }
|
||||
strafesnet_physics = { path = "../engine/physics", 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,8 +3,6 @@ mod util;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
#[cfg(test)]
|
||||
mod test_scenes;
|
||||
|
||||
use std::time::Instant;
|
||||
|
||||
@@ -31,8 +29,9 @@ fn run_replay()->Result<(),ReplayError>{
|
||||
let bot=strafesnet_snf::read_bot(data)?.read_all()?;
|
||||
|
||||
// create recording
|
||||
let mut physics_data=PhysicsData::default();
|
||||
println!("generating models..");
|
||||
let physics_data=PhysicsData::new(&map);
|
||||
physics_data.generate_models(&map);
|
||||
println!("simulating...");
|
||||
let mut physics=PhysicsState::default();
|
||||
for ins in bot.instructions{
|
||||
@@ -140,8 +139,9 @@ fn test_determinism()->Result<(),ReplayError>{
|
||||
let data=read_entire_file("../tools/bhop_maps/5692113331.snfm")?;
|
||||
let map=strafesnet_snf::read_map(data)?.into_complete_map()?;
|
||||
|
||||
let mut physics_data=PhysicsData::default();
|
||||
println!("generating models..");
|
||||
let physics_data=PhysicsData::new(&map);
|
||||
physics_data.generate_models(&map);
|
||||
|
||||
let (send,recv)=std::sync::mpsc::channel();
|
||||
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
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,8 +10,9 @@ fn physics_bug_2()->Result<(),ReplayError>{
|
||||
let map=strafesnet_snf::read_map(data)?.into_complete_map()?;
|
||||
|
||||
// create recording
|
||||
let mut physics_data=PhysicsData::default();
|
||||
println!("generating models..");
|
||||
let physics_data=PhysicsData::new(&map);
|
||||
physics_data.generate_models(&map);
|
||||
println!("simulating...");
|
||||
|
||||
//teleport to bug
|
||||
@@ -44,8 +45,9 @@ fn physics_bug_3()->Result<(),ReplayError>{
|
||||
let map=strafesnet_snf::read_map(data)?.into_complete_map()?;
|
||||
|
||||
// create recording
|
||||
let mut physics_data=PhysicsData::default();
|
||||
println!("generating models..");
|
||||
let physics_data=PhysicsData::new(&map);
|
||||
physics_data.generate_models(&map);
|
||||
println!("simulating...");
|
||||
|
||||
//teleport to bug
|
||||
|
||||
@@ -2,9 +2,7 @@ use crate::integer::{vec3,Planar64Vec3};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Aabb{
|
||||
// min is inclusive
|
||||
min:Planar64Vec3,
|
||||
// max is not inclusive
|
||||
max:Planar64Vec3,
|
||||
}
|
||||
|
||||
@@ -45,7 +43,7 @@ impl Aabb{
|
||||
}
|
||||
#[inline]
|
||||
pub fn contains(&self,point:Planar64Vec3)->bool{
|
||||
let bvec=self.min.le(point)&point.lt(self.max);
|
||||
let bvec=self.min.lt(point)&point.lt(self.max);
|
||||
bvec.all()
|
||||
}
|
||||
#[inline]
|
||||
|
||||
@@ -12,7 +12,7 @@ mod mesh;
|
||||
mod error;
|
||||
mod union;
|
||||
pub mod loader;
|
||||
pub mod primitives;
|
||||
mod primitives;
|
||||
|
||||
pub mod data{
|
||||
pub struct RobloxMeshBytes(Vec<u8>);
|
||||
|
||||
Reference in New Issue
Block a user