2024-01-30 06:37:05 +00:00
use std ::collections ::HashMap ;
use std ::collections ::HashSet ;
use crate ::model_physics ::{ self , PhysicsMesh , PhysicsMeshTransform , TransformedMesh , MeshQuery , PhysicsMeshId , PhysicsSubmeshId } ;
2024-01-30 00:19:41 +00:00
use strafesnet_common ::bvh ;
2024-01-30 06:37:05 +00:00
use strafesnet_common ::map ;
2024-01-30 00:19:41 +00:00
use strafesnet_common ::aabb ;
2024-01-30 06:37:05 +00:00
use strafesnet_common ::gameplay_modes ::{ self , StageId } ;
use strafesnet_common ::gameplay_attributes ::{ self , CollisionAttributesId } ;
use strafesnet_common ::model ::{ MeshId , ModelId } ;
use strafesnet_common ::gameplay_style ::{ self , StyleModifiers } ;
2024-01-30 00:19:41 +00:00
use strafesnet_common ::instruction ::{ self , InstructionEmitter , InstructionConsumer , TimedInstruction } ;
2024-01-30 06:37:05 +00:00
use strafesnet_common ::integer ::{ self , Time , Planar64 , Planar64Vec3 , Planar64Mat3 , Angle32 , Ratio64Vec2 } ;
use gameplay ::ModeState ;
2023-09-27 09:12:20 +00:00
2023-09-19 01:34:48 +00:00
#[ derive(Debug) ]
2023-09-09 00:34:26 +00:00
pub enum PhysicsInstruction {
2023-11-30 09:51:17 +00:00
CollisionStart ( Collision ) ,
CollisionEnd ( Collision ) ,
2023-09-09 00:00:50 +00:00
StrafeTick ,
2023-09-09 03:14:18 +00:00
ReachWalkTargetVelocity ,
2023-09-09 03:13:30 +00:00
// Water,
// Spawn(
// Option<SpawnId>,
// bool,//true = Trigger; false = teleport
// bool,//true = Force
// )
2023-09-20 00:53:29 +00:00
//InputInstructions conditionally activate RefreshWalkTarget (by doing what SetWalkTargetVelocity used to do and then flagging it)
2023-10-05 03:04:04 +00:00
Input ( PhysicsInputInstruction ) ,
}
#[ derive(Debug) ]
pub enum PhysicsInputInstruction {
ReplaceMouse ( MouseState , MouseState ) ,
SetNextMouse ( MouseState ) ,
SetMoveRight ( bool ) ,
SetMoveUp ( bool ) ,
2023-10-10 22:33:32 +00:00
SetMoveBack ( bool ) ,
SetMoveLeft ( bool ) ,
2023-10-05 03:04:04 +00:00
SetMoveDown ( bool ) ,
2023-10-10 22:33:32 +00:00
SetMoveForward ( bool ) ,
2023-10-05 03:04:04 +00:00
SetJump ( bool ) ,
SetZoom ( bool ) ,
Reset ,
2023-09-30 07:13:26 +00:00
Idle ,
//Idle: there were no input events, but the simulation is safe to advance to this timestep
//for interpolation / networking / playback reasons, most playback heads will always want
//to be 1 instruction ahead to generate the next state for interpolation.
2023-09-09 00:00:50 +00:00
}
2023-10-19 00:17:21 +00:00
#[ derive(Clone,Hash,Default) ]
2023-11-30 09:51:17 +00:00
pub struct Body {
pub position :Planar64Vec3 , //I64 where 2^32 = 1 u
pub velocity :Planar64Vec3 , //I64 where 2^32 = 1 u/s
pub acceleration :Planar64Vec3 , //I64 where 2^32 = 1 u/s/s
pub time :Time , //nanoseconds x xxxxD!
}
impl std ::ops ::Neg for Body {
type Output = Self ;
fn neg ( self ) ->Self ::Output {
Self {
position :self . position ,
velocity :- self . velocity ,
acceleration :self . acceleration ,
time :- self . time ,
}
}
2023-09-19 04:10:07 +00:00
}
2023-09-09 03:14:18 +00:00
2023-10-03 05:45:48 +00:00
//hey dumbass just use a delta
2023-10-05 03:04:04 +00:00
#[ derive(Clone,Debug) ]
pub struct MouseState {
pub pos : glam ::IVec2 ,
2023-09-27 09:12:20 +00:00
pub time :Time ,
2023-09-10 20:24:47 +00:00
}
2023-10-05 03:04:04 +00:00
impl Default for MouseState {
fn default ( ) -> Self {
2023-09-20 00:53:29 +00:00
Self {
2023-09-27 09:12:20 +00:00
time :Time ::ZERO ,
2023-10-05 03:04:04 +00:00
pos :glam ::IVec2 ::ZERO ,
2023-09-20 00:53:29 +00:00
}
}
2023-10-05 03:04:04 +00:00
}
impl MouseState {
2023-09-27 09:12:20 +00:00
pub fn lerp ( & self , target :& MouseState , time :Time ) ->glam ::IVec2 {
2023-10-05 03:04:04 +00:00
let m0 = self . pos . as_i64vec2 ( ) ;
let m1 = target . pos . as_i64vec2 ( ) ;
//these are deltas
2023-09-27 09:12:20 +00:00
let t1t = ( target . time - time ) . nanos ( ) ;
let tt0 = ( time - self . time ) . nanos ( ) ;
let dt = ( target . time - self . time ) . nanos ( ) ;
2023-10-05 03:04:04 +00:00
( ( m0 * t1t + m1 * tt0 ) / dt ) . as_ivec2 ( )
2023-09-10 20:24:47 +00:00
}
}
2023-11-30 09:51:17 +00:00
enum JumpDirection {
Exactly ( Planar64Vec3 ) ,
FromContactNormal ,
}
2023-10-17 07:17:54 +00:00
enum WalkEnum {
2023-09-19 06:36:14 +00:00
Reached ,
2023-10-17 07:17:54 +00:00
Transient ( WalkTarget ) ,
2023-09-19 06:36:14 +00:00
}
2023-10-17 07:17:54 +00:00
struct WalkTarget {
velocity :Planar64Vec3 ,
time :Time ,
2023-09-19 04:10:07 +00:00
}
2023-10-17 07:17:54 +00:00
struct WalkState {
2023-11-30 09:51:17 +00:00
jump_direction :JumpDirection ,
contact :ContactCollision ,
2023-10-17 07:17:54 +00:00
state :WalkEnum ,
}
impl WalkEnum {
//args going crazy
//(walk_enum,body.acceleration)=with_target_velocity();
2023-11-30 09:51:17 +00:00
fn with_target_velocity ( body :& Body , style :& StyleModifiers , velocity :Planar64Vec3 , normal :& Planar64Vec3 , speed :Planar64 , normal_accel :Planar64 ) ->( WalkEnum , Planar64Vec3 ) {
2023-10-17 07:17:54 +00:00
let mut target_diff = velocity - body . velocity ;
//remove normal component
target_diff - = normal . clone ( ) * ( normal . dot ( target_diff ) / normal . dot ( normal . clone ( ) ) ) ;
if target_diff = = Planar64Vec3 ::ZERO {
2023-11-30 09:51:17 +00:00
( WalkEnum ::Reached , Planar64Vec3 ::ZERO )
2023-10-17 07:17:54 +00:00
} else {
//normal friction acceleration is clippedAcceleration.dot(normal)*friction
2023-10-17 23:18:55 +00:00
let diff_len = target_diff . length ( ) ;
2023-11-30 09:51:17 +00:00
let friction = if diff_len < speed {
2023-10-17 23:18:55 +00:00
style . static_friction
} else {
style . kinetic_friction
} ;
2023-11-30 09:51:17 +00:00
let accel = style . walk_accel . min ( normal_accel * friction ) ;
2023-10-17 23:18:55 +00:00
let time_delta = diff_len / accel ;
2023-11-30 09:51:17 +00:00
let a = target_diff . with_length ( accel ) ;
2023-10-17 07:17:54 +00:00
( WalkEnum ::Transient ( WalkTarget { velocity , time :body . time + Time ::from ( time_delta ) } ) , a )
2023-09-19 04:10:07 +00:00
}
}
}
2023-10-17 07:17:54 +00:00
impl WalkState {
2023-11-30 09:51:17 +00:00
fn ground ( body :& Body , style :& StyleModifiers , gravity :Planar64Vec3 , velocity :Planar64Vec3 , contact :ContactCollision , normal :& Planar64Vec3 ) ->( Self , Planar64Vec3 ) {
let ( walk_enum , a ) = WalkEnum ::with_target_velocity ( body , style , velocity , & Planar64Vec3 ::Y , style . walk_speed , - normal . dot ( gravity ) ) ;
2023-10-17 07:17:54 +00:00
( Self {
state :walk_enum ,
2023-11-30 09:51:17 +00:00
contact ,
jump_direction :JumpDirection ::Exactly ( Planar64Vec3 ::Y ) ,
2023-10-17 07:17:54 +00:00
} , a )
}
2023-11-30 09:51:17 +00:00
fn ladder ( body :& Body , style :& StyleModifiers , gravity :Planar64Vec3 , velocity :Planar64Vec3 , contact :ContactCollision , normal :& Planar64Vec3 ) ->( Self , Planar64Vec3 ) {
let ( walk_enum , a ) = WalkEnum ::with_target_velocity ( body , style , velocity , normal , style . ladder_speed , style . ladder_accel ) ;
2023-10-17 07:17:54 +00:00
( Self {
state :walk_enum ,
2023-11-30 09:51:17 +00:00
contact ,
jump_direction :JumpDirection ::FromContactNormal ,
2023-10-17 07:17:54 +00:00
} , a )
}
}
2023-09-19 04:10:07 +00:00
2023-11-30 09:51:17 +00:00
#[ derive(Default) ]
2023-10-18 23:21:06 +00:00
struct PhysicsModels {
2024-01-30 06:37:05 +00:00
meshes :HashMap < PhysicsMeshId , PhysicsMesh > ,
models :HashMap < PhysicsModelId , PhysicsModel > ,
2023-11-30 09:51:17 +00:00
//separate models into Contacting and Intersecting?
//wrap model id with ContactingModelId and IntersectingModelId
//attributes can be split into contacting and intersecting (this also saves a bit of memory)
//can go even further and deduplicate General attributes separately, reconstructing it when queried
2024-01-30 06:37:05 +00:00
attributes :HashMap < PhysicsAttributesId , PhysicsCollisionAttributes > ,
2023-10-18 23:21:06 +00:00
}
impl PhysicsModels {
fn clear ( & mut self ) {
2023-11-30 09:51:17 +00:00
self . meshes . clear ( ) ;
2023-10-18 23:21:06 +00:00
self . models . clear ( ) ;
2023-11-30 09:51:17 +00:00
self . attributes . clear ( ) ;
}
//TODO: "statically" verify the maps don't refer to any nonexistant data, if they do delete the references.
//then I can make these getter functions unchecked.
2024-01-30 06:37:05 +00:00
fn mesh ( & self , convex_mesh_id :ConvexMeshId ) ->TransformedMesh {
let model = & self . models [ & convex_mesh_id . model_id ] ;
2023-11-30 09:51:17 +00:00
TransformedMesh ::new (
2024-01-30 06:37:05 +00:00
self . meshes [ & model . mesh_id ] . submesh_view ( convex_mesh_id . submesh_id ) ,
& model . transform
2023-11-30 09:51:17 +00:00
)
}
2024-01-30 06:37:05 +00:00
fn model ( & self , model_id :PhysicsModelId ) ->& PhysicsModel {
& self . models [ & model_id ]
2023-11-30 09:51:17 +00:00
}
2024-01-30 06:37:05 +00:00
fn attr ( & self , model_id :PhysicsModelId ) ->& PhysicsCollisionAttributes {
& self . attributes [ & self . models [ & model_id ] . attr_id ]
2023-10-18 23:21:06 +00:00
}
}
2023-10-05 03:04:04 +00:00
#[ derive(Clone) ]
2023-10-31 05:25:03 +00:00
pub struct PhysicsCamera {
2023-09-27 09:12:20 +00:00
//punch: Planar64Vec3,
//punch_velocity: Planar64Vec3,
sensitivity :Ratio64Vec2 , //dots to Angle32 ratios
mouse :MouseState , //last seen absolute mouse pos
clamped_mouse_pos :glam ::IVec2 , //angles are calculated from this cumulative value
//angle limits could be an enum + struct that defines whether it's limited and selects clamp or wrap depending
// enum AngleLimit{
// Unlimited,
// Limited{lower:Angle32,upper:Angle32},
// }
//pitch_limit:AngleLimit,
//yaw_limit:AngleLimit,
2023-09-20 00:53:29 +00:00
}
2024-01-30 06:37:05 +00:00
impl PhysicsCamera {
const ANGLE_PITCH_LOWER_LIMIT :Angle32 = Angle32 ::NEG_FRAC_PI_2 ;
const ANGLE_PITCH_UPPER_LIMIT :Angle32 = Angle32 ::FRAC_PI_2 ;
2023-09-27 09:12:20 +00:00
pub fn move_mouse ( & mut self , mouse_pos :glam ::IVec2 ) {
2024-01-30 06:37:05 +00:00
let mut unclamped_mouse_pos = mouse_pos - self . mouse . pos + self . clamped_mouse_pos ;
2023-09-27 09:12:20 +00:00
unclamped_mouse_pos . y = unclamped_mouse_pos . y . clamp (
2024-01-30 06:37:05 +00:00
self . sensitivity . y . rhs_div_int ( Self ::ANGLE_PITCH_LOWER_LIMIT . get ( ) as i64 ) as i32 ,
self . sensitivity . y . rhs_div_int ( Self ::ANGLE_PITCH_UPPER_LIMIT . get ( ) as i64 ) as i32 ,
2023-09-27 09:12:20 +00:00
) ;
self . clamped_mouse_pos = unclamped_mouse_pos ;
2023-09-20 00:53:29 +00:00
}
2023-09-27 09:12:20 +00:00
pub fn simulate_move_angles ( & self , mouse_pos :glam ::IVec2 ) ->glam ::Vec2 {
let a = - self . sensitivity . mul_int ( ( mouse_pos - self . mouse . pos + self . clamped_mouse_pos ) . as_i64vec2 ( ) ) ;
let ax = Angle32 ::wrap_from_i64 ( a . x ) ;
let ay = Angle32 ::clamp_from_i64 ( a . y )
//clamp to actual vertical cam limit
2024-01-30 06:37:05 +00:00
. clamp ( Self ::ANGLE_PITCH_LOWER_LIMIT , Self ::ANGLE_PITCH_UPPER_LIMIT ) ;
2023-09-27 09:12:20 +00:00
return glam ::vec2 ( ax . into ( ) , ay . into ( ) ) ;
}
2023-10-17 06:20:38 +00:00
fn simulate_move_rotation ( & self , mouse_pos :glam ::IVec2 ) ->Planar64Mat3 {
let a = - self . sensitivity . mul_int ( ( mouse_pos - self . mouse . pos + self . clamped_mouse_pos ) . as_i64vec2 ( ) ) ;
let ax = Angle32 ::wrap_from_i64 ( a . x ) ;
let ay = Angle32 ::clamp_from_i64 ( a . y )
//clamp to actual vertical cam limit
2024-01-30 06:37:05 +00:00
. clamp ( Self ::ANGLE_PITCH_LOWER_LIMIT , Self ::ANGLE_PITCH_UPPER_LIMIT ) ;
2023-10-17 06:20:38 +00:00
Planar64Mat3 ::from_rotation_yx ( ax , ay )
}
2023-09-27 09:12:20 +00:00
fn simulate_move_rotation_y ( & self , mouse_pos_x :i32 ) ->Planar64Mat3 {
let ax = - self . sensitivity . x . mul_int ( ( mouse_pos_x - self . mouse . pos . x + self . clamped_mouse_pos . x ) as i64 ) ;
Planar64Mat3 ::from_rotation_y ( Angle32 ::wrap_from_i64 ( ax ) )
2023-09-20 00:53:29 +00:00
}
}
2023-10-19 00:17:21 +00:00
impl std ::default ::Default for PhysicsCamera {
fn default ( ) ->Self {
Self {
sensitivity :Ratio64Vec2 ::ONE * 200_000 ,
mouse :MouseState ::default ( ) , //t=0 does not cause divide by zero because it's immediately replaced
clamped_mouse_pos :glam ::IVec2 ::ZERO ,
}
}
}
2024-01-30 06:37:05 +00:00
mod gameplay {
use super ::{ gameplay_modes , HashSet , HashMap , ModelId } ;
pub struct ModeState {
mode_id :gameplay_modes ::ModeId ,
stage_id :gameplay_modes ::StageId ,
next_ordered_checkpoint_id :gameplay_modes ::CheckpointId , //which OrderedCheckpoint model_id you must pass next (if 0 you haven't passed OrderedCheckpoint0)
unordered_checkpoints :HashSet < ModelId > ,
jump_counts :HashMap < ModelId , u32 > , //model_id -> jump count
}
impl ModeState {
pub const fn get_mode_id ( & self ) ->gameplay_modes ::ModeId {
self . mode_id
}
pub const fn get_stage_id ( & self ) ->gameplay_modes ::StageId {
self . stage_id
}
pub const fn get_next_ordered_checkpoint_id ( & self ) ->gameplay_modes ::CheckpointId {
self . next_ordered_checkpoint_id
}
pub fn get_jump_count ( & self , model_id :ModelId ) ->Option < u32 > {
self . jump_counts . get ( & model_id ) . copied ( )
}
pub const fn ordered_checkpoint_count ( & self ) ->u32 {
self . next_ordered_checkpoint_id . get ( )
}
pub fn unordered_checkpoint_count ( & self ) ->u32 {
self . unordered_checkpoints . len ( ) as u32
}
pub fn set_mode_id ( & mut self , mode_id :gameplay_modes ::ModeId ) {
self . clear ( ) ;
self . mode_id = mode_id ;
}
pub fn set_stage_id ( & mut self , stage_id :gameplay_modes ::StageId ) {
self . clear_checkpoints ( ) ;
self . stage_id = stage_id ;
}
pub fn accumulate_ordered_checkpoint ( & mut self , stage :& gameplay_modes ::Stage , model_id :ModelId ) {
if stage . is_next_ordered_checkpoint ( self . get_next_ordered_checkpoint_id ( ) , model_id ) {
self . next_ordered_checkpoint_id = gameplay_modes ::CheckpointId ::new ( self . next_ordered_checkpoint_id . get ( ) + 1 ) ;
}
}
pub fn accumulate_unordered_checkpoint ( & mut self , stage :& gameplay_modes ::Stage , model_id :ModelId ) {
if stage . is_unordered_checkpoint ( model_id ) {
self . unordered_checkpoints . insert ( model_id ) ;
}
}
pub fn clear ( & mut self ) {
self . clear_jump_counts ( ) ;
self . clear_checkpoints ( ) ;
}
pub fn clear_jump_counts ( & mut self ) {
self . jump_counts . clear ( ) ;
}
pub fn clear_checkpoints ( & mut self ) {
self . next_ordered_checkpoint_id = gameplay_modes ::CheckpointId ::FIRST ;
self . unordered_checkpoints . clear ( ) ;
}
}
impl std ::default ::Default for ModeState {
fn default ( ) ->Self {
Self {
mode_id :gameplay_modes ::ModeId ::MAIN ,
stage_id :gameplay_modes ::StageId ::FIRST ,
next_ordered_checkpoint_id :gameplay_modes ::CheckpointId ::FIRST ,
unordered_checkpoints :HashSet ::new ( ) ,
jump_counts :HashMap ::new ( ) ,
}
2023-10-03 05:45:20 +00:00
}
}
}
2023-10-17 23:18:55 +00:00
struct WorldState { }
2024-01-30 06:37:05 +00:00
struct HitboxMesh {
2023-11-30 09:51:17 +00:00
halfsize :Planar64Vec3 ,
mesh :PhysicsMesh ,
2024-01-30 06:37:05 +00:00
transform :PhysicsMeshTransform ,
2023-11-30 09:51:17 +00:00
}
2024-01-30 06:37:05 +00:00
impl HitboxMesh {
2024-01-30 00:19:41 +00:00
fn new ( mesh :PhysicsMesh , transform :integer ::Planar64Affine3 ) ->Self {
2023-11-30 09:51:17 +00:00
//calculate extents
2024-01-30 00:19:41 +00:00
let mut aabb = aabb ::Aabb ::default ( ) ;
2024-01-30 06:37:05 +00:00
let transform = PhysicsMeshTransform ::new ( transform ) ;
let transformed_mesh = TransformedMesh ::new ( mesh . complete_mesh_view ( ) , & transform ) ;
for vert in transformed_mesh . verts ( ) {
aabb . grow ( vert ) ;
2023-11-30 09:51:17 +00:00
}
Self {
halfsize :aabb . size ( ) / 2 ,
mesh ,
transform ,
}
}
#[ inline ]
fn transformed_mesh ( & self ) ->TransformedMesh {
2024-01-30 06:37:05 +00:00
TransformedMesh ::new ( self . mesh . complete_mesh_view ( ) , & self . transform )
2023-11-30 09:51:17 +00:00
}
}
2024-01-30 06:37:05 +00:00
trait StyleHelper {
fn get_control ( & self , control :u32 , controls :u32 ) ->bool ;
fn allow_strafe ( & self , controls :u32 ) ->bool ;
fn get_control_dir ( & self , controls :u32 ) ->Planar64Vec3 ;
//fn get_jump_time(&self)->Planar64;
//fn get_jump_height(&self)->Planar64;
//fn get_jump_energy(&self)->Planar64;
fn get_jump_deltav ( & self ) ->Planar64 ;
fn get_walk_target_velocity ( & self , camera :& PhysicsCamera , controls :u32 , next_mouse :& MouseState , time :Time , normal :& Planar64Vec3 ) ->Planar64Vec3 ;
fn get_ladder_target_velocity ( & self , camera :& PhysicsCamera , controls :u32 , next_mouse :& MouseState , time :Time , normal :& Planar64Vec3 ) ->Planar64Vec3 ;
fn get_propulsion_control_dir ( & self , camera :& PhysicsCamera , controls :u32 , next_mouse :& MouseState , time :Time ) ->Planar64Vec3 ;
fn calculate_mesh ( & self ) ->HitboxMesh ;
}
impl StyleHelper for StyleModifiers {
2023-10-03 23:52:45 +00:00
fn get_control ( & self , control :u32 , controls :u32 ) ->bool {
2023-10-05 03:04:04 +00:00
controls & self . controls_mask & control = = control
2023-10-03 23:52:45 +00:00
}
2023-10-31 21:11:39 +00:00
fn allow_strafe ( & self , controls :u32 ) ->bool {
//disable strafing according to strafe settings
2024-01-30 06:37:05 +00:00
self . strafe . as_ref ( ) . is_some_and ( | s | s . mask ( controls ) )
2023-10-31 21:11:39 +00:00
}
2023-09-27 09:12:20 +00:00
fn get_control_dir ( & self , controls :u32 ) ->Planar64Vec3 {
2023-10-03 23:52:45 +00:00
//don't get fancy just do it
2023-09-27 09:12:20 +00:00
let mut control_dir :Planar64Vec3 = Planar64Vec3 ::ZERO ;
2023-10-03 23:52:45 +00:00
//Apply mask after held check so you can require non-allowed keys to be held for some reason
let controls = controls & self . controls_mask ;
if controls & Self ::CONTROL_MOVEFORWARD = = Self ::CONTROL_MOVEFORWARD {
control_dir + = Self ::FORWARD_DIR ;
}
if controls & Self ::CONTROL_MOVEBACK = = Self ::CONTROL_MOVEBACK {
2023-09-27 09:12:20 +00:00
control_dir - = Self ::FORWARD_DIR ;
2023-10-03 23:52:45 +00:00
}
if controls & Self ::CONTROL_MOVELEFT = = Self ::CONTROL_MOVELEFT {
2023-09-27 09:12:20 +00:00
control_dir - = Self ::RIGHT_DIR ;
2023-10-03 23:52:45 +00:00
}
if controls & Self ::CONTROL_MOVERIGHT = = Self ::CONTROL_MOVERIGHT {
control_dir + = Self ::RIGHT_DIR ;
}
if controls & Self ::CONTROL_MOVEUP = = Self ::CONTROL_MOVEUP {
control_dir + = Self ::UP_DIR ;
}
if controls & Self ::CONTROL_MOVEDOWN = = Self ::CONTROL_MOVEDOWN {
2023-09-27 09:12:20 +00:00
control_dir - = Self ::UP_DIR ;
2023-10-03 23:52:45 +00:00
}
return control_dir
}
2023-10-14 21:51:13 +00:00
2023-10-17 23:18:55 +00:00
//fn get_jump_time(&self)->Planar64
//fn get_jump_height(&self)->Planar64
//fn get_jump_energy(&self)->Planar64
fn get_jump_deltav ( & self ) ->Planar64 {
match & self . jump_impulse {
2024-01-30 06:37:05 +00:00
& gameplay_style ::JumpImpulse ::FromTime ( time ) = > self . gravity . length ( ) * ( time / 2 ) ,
& gameplay_style ::JumpImpulse ::FromHeight ( height ) = > ( self . gravity . length ( ) * height * 2 ) . sqrt ( ) ,
& gameplay_style ::JumpImpulse ::FromDeltaV ( deltav ) = > deltav ,
& gameplay_style ::JumpImpulse ::FromEnergy ( energy ) = > ( energy * 2 / self . mass ) . sqrt ( ) ,
2023-10-17 23:18:55 +00:00
}
2023-10-17 07:17:54 +00:00
}
2023-11-30 09:51:17 +00:00
fn get_walk_target_velocity ( & self , camera :& PhysicsCamera , controls :u32 , next_mouse :& MouseState , time :Time , normal :& Planar64Vec3 ) ->Planar64Vec3 {
let mut control_dir = self . get_control_dir ( controls ) ;
if control_dir = = Planar64Vec3 ::ZERO {
return control_dir ;
}
2023-10-17 07:17:54 +00:00
let camera_mat = camera . simulate_move_rotation_y ( camera . mouse . lerp ( & next_mouse , time ) . x ) ;
2023-11-30 09:51:17 +00:00
control_dir = camera_mat * control_dir ;
let n = normal . length ( ) ;
let m = control_dir . length ( ) ;
let d = normal . dot ( control_dir ) / m ;
if d < n {
let cr = normal . cross ( control_dir ) ;
if cr = = Planar64Vec3 ::ZERO {
Planar64Vec3 ::ZERO
} else {
cr . cross ( * normal ) * ( self . walk_speed / ( n * ( n * n - d * d ) . sqrt ( ) * m ) )
}
} else {
Planar64Vec3 ::ZERO
}
2023-10-17 23:18:55 +00:00
}
2023-11-30 09:51:17 +00:00
fn get_ladder_target_velocity ( & self , camera :& PhysicsCamera , controls :u32 , next_mouse :& MouseState , time :Time , normal :& Planar64Vec3 ) ->Planar64Vec3 {
let mut control_dir = self . get_control_dir ( controls ) ;
if control_dir = = Planar64Vec3 ::ZERO {
return control_dir ;
}
2023-10-17 23:18:55 +00:00
let camera_mat = camera . simulate_move_rotation ( camera . mouse . lerp ( & next_mouse , time ) ) ;
2023-11-30 09:51:17 +00:00
control_dir = camera_mat * control_dir ;
let n = normal . length ( ) ;
let m = control_dir . length ( ) ;
let mut d = normal . dot ( control_dir ) / m ;
if d < - self . ladder_dot * n {
control_dir = Planar64Vec3 ::Y * m ;
d = normal . y ( ) ;
} else if self . ladder_dot * n < d {
control_dir = Planar64Vec3 ::NEG_Y * m ;
d = - normal . y ( ) ;
}
//n=d if you are standing on top of a ladder and press E.
//two fixes:
//- ladder movement is not allowed on walkable surfaces
//- fix the underlying issue
if d . get ( ) . unsigned_abs ( ) < n . get ( ) . unsigned_abs ( ) {
let cr = normal . cross ( control_dir ) ;
if cr = = Planar64Vec3 ::ZERO {
Planar64Vec3 ::ZERO
} else {
cr . cross ( * normal ) * ( self . ladder_speed / ( n * ( n * n - d * d ) . sqrt ( ) ) )
}
} else {
Planar64Vec3 ::ZERO
}
2023-10-17 07:17:54 +00:00
}
2023-10-20 20:47:26 +00:00
fn get_propulsion_control_dir ( & self , camera :& PhysicsCamera , controls :u32 , next_mouse :& MouseState , time :Time ) ->Planar64Vec3 {
2023-10-17 07:17:54 +00:00
let camera_mat = camera . simulate_move_rotation ( camera . mouse . lerp ( & next_mouse , time ) ) ;
2023-10-20 20:47:26 +00:00
camera_mat * self . get_control_dir ( controls )
2023-10-14 21:51:13 +00:00
}
2024-01-30 06:37:05 +00:00
fn calculate_mesh ( & self ) ->HitboxMesh {
let mesh = match self . hitbox . mesh {
gameplay_style ::HitboxMesh ::Box = > PhysicsMesh ::unit_cube ( ) ,
gameplay_style ::HitboxMesh ::Cylinder = > PhysicsMesh ::unit_cylinder ( ) ,
} ;
let transform = integer ::Planar64Affine3 ::new ( Planar64Mat3 ::from_diagonal ( self . hitbox . halfsize ) , Planar64Vec3 ::ZERO ) ;
HitboxMesh ::new ( mesh , transform )
2023-11-30 09:51:17 +00:00
}
2023-10-03 23:52:45 +00:00
}
2023-10-03 05:45:20 +00:00
2023-10-13 02:44:46 +00:00
enum MoveState {
Air ,
Walk ( WalkState ) ,
Water ,
Ladder ( WalkState ) ,
}
2023-10-19 00:17:21 +00:00
#[ derive(Clone,Default) ]
2023-10-05 03:04:04 +00:00
pub struct PhysicsOutputState {
body :Body ,
2023-10-31 05:25:03 +00:00
camera :PhysicsCamera ,
camera_offset :Planar64Vec3 ,
2023-10-05 03:04:04 +00:00
}
impl PhysicsOutputState {
2023-10-19 00:17:21 +00:00
pub fn extrapolate ( & self , mouse_pos :glam ::IVec2 , time :Time ) ->( glam ::Vec3 , glam ::Vec2 ) {
2023-10-31 05:25:03 +00:00
( ( self . body . extrapolated_position ( time ) + self . camera_offset ) . into ( ) , self . camera . simulate_move_angles ( mouse_pos ) )
2023-10-05 03:04:04 +00:00
}
}
2023-09-08 18:33:16 +00:00
2023-11-30 09:51:17 +00:00
#[ derive(Clone,Hash,Eq,PartialEq) ]
2023-10-03 05:45:20 +00:00
enum PhysicsCollisionAttributes {
Contact { //track whether you are contacting the object
2024-01-30 06:37:05 +00:00
contacting :gameplay_attributes ::ContactingAttributes ,
general :gameplay_attributes ::GeneralAttributes ,
2023-10-03 05:45:20 +00:00
} ,
Intersect { //track whether you are intersecting the object
2024-01-30 06:37:05 +00:00
intersecting :gameplay_attributes ::IntersectingAttributes ,
general :gameplay_attributes ::GeneralAttributes ,
2023-10-03 05:45:20 +00:00
} ,
}
2023-11-30 09:51:17 +00:00
struct NonPhysicsError ;
2024-01-30 06:37:05 +00:00
impl TryFrom < & gameplay_attributes ::CollisionAttributes > for PhysicsCollisionAttributes {
2023-11-30 09:51:17 +00:00
type Error = NonPhysicsError ;
2024-01-30 06:37:05 +00:00
fn try_from ( value :& gameplay_attributes ::CollisionAttributes ) ->Result < Self , Self ::Error > {
2023-11-30 09:51:17 +00:00
match value {
2024-01-30 06:37:05 +00:00
gameplay_attributes ::CollisionAttributes ::Decoration = > Err ( NonPhysicsError ) ,
gameplay_attributes ::CollisionAttributes ::Contact { contacting , general } = > Ok ( Self ::Contact { contacting :contacting . clone ( ) , general :general . clone ( ) } ) ,
gameplay_attributes ::CollisionAttributes ::Intersect { intersecting , general } = > Ok ( Self ::Intersect { intersecting :intersecting . clone ( ) , general :general . clone ( ) } ) ,
2023-11-30 09:51:17 +00:00
}
}
}
2024-01-30 06:37:05 +00:00
#[ derive(Clone,Copy,Hash,id::Id,Eq,PartialEq) ]
struct PhysicsAttributesId ( u32 ) ;
impl Into < CollisionAttributesId > for PhysicsAttributesId {
fn into ( self ) ->CollisionAttributesId {
CollisionAttributesId ::new ( self . 0 )
}
}
impl From < CollisionAttributesId > for PhysicsAttributesId {
fn from ( value :CollisionAttributesId ) ->Self {
Self ::new ( value . get ( ) )
}
}
//unique physics meshes indexed by this
#[ derive(Debug,Default,Clone,Copy,Eq,Hash,PartialEq) ]
struct ConvexMeshId {
model_id :PhysicsModelId ,
submesh_id :PhysicsSubmeshId ,
}
#[ derive(Debug,Default,Clone,Copy,Hash,id::Id,Eq,PartialEq) ]
struct PhysicsModelId ( u32 ) ;
impl Into < ModelId > for PhysicsModelId {
fn into ( self ) ->ModelId {
ModelId ::new ( self . 0 )
}
}
impl From < ModelId > for PhysicsModelId {
fn from ( value :ModelId ) ->Self {
Self ::new ( value . get ( ) )
}
}
2023-11-30 09:51:17 +00:00
pub struct PhysicsModel {
2023-09-08 22:54:43 +00:00
//A model is a thing that has a hitbox. can be represented by a list of TreyMesh-es
//in this iteration, all it needs is extents.
2024-01-30 06:37:05 +00:00
mesh_id :PhysicsMeshId ,
//put these up on the Model (data normalization)
attr_id :PhysicsAttributesId ,
transform :PhysicsMeshTransform ,
2023-09-08 22:54:43 +00:00
}
2023-11-30 09:51:17 +00:00
impl PhysicsModel {
2024-01-30 06:37:05 +00:00
const fn new ( mesh_id :PhysicsMeshId , attr_id :PhysicsAttributesId , transform :PhysicsMeshTransform ) ->Self {
2023-09-28 23:15:12 +00:00
Self {
2023-11-30 09:51:17 +00:00
mesh_id ,
attr_id ,
transform ,
2023-09-28 23:15:12 +00:00
}
2023-09-08 23:14:01 +00:00
}
2023-09-08 22:54:43 +00:00
}
2023-09-19 01:34:48 +00:00
#[ derive(Debug,Clone,Eq,Hash,PartialEq) ]
2023-11-30 09:51:17 +00:00
struct ContactCollision {
2024-01-30 06:37:05 +00:00
face_id :model_physics ::MinkowskiFace ,
convex_mesh_id :ConvexMeshId ,
2023-09-08 22:54:22 +00:00
}
2023-11-30 09:51:17 +00:00
#[ derive(Debug,Clone,Eq,Hash,PartialEq) ]
struct IntersectCollision {
2024-01-30 06:37:05 +00:00
convex_mesh_id :ConvexMeshId ,
2023-11-30 09:51:17 +00:00
}
#[ derive(Debug,Clone,Eq,Hash,PartialEq) ]
enum Collision {
Contact ( ContactCollision ) ,
Intersect ( IntersectCollision ) ,
}
impl Collision {
2024-01-30 06:37:05 +00:00
const fn convex_mesh_id ( & self ) ->ConvexMeshId {
2023-11-30 09:51:17 +00:00
match self {
2024-01-30 06:37:05 +00:00
& Collision ::Contact ( ContactCollision { convex_mesh_id , face_id :_ } )
| & Collision ::Intersect ( IntersectCollision { convex_mesh_id } ) = > convex_mesh_id ,
2023-11-30 09:51:17 +00:00
}
2023-10-04 00:20:35 +00:00
}
2024-01-30 06:37:05 +00:00
const fn face_id ( & self ) ->Option < model_physics ::MinkowskiFace > {
2023-11-30 09:51:17 +00:00
match self {
2024-01-30 06:37:05 +00:00
& Collision ::Contact ( ContactCollision { convex_mesh_id :_ , face_id } ) = > Some ( face_id ) ,
& Collision ::Intersect ( IntersectCollision { convex_mesh_id :_ } ) = > None ,
2023-11-30 09:51:17 +00:00
}
2023-09-08 22:54:22 +00:00
}
}
2023-11-30 09:51:17 +00:00
#[ derive(Default) ]
2023-10-13 02:44:46 +00:00
struct TouchingState {
2024-01-30 06:37:05 +00:00
contacts :HashSet ::< ContactCollision > ,
intersects :HashSet ::< IntersectCollision > ,
2023-10-13 02:44:46 +00:00
}
impl TouchingState {
fn clear ( & mut self ) {
self . contacts . clear ( ) ;
self . intersects . clear ( ) ;
}
2023-11-30 09:51:17 +00:00
fn insert ( & mut self , collision :Collision ) ->bool {
match collision {
Collision ::Contact ( collision ) = > self . contacts . insert ( collision ) ,
Collision ::Intersect ( collision ) = > self . intersects . insert ( collision ) ,
}
2023-10-13 02:44:46 +00:00
}
2023-11-30 09:51:17 +00:00
fn remove ( & mut self , collision :& Collision ) ->bool {
match collision {
Collision ::Contact ( collision ) = > self . contacts . remove ( collision ) ,
Collision ::Intersect ( collision ) = > self . intersects . remove ( collision ) ,
}
2023-10-13 02:44:46 +00:00
}
2023-11-30 09:51:17 +00:00
fn base_acceleration ( & self , models :& PhysicsModels , style :& StyleModifiers , camera :& PhysicsCamera , controls :u32 , next_mouse :& MouseState , time :Time ) ->Planar64Vec3 {
let mut a = style . gravity ;
if let Some ( rocket_force ) = style . rocket_force {
a + = style . get_propulsion_control_dir ( camera , controls , next_mouse , time ) * rocket_force ;
}
//add accelerators
for contact in & self . contacts {
2024-01-30 06:37:05 +00:00
match models . attr ( contact . convex_mesh_id . model_id ) {
2023-11-30 09:51:17 +00:00
PhysicsCollisionAttributes ::Contact { contacting , general } = > {
match & general . accelerator {
Some ( accelerator ) = > a + = accelerator . acceleration ,
None = > ( ) ,
}
} ,
_ = > panic! ( " impossible touching state " ) ,
}
}
for intersect in & self . intersects {
2024-01-30 06:37:05 +00:00
match models . attr ( intersect . convex_mesh_id . model_id ) {
2023-11-30 09:51:17 +00:00
PhysicsCollisionAttributes ::Intersect { intersecting , general } = > {
match & general . accelerator {
Some ( accelerator ) = > a + = accelerator . acceleration ,
None = > ( ) ,
}
} ,
_ = > panic! ( " impossible touching state " ) ,
}
}
//add water../?
a
}
2024-01-30 06:37:05 +00:00
fn constrain_velocity ( & self , models :& PhysicsModels , hitbox_mesh :& HitboxMesh , velocity :& mut Planar64Vec3 ) {
2023-11-30 09:51:17 +00:00
//TODO: trey push solve
for contact in & self . contacts {
2024-01-30 06:37:05 +00:00
let n = contact_normal ( models , hitbox_mesh , contact ) ;
2023-11-30 09:51:17 +00:00
let d = n . dot128 ( * velocity ) ;
if d < 0 {
* velocity - = n * Planar64 ::raw ( ( ( d < < 32 ) / n . dot128 ( n ) ) as i64 ) ;
2023-10-13 02:44:46 +00:00
}
}
}
2024-01-30 06:37:05 +00:00
fn constrain_acceleration ( & self , models :& PhysicsModels , hitbox_mesh :& HitboxMesh , acceleration :& mut Planar64Vec3 ) {
2023-11-30 09:51:17 +00:00
//TODO: trey push solve
for contact in & self . contacts {
2024-01-30 06:37:05 +00:00
let n = contact_normal ( models , hitbox_mesh , contact ) ;
2023-11-30 09:51:17 +00:00
let d = n . dot128 ( * acceleration ) ;
if d < 0 {
* acceleration - = n * Planar64 ::raw ( ( ( d < < 32 ) / n . dot128 ( n ) ) as i64 ) ;
2023-10-13 02:44:46 +00:00
}
}
}
2024-01-30 06:37:05 +00:00
fn get_move_state ( & self , body :& Body , models :& PhysicsModels , style :& StyleModifiers , hitbox_mesh :& HitboxMesh , camera :& PhysicsCamera , controls :u32 , next_mouse :& MouseState , time :Time ) ->( MoveState , Planar64Vec3 ) {
2023-11-30 09:51:17 +00:00
//check current move conditions and use heuristics to determine
//which ladder to climb on, which ground to walk on, etc
//collect move state affecting objects from contacts (accelerator,water,ladder,ground)
let gravity = self . base_acceleration ( models , style , camera , controls , next_mouse , time ) ;
let mut move_state = MoveState ::Air ;
let mut a = gravity ;
for contact in & self . contacts {
2024-01-30 06:37:05 +00:00
match models . attr ( contact . convex_mesh_id . model_id ) {
2023-11-30 09:51:17 +00:00
PhysicsCollisionAttributes ::Contact { contacting , general } = > {
2024-01-30 06:37:05 +00:00
let normal = contact_normal ( models , hitbox_mesh , contact ) ;
2023-11-30 09:51:17 +00:00
match & contacting . contact_behaviour {
2024-01-30 06:37:05 +00:00
Some ( gameplay_attributes ::ContactingBehaviour ::Ladder ( _ ) ) = > {
2023-11-30 09:51:17 +00:00
//ladder walkstate
let mut target_velocity = style . get_ladder_target_velocity ( camera , controls , next_mouse , time , & normal ) ;
2024-01-30 06:37:05 +00:00
self . constrain_velocity ( models , hitbox_mesh , & mut target_velocity ) ;
2023-11-30 09:51:17 +00:00
let ( walk_state , mut acceleration ) = WalkState ::ladder ( body , style , gravity , target_velocity , contact . clone ( ) , & normal ) ;
move_state = MoveState ::Ladder ( walk_state ) ;
2024-01-30 06:37:05 +00:00
self . constrain_acceleration ( models , hitbox_mesh , & mut acceleration ) ;
2023-11-30 09:51:17 +00:00
a = acceleration ;
} ,
None = > if style . surf_slope . map_or ( true , | s | normal . walkable ( s , Planar64Vec3 ::Y ) ) {
//check ground
let mut target_velocity = style . get_walk_target_velocity ( camera , controls , next_mouse , time , & normal ) ;
2024-01-30 06:37:05 +00:00
self . constrain_velocity ( models , hitbox_mesh , & mut target_velocity ) ;
2023-11-30 09:51:17 +00:00
let ( walk_state , mut acceleration ) = WalkState ::ground ( body , style , gravity , target_velocity , contact . clone ( ) , & normal ) ;
move_state = MoveState ::Walk ( walk_state ) ;
2024-01-30 06:37:05 +00:00
self . constrain_acceleration ( models , hitbox_mesh , & mut acceleration ) ;
2023-11-30 09:51:17 +00:00
a = acceleration ;
} ,
_ = > ( ) ,
}
} ,
_ = > panic! ( " impossible touching state " ) ,
}
}
for intersect in & self . intersects {
//water
}
2024-01-30 06:37:05 +00:00
self . constrain_acceleration ( models , hitbox_mesh , & mut a ) ;
2023-11-30 09:51:17 +00:00
( move_state , a )
}
2024-01-30 06:37:05 +00:00
fn predict_collision_end ( & self , collector :& mut instruction ::InstructionCollector < PhysicsInstruction > , models :& PhysicsModels , hitbox_mesh :& HitboxMesh , body :& Body , time :Time ) {
2023-11-30 09:51:17 +00:00
let relative_body = VirtualBody ::relative ( & Body ::default ( ) , body ) . body ( time ) ;
for contact in & self . contacts {
//detect face slide off
2024-01-30 06:37:05 +00:00
let model_mesh = models . mesh ( contact . 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 , collector . time ( ) , contact . face_id ) . map ( | ( _face , time ) | {
2023-11-30 09:51:17 +00:00
TimedInstruction {
time ,
instruction :PhysicsInstruction ::CollisionEnd (
2024-01-30 06:37:05 +00:00
Collision ::Contact ( ContactCollision { convex_mesh_id :contact . convex_mesh_id , face_id :contact . face_id } )
2023-11-30 09:51:17 +00:00
) ,
}
} ) ) ;
}
for intersect in & self . intersects {
//detect model collision in reverse
2024-01-30 06:37:05 +00:00
let model_mesh = models . mesh ( intersect . convex_mesh_id ) ;
let minkowski = model_physics ::MinkowskiMesh ::minkowski_sum ( model_mesh , hitbox_mesh . transformed_mesh ( ) ) ;
collector . collect ( minkowski . predict_collision_out ( & relative_body , collector . time ( ) ) . map ( | ( _face , time ) | {
2023-11-30 09:51:17 +00:00
TimedInstruction {
time ,
instruction :PhysicsInstruction ::CollisionEnd (
2024-01-30 06:37:05 +00:00
Collision ::Intersect ( IntersectCollision { convex_mesh_id :intersect . convex_mesh_id } )
2023-11-30 09:51:17 +00:00
) ,
}
} ) ) ;
2023-10-13 02:44:46 +00:00
}
}
}
2023-11-30 09:51:17 +00:00
impl Body {
2024-01-30 06:37:05 +00:00
pub const fn new ( position :Planar64Vec3 , velocity :Planar64Vec3 , acceleration :Planar64Vec3 , time :Time ) ->Self {
2023-09-09 03:14:18 +00:00
Self {
2023-09-18 23:03:27 +00:00
position ,
velocity ,
acceleration ,
2023-11-30 09:51:17 +00:00
time ,
2023-09-09 03:14:18 +00:00
}
}
2023-09-27 09:12:20 +00:00
pub fn extrapolated_position ( & self , time :Time ) ->Planar64Vec3 {
let dt = time - self . time ;
self . position + self . velocity * dt + self . acceleration * ( dt * dt / 2 )
2023-09-09 03:14:18 +00:00
}
2023-09-27 09:12:20 +00:00
pub fn extrapolated_velocity ( & self , time :Time ) ->Planar64Vec3 {
let dt = time - self . time ;
self . velocity + self . acceleration * dt
2023-09-18 23:04:18 +00:00
}
2023-09-27 09:12:20 +00:00
pub fn advance_time ( & mut self , time :Time ) {
2023-09-09 03:14:18 +00:00
self . position = self . extrapolated_position ( time ) ;
2023-09-18 23:04:18 +00:00
self . velocity = self . extrapolated_velocity ( time ) ;
2023-09-09 03:14:18 +00:00
self . time = time ;
}
2023-11-30 09:51:17 +00:00
pub fn infinity_dir ( & self ) ->Option < Planar64Vec3 > {
if self . velocity = = Planar64Vec3 ::ZERO {
if self . acceleration = = Planar64Vec3 ::ZERO {
None
} else {
Some ( self . acceleration )
}
} else {
Some ( self . velocity )
}
}
2024-01-30 00:19:41 +00:00
pub fn grow_aabb ( & self , aabb :& mut aabb ::Aabb , t0 :Time , t1 :Time ) {
2023-11-30 09:51:17 +00:00
aabb . grow ( self . extrapolated_position ( t0 ) ) ;
aabb . grow ( self . extrapolated_position ( t1 ) ) ;
//v+a*t==0
//goober code
if self . acceleration . x ( ) ! = Planar64 ::ZERO {
let t = Time ::from ( - self . velocity . x ( ) / self . acceleration . x ( ) ) ;
if t0 < t & & t < t1 {
aabb . grow ( self . extrapolated_position ( t ) ) ;
}
}
if self . acceleration . y ( ) ! = Planar64 ::ZERO {
let t = Time ::from ( - self . velocity . y ( ) / self . acceleration . y ( ) ) ;
if t0 < t & & t < t1 {
aabb . grow ( self . extrapolated_position ( t ) ) ;
}
}
if self . acceleration . z ( ) ! = Planar64 ::ZERO {
let t = Time ::from ( - self . velocity . z ( ) / self . acceleration . z ( ) ) ;
if t0 < t & & t < t1 {
aabb . grow ( self . extrapolated_position ( t ) ) ;
}
}
}
2023-09-09 03:14:18 +00:00
}
2023-10-14 22:41:59 +00:00
impl std ::fmt ::Display for Body {
fn fmt ( & self , f :& mut std ::fmt ::Formatter < '_ > ) ->std ::fmt ::Result {
write! ( f , " p({}) v({}) a({}) t({}) " , self . position , self . velocity , self . acceleration , self . time )
}
}
2023-09-09 03:14:18 +00:00
2023-11-30 09:51:17 +00:00
struct VirtualBody < ' a > {
body0 :& ' a Body ,
body1 :& ' a Body ,
}
impl VirtualBody < '_ > {
2024-01-30 06:37:05 +00:00
const fn relative < ' a > ( body0 :& ' a Body , body1 :& ' a Body ) ->VirtualBody < ' a > {
2023-11-30 09:51:17 +00:00
//(p0,v0,a0,t0)
//(p1,v1,a1,t1)
VirtualBody {
body0 ,
body1 ,
}
}
fn extrapolated_position ( & self , time :Time ) ->Planar64Vec3 {
self . body1 . extrapolated_position ( time ) - self . body0 . extrapolated_position ( time )
}
fn extrapolated_velocity ( & self , time :Time ) ->Planar64Vec3 {
self . body1 . extrapolated_velocity ( time ) - self . body0 . extrapolated_velocity ( time )
}
fn acceleration ( & self ) ->Planar64Vec3 {
self . body1 . acceleration - self . body0 . acceleration
}
fn body ( & self , time :Time ) ->Body {
Body ::new ( self . extrapolated_position ( time ) , self . extrapolated_velocity ( time ) , self . acceleration ( ) , time )
}
}
2024-01-30 06:37:05 +00:00
pub struct PhysicsState {
time :Time ,
body :Body ,
world :WorldState , //currently there is only one state the world can be in
touching :TouchingState ,
//camera must exist in state because wormholes modify the camera, also camera punch
camera :PhysicsCamera ,
//input_state
pub next_mouse :MouseState , //Where is the mouse headed next
controls :u32 , //TODO this should be a struct
//style
style :StyleModifiers , //mode style with custom style updates applied
//gameplay_state
mode_state :ModeState ,
move_state :MoveState ,
}
//random collection of contextual data that doesn't belong in PhysicsState
pub struct PhysicsData {
//permanent map data
bvh :bvh ::BvhNode < ConvexMeshId > ,
//transient map/environment data (open world loads/unloads parts of this data)
models :PhysicsModels ,
//semi-transient data
modes :gameplay_modes ::Modes ,
//cached calculations
hitbox_mesh :HitboxMesh ,
}
2023-10-05 03:04:04 +00:00
impl Default for PhysicsState {
2023-10-31 05:25:03 +00:00
fn default ( ) ->Self {
2023-10-05 03:04:04 +00:00
Self {
2023-11-30 09:51:17 +00:00
body :Body ::new ( Planar64Vec3 ::int ( 0 , 50 , 0 ) , Planar64Vec3 ::int ( 0 , 0 , 0 ) , Planar64Vec3 ::int ( 0 , - 100 , 0 ) , Time ::ZERO ) ,
2023-10-31 05:25:03 +00:00
time :Time ::ZERO ,
2023-10-05 03:04:04 +00:00
style :StyleModifiers ::default ( ) ,
2023-10-13 02:44:46 +00:00
touching :TouchingState ::default ( ) ,
move_state : MoveState ::Air ,
2023-10-31 05:25:03 +00:00
camera :PhysicsCamera ::default ( ) ,
next_mouse :MouseState ::default ( ) ,
controls :0 ,
2023-10-05 03:04:04 +00:00
world :WorldState { } ,
2024-01-30 06:37:05 +00:00
mode_state :ModeState ::default ( ) ,
}
}
}
impl Default for PhysicsData {
fn default ( ) ->Self {
Self {
bvh :bvh ::BvhNode ::default ( ) ,
models :Default ::default ( ) ,
modes :Default ::default ( ) ,
hitbox_mesh :StyleModifiers ::default ( ) . calculate_mesh ( ) ,
2023-10-05 03:04:04 +00:00
}
}
}
2023-09-08 18:33:16 +00:00
impl PhysicsState {
2023-10-04 02:43:41 +00:00
pub fn clear ( & mut self ) {
2023-10-13 02:44:46 +00:00
self . touching . clear ( ) ;
2023-10-05 03:04:04 +00:00
}
pub fn output ( & self ) ->PhysicsOutputState {
PhysicsOutputState {
body :self . body . clone ( ) ,
camera :self . camera . clone ( ) ,
2023-10-31 05:25:03 +00:00
camera_offset :self . style . camera_offset . clone ( ) ,
2023-10-05 03:04:04 +00:00
}
}
2023-10-10 02:44:49 +00:00
pub fn load_user_settings ( & mut self , user_settings :& crate ::settings ::UserSettings ) {
self . camera . sensitivity = user_settings . calculate_sensitivity ( ) ;
}
2023-09-27 09:12:20 +00:00
pub fn advance_time ( & mut self , time : Time ) {
2023-09-09 03:14:18 +00:00
self . body . advance_time ( time ) ;
self . time = time ;
2023-09-08 18:33:16 +00:00
}
2023-09-08 18:33:20 +00:00
2023-09-20 00:53:29 +00:00
fn set_control ( & mut self , control :u32 , state :bool ) {
self . controls = if state { self . controls | control } else { self . controls & ! control } ;
}
2023-10-13 02:44:46 +00:00
2023-10-31 21:11:39 +00:00
fn next_strafe_instruction ( & self ) ->Option < TimedInstruction < PhysicsInstruction > > {
self . style . strafe . as_ref ( ) . map ( | strafe | {
2023-10-20 20:46:59 +00:00
TimedInstruction {
2024-01-30 06:37:05 +00:00
time :strafe . next_tick ( self . time ) ,
2023-10-20 20:46:59 +00:00
//only poll the physics if there is a before and after mouse event
instruction :PhysicsInstruction ::StrafeTick
}
} )
2023-09-08 18:33:20 +00:00
}
2023-09-08 21:11:24 +00:00
2023-09-09 03:13:30 +00:00
//state mutated on collision:
//Accelerator
//stair step-up
//state mutated on instruction
//change fly acceleration (fly_sustain)
//change fly velocity
//generic event emmiters
//PlatformStandTime
//walk/swim/air/ladder sounds
//VState?
//falling under the map
// fn next_respawn_instruction(&self) -> Option<TimedInstruction<PhysicsInstruction>> {
// if self.body.position<self.world.min_y {
// return Some(TimedInstruction{
// time:self.time,
// instruction:PhysicsInstruction::Trigger(None)
// });
// }
// }
// fn next_water_instruction(&self) -> Option<TimedInstruction<PhysicsInstruction>> {
// return Some(TimedInstruction{
// time:(self.time*self.strafe_tick_num/self.strafe_tick_den+1)*self.strafe_tick_den/self.strafe_tick_num,
// //only poll the physics if there is a before and after mouse event
// instruction:PhysicsInstruction::Water
// });
// }
2023-10-13 02:44:46 +00:00
fn next_move_instruction ( & self ) ->Option < TimedInstruction < PhysicsInstruction > > {
2023-09-19 04:10:07 +00:00
//check if you have a valid walk state and create an instruction
2023-10-13 02:44:46 +00:00
match & self . move_state {
MoveState ::Walk ( walk_state ) | MoveState ::Ladder ( walk_state ) = > match & walk_state . state {
2023-10-17 07:17:54 +00:00
WalkEnum ::Transient ( walk_target ) = > Some ( TimedInstruction {
time :walk_target . time ,
2023-09-19 06:36:14 +00:00
instruction :PhysicsInstruction ::ReachWalkTargetVelocity
} ) ,
WalkEnum ::Reached = > None ,
}
2023-10-13 02:44:46 +00:00
MoveState ::Air = > self . next_strafe_instruction ( ) ,
MoveState ::Water = > None , //TODO
2023-09-19 04:10:07 +00:00
}
2023-09-08 21:11:24 +00:00
}
2023-09-08 18:33:20 +00:00
}
2024-01-30 06:37:05 +00:00
#[ derive(Default) ]
pub struct PhysicsContext {
pub state :PhysicsState , //this captures the entire state of the physics.
data :PhysicsData , //data currently loaded into memory which is needded for physics to run, but is not part of the state.
}
impl instruction ::InstructionConsumer < PhysicsInstruction > for PhysicsContext {
fn process_instruction ( & mut self , ins :TimedInstruction < PhysicsInstruction > ) {
atomic_state_update ( & mut self . state , & self . data , ins )
}
}
impl instruction ::InstructionEmitter < PhysicsInstruction > for PhysicsContext {
2023-09-09 00:34:26 +00:00
//this little next instruction function can cache its return value and invalidate the cached value by watching the State.
2023-11-30 09:51:17 +00:00
fn next_instruction ( & self , time_limit :Time ) ->Option < TimedInstruction < PhysicsInstruction > > {
2024-01-30 06:37:05 +00:00
literally_next_instruction_but_with_context ( & self . state , & self . data , time_limit )
}
}
impl PhysicsContext {
pub fn spawn ( & mut self ) {
self . process_instruction ( instruction ::TimedInstruction {
time :self . state . time ,
instruction : PhysicsInstruction ::Input ( PhysicsInputInstruction ::Reset ) ,
} ) ;
}
pub fn generate_models ( & mut self , map :& map ::CompleteMap ) {
self . data . modes = map . modes . clone ( ) ;
let mut used_attributes = Vec ::new ( ) ;
let mut physics_attr_id_from_model_attr_id = HashMap ::< CollisionAttributesId , PhysicsAttributesId > ::new ( ) ;
let mut used_meshes = Vec ::new ( ) ;
let mut physics_mesh_id_from_model_mesh_id = HashMap ::< MeshId , PhysicsMeshId > ::new ( ) ;
self . data . models . models = map . models . iter ( ) . enumerate ( ) . filter_map ( | ( model_id , model ) | {
let attr_id = if let Some ( & attr_id ) = physics_attr_id_from_model_attr_id . get ( & model . attributes ) {
attr_id
} else {
//check if it's real
match map . attributes . get ( model . attributes . get ( ) as usize ) . and_then ( | m_attr | {
PhysicsCollisionAttributes ::try_from ( m_attr ) . map_or ( None , | p_attr | {
let attr_id = PhysicsAttributesId ::new ( used_attributes . len ( ) as u32 ) ;
used_attributes . push ( p_attr ) ;
physics_attr_id_from_model_attr_id . insert ( model . attributes , attr_id ) ;
Some ( attr_id )
} )
} ) {
Some ( attr_id ) = > attr_id ,
None = > return None ,
}
} ;
let mesh_id = if let Some ( & mesh_id ) = physics_mesh_id_from_model_mesh_id . get ( & model . mesh ) {
mesh_id
} else {
match map . meshes . get ( model . mesh . get ( ) as usize ) . and_then ( | mesh | {
let mesh_id = PhysicsMeshId ::new ( used_meshes . len ( ) as u32 ) ;
used_meshes . push ( PhysicsMesh ::from ( mesh ) ) ;
physics_mesh_id_from_model_mesh_id . insert ( model . mesh , mesh_id ) ;
Some ( mesh_id )
} ) {
Some ( mesh_id ) = > mesh_id ,
None = > return None ,
}
} ;
Some ( ( PhysicsModelId ::new ( model_id as u32 ) , PhysicsModel ::new ( mesh_id , attr_id , PhysicsMeshTransform ::new ( model . transform ) ) ) )
} ) . collect ( ) ;
self . data . models . attributes = used_attributes . into_iter ( ) . enumerate ( ) . map ( | ( attr_id , attr ) | ( PhysicsAttributesId ::new ( attr_id as u32 ) , attr ) ) . collect ( ) ;
self . data . models . meshes = used_meshes . into_iter ( ) . enumerate ( ) . map ( | ( mesh_id , mesh ) | ( PhysicsMeshId ::new ( mesh_id as u32 ) , mesh ) ) . collect ( ) ;
let convex_mesh_aabb_list = self . data . models . models . iter ( )
. flat_map ( | ( & model_id , model ) | {
self . data . models . meshes [ & model . mesh_id ] . submesh_views ( )
. enumerate ( ) . map ( move | ( submesh_id , view ) | {
let mut aabb = aabb ::Aabb ::default ( ) ;
let transformed_mesh = TransformedMesh ::new ( view , & model . transform ) ;
for v in transformed_mesh . verts ( ) {
aabb . grow ( v ) ;
}
( ConvexMeshId {
model_id ,
submesh_id :PhysicsSubmeshId ::new ( submesh_id as u32 ) ,
} , aabb )
} )
} ) . collect ( ) ;
self . data . bvh = bvh ::generate_bvh ( convex_mesh_aabb_list ) ;
println! ( " Physics Objects: {} " , self . data . models . models . len ( ) ) ;
}
//tickless gaming
pub fn run ( & mut self , time_limit :Time ) {
//prepare is ommitted - everything is done via instructions.
while let Some ( instruction ) = self . next_instruction ( time_limit ) { //collect
//process
self . process_instruction ( instruction ) ;
//write hash lol
}
}
}
//TODO get rid of this trash
fn refresh_walk_target ( s :& mut PhysicsState , data :& PhysicsData ) ->Planar64Vec3 {
match & mut s . move_state {
MoveState ::Air | MoveState ::Water = > s . touching . base_acceleration ( & data . models , & s . style , & s . camera , s . controls , & s . next_mouse , s . time ) ,
MoveState ::Walk ( WalkState { state , contact , jump_direction :_ } ) = > {
let n = contact_normal ( & data . models , & data . hitbox_mesh , contact ) ;
let gravity = s . touching . base_acceleration ( & data . models , & s . style , & s . camera , s . controls , & s . next_mouse , s . time ) ;
let a ;
let mut v = s . style . get_walk_target_velocity ( & s . camera , s . controls , & s . next_mouse , s . time , & n ) ;
s . touching . constrain_velocity ( & data . models , & data . hitbox_mesh , & mut v ) ;
let normal_accel = - n . dot ( gravity ) / n . length ( ) ;
( * state , a ) = WalkEnum ::with_target_velocity ( & s . body , & s . style , v , & n , s . style . walk_speed , normal_accel ) ;
a
} ,
MoveState ::Ladder ( WalkState { state , contact , jump_direction :_ } ) = > {
let n = contact_normal ( & data . models , & data . hitbox_mesh , contact ) ;
//let gravity=s.touching.base_acceleration(&data.models,&s.style,&s.camera,s.controls,&s.next_mouse,s.time);
let a ;
let mut v = s . style . get_ladder_target_velocity ( & s . camera , s . controls , & s . next_mouse , s . time , & n ) ;
s . touching . constrain_velocity ( & data . models , & data . hitbox_mesh , & mut v ) ;
( * state , a ) = WalkEnum ::with_target_velocity ( & s . body , & s . style , v , & n , s . style . ladder_speed , s . style . ladder_accel ) ;
a
} ,
}
}
fn literally_next_instruction_but_with_context ( state :& PhysicsState , data :& PhysicsData , time_limit :Time ) ->Option < TimedInstruction < PhysicsInstruction > > {
2023-09-08 18:33:20 +00:00
//JUST POLLING!!! NO MUTATION
2024-01-30 00:19:41 +00:00
let mut collector = instruction ::InstructionCollector ::new ( time_limit ) ;
2023-11-30 09:51:17 +00:00
2024-01-30 06:37:05 +00:00
collector . collect ( state . next_move_instruction ( ) ) ;
2023-11-30 09:51:17 +00:00
//check for collision ends
2024-01-30 06:37:05 +00:00
state . touching . predict_collision_end ( & mut collector , & data . models , & data . hitbox_mesh , & state . body , state . time ) ;
2023-11-30 09:51:17 +00:00
//check for collision starts
2024-01-30 00:19:41 +00:00
let mut aabb = aabb ::Aabb ::default ( ) ;
2024-01-30 06:37:05 +00:00
state . body . grow_aabb ( & mut aabb , state . time , collector . time ( ) ) ;
aabb . inflate ( data . hitbox_mesh . halfsize ) ;
2023-11-30 09:51:17 +00:00
//common body
2024-01-30 06:37:05 +00:00
let relative_body = VirtualBody ::relative ( & Body ::default ( ) , & state . body ) . body ( state . time ) ;
data . bvh . the_tester ( & aabb , & mut | convex_mesh_id | {
2023-11-30 09:51:17 +00:00
//no checks are needed because of the time limits.
2024-01-30 06:37:05 +00:00
let model_mesh = data . models . mesh ( convex_mesh_id ) ;
let minkowski = model_physics ::MinkowskiMesh ::minkowski_sum ( model_mesh , data . hitbox_mesh . transformed_mesh ( ) ) ;
2023-11-30 09:51:17 +00:00
collector . collect ( minkowski . predict_collision_in ( & relative_body , collector . time ( ) )
//temp (?) code to avoid collision loops
2024-01-30 06:37:05 +00:00
. map_or ( None , | ( face , time ) | if time = = state . time { None } else { Some ( ( face , time ) ) } )
2023-11-30 09:51:17 +00:00
. map ( | ( face , time ) | {
2024-01-30 06:37:05 +00:00
TimedInstruction { time , instruction :PhysicsInstruction ::CollisionStart ( match data . models . attr ( convex_mesh_id . model_id ) {
PhysicsCollisionAttributes ::Contact { contacting :_ , general :_ } = > Collision ::Contact ( ContactCollision { convex_mesh_id , face_id :face } ) ,
PhysicsCollisionAttributes ::Intersect { intersecting :_ , general :_ } = > Collision ::Intersect ( IntersectCollision { convex_mesh_id } ) ,
2023-11-30 09:51:17 +00:00
} ) }
} ) ) ;
2023-10-06 06:52:04 +00:00
} ) ;
2023-09-09 03:12:58 +00:00
collector . instruction ( )
2023-09-08 18:33:20 +00:00
}
2023-09-09 00:15:49 +00:00
2023-11-30 09:51:17 +00:00
fn get_walk_state ( move_state :& MoveState ) ->Option < & WalkState > {
match move_state {
MoveState ::Walk ( walk_state ) | MoveState ::Ladder ( walk_state ) = > Some ( walk_state ) ,
MoveState ::Air | MoveState ::Water = > None ,
}
}
2024-01-30 06:37:05 +00:00
fn jumped_velocity ( models :& PhysicsModels , style :& StyleModifiers , hitbox_mesh :& HitboxMesh , walk_state :& WalkState , v :& mut Planar64Vec3 ) {
2023-11-30 09:51:17 +00:00
let jump_dir = match & walk_state . jump_direction {
2024-01-30 06:37:05 +00:00
JumpDirection ::FromContactNormal = > contact_normal ( models , hitbox_mesh , & walk_state . contact ) ,
2023-11-30 09:51:17 +00:00
& JumpDirection ::Exactly ( dir ) = > dir ,
} ;
* v = * v + jump_dir * ( style . get_jump_deltav ( ) / jump_dir . length ( ) ) ;
}
2024-01-30 06:37:05 +00:00
fn contact_normal ( models :& PhysicsModels , hitbox_mesh :& HitboxMesh , contact :& ContactCollision ) ->Planar64Vec3 {
let model_mesh = models . mesh ( contact . convex_mesh_id ) ;
let minkowski = model_physics ::MinkowskiMesh ::minkowski_sum ( model_mesh , hitbox_mesh . transformed_mesh ( ) ) ;
2023-11-30 09:51:17 +00:00
minkowski . face_nd ( contact . face_id ) . 0
}
fn set_position ( body :& mut Body , touching :& mut TouchingState , point :Planar64Vec3 ) ->Planar64Vec3 {
//test intersections at new position
//hovering above the surface 0 units is not intersecting. you will fall into it just fine
2023-10-18 23:18:17 +00:00
body . position = point ;
//manual clear //for c in contacts{process_instruction(CollisionEnd(c))}
touching . clear ( ) ;
//TODO: calculate contacts and determine the actual state
//touching.recalculate(body);
2023-11-30 09:51:17 +00:00
point
}
2024-01-30 06:37:05 +00:00
fn set_velocity_cull ( body :& mut Body , touching :& mut TouchingState , models :& PhysicsModels , hitbox_mesh :& HitboxMesh , v :Planar64Vec3 ) ->bool {
2023-11-30 09:51:17 +00:00
//This is not correct but is better than what I have
let mut culled = false ;
touching . contacts . retain ( | contact | {
2024-01-30 06:37:05 +00:00
let n = contact_normal ( models , hitbox_mesh , contact ) ;
2023-11-30 09:51:17 +00:00
let r = n . dot ( v ) < = Planar64 ::ZERO ;
if ! r {
culled = true ;
println! ( " set_velocity_cull contact= {:?} " , contact ) ;
}
r
} ) ;
2024-01-30 06:37:05 +00:00
set_velocity ( body , touching , models , hitbox_mesh , v ) ;
2023-11-30 09:51:17 +00:00
culled
}
2024-01-30 06:37:05 +00:00
fn set_velocity ( body :& mut Body , touching :& TouchingState , models :& PhysicsModels , hitbox_mesh :& HitboxMesh , mut v :Planar64Vec3 ) ->Planar64Vec3 {
touching . constrain_velocity ( models , hitbox_mesh , & mut v ) ;
2023-11-30 09:51:17 +00:00
body . velocity = v ;
v
}
2024-01-30 06:37:05 +00:00
fn set_acceleration_cull ( body :& mut Body , touching :& mut TouchingState , models :& PhysicsModels , hitbox_mesh :& HitboxMesh , a :Planar64Vec3 ) ->bool {
2023-11-30 09:51:17 +00:00
//This is not correct but is better than what I have
let mut culled = false ;
touching . contacts . retain ( | contact | {
2024-01-30 06:37:05 +00:00
let n = contact_normal ( models , hitbox_mesh , contact ) ;
2023-11-30 09:51:17 +00:00
let r = n . dot ( a ) < = Planar64 ::ZERO ;
if ! r {
culled = true ;
println! ( " set_acceleration_cull contact= {:?} " , contact ) ;
}
r
} ) ;
2024-01-30 06:37:05 +00:00
set_acceleration ( body , touching , models , hitbox_mesh , a ) ;
2023-11-30 09:51:17 +00:00
culled
}
2024-01-30 06:37:05 +00:00
fn set_acceleration ( body :& mut Body , touching :& TouchingState , models :& PhysicsModels , hitbox_mesh :& HitboxMesh , mut a :Planar64Vec3 ) ->Planar64Vec3 {
touching . constrain_acceleration ( models , hitbox_mesh , & mut a ) ;
2023-11-30 09:51:17 +00:00
body . acceleration = a ;
a
}
2024-01-30 06:37:05 +00:00
fn teleport ( body :& mut Body , touching :& mut TouchingState , models :& PhysicsModels , style :& StyleModifiers , hitbox_mesh :& HitboxMesh , point :Planar64Vec3 ) ->MoveState {
2023-11-30 09:51:17 +00:00
set_position ( body , touching , point ) ;
2024-01-30 06:37:05 +00:00
set_acceleration ( body , touching , models , hitbox_mesh , style . gravity ) ;
2023-11-30 09:51:17 +00:00
MoveState ::Air
2023-10-18 23:18:17 +00:00
}
2024-01-30 06:37:05 +00:00
fn teleport_to_spawn ( body :& mut Body , touching :& mut TouchingState , style :& StyleModifiers , hitbox_mesh :& HitboxMesh , mode :& gameplay_modes ::Mode , models :& PhysicsModels , stage_id :gameplay_modes ::StageId ) ->Option < MoveState > {
let model = models . model ( mode . get_spawn_model_id ( stage_id ) ? . into ( ) ) ;
let point = model . transform . vertex . transform_point3 ( Planar64Vec3 ::Y ) + Planar64Vec3 ::Y * ( style . hitbox . halfsize . y ( ) + Planar64 ::ONE / 16 ) ;
Some ( teleport ( body , touching , models , style , hitbox_mesh , point ) )
2023-10-28 23:38:16 +00:00
}
2023-10-18 23:18:17 +00:00
2024-01-30 06:37:05 +00:00
fn run_teleport_behaviour ( wormhole :& Option < gameplay_attributes ::Wormhole > , models :& PhysicsModels , mode :& gameplay_modes ::Mode , style :& StyleModifiers , hitbox_mesh :& HitboxMesh , mode_state :& mut ModeState , touching :& mut TouchingState , body :& mut Body , convex_mesh_id :ConvexMeshId ) ->Option < MoveState > {
2023-11-30 09:51:17 +00:00
//TODO: jump count and checkpoints are always reset on teleport.
//Map makers are expected to use tools to prevent
//multi-boosting on JumpLimit boosters such as spawning into a SetVelocity
2024-01-30 06:37:05 +00:00
if let Some ( stage_element ) = mode . get_element ( convex_mesh_id . model_id . into ( ) ) {
if let Some ( stage ) = mode . get_stage ( stage_element . stage_id ( ) ) {
if mode_state . get_stage_id ( ) < stage_element . stage_id ( ) {
//checkpoint check
//check if current stage is complete
if let Some ( current_stage ) = mode . get_stage ( mode_state . get_stage_id ( ) ) {
if ! current_stage . is_complete ( mode_state . ordered_checkpoint_count ( ) , mode_state . unordered_checkpoint_count ( ) ) {
//do the stage checkpoints have to be reset?
return teleport_to_spawn ( body , touching , style , hitbox_mesh , mode , models , mode_state . get_stage_id ( ) ) ;
}
}
//check if all between stages have no checkpoints required to pass them
for stage_id in mode_state . get_stage_id ( ) . get ( ) + 1 .. stage_element . stage_id ( ) . get ( ) {
let stage_id = StageId ::new ( stage_id ) ;
//check if none of the between stages has checkpoints, if they do teleport back to that stage
if ! mode . get_stage ( stage_id ) ? . is_empty ( ) {
mode_state . set_stage_id ( stage_id ) ;
return teleport_to_spawn ( body , touching , style , hitbox_mesh , mode , models , stage_id ) ;
}
}
//notably you do not get teleported for touching ordered checkpoints in the wrong order within the same stage.
mode_state . set_stage_id ( stage_element . stage_id ( ) ) ;
} else if stage_element . force ( ) {
//forced stage_element will set the stage_id even if the stage has already been passed
mode_state . set_stage_id ( stage_element . stage_id ( ) ) ;
2023-10-18 23:18:17 +00:00
}
2024-01-30 06:37:05 +00:00
match stage_element . behaviour ( ) {
gameplay_modes ::StageElementBehaviour ::SpawnAt = > ( ) ,
gameplay_modes ::StageElementBehaviour ::Trigger
| gameplay_modes ::StageElementBehaviour ::Teleport = > {
2023-10-18 23:18:17 +00:00
//I guess this is correct behaviour when trying to teleport to a non-existent spawn but it's still weird
2024-01-30 06:37:05 +00:00
return teleport_to_spawn ( body , touching , style , hitbox_mesh , mode , models , mode_state . get_stage_id ( ) ) ;
2023-10-18 23:18:17 +00:00
} ,
2024-01-30 06:37:05 +00:00
gameplay_modes ::StageElementBehaviour ::Platform = > ( ) ,
gameplay_modes ::StageElementBehaviour ::Check = > ( ) , //this is to run the checkpoint check behaviour without any other side effects
gameplay_modes ::StageElementBehaviour ::Checkpoint = > {
//each of these checks if the model is actually a valid respective checkpoint object
//accumulate sequential ordered checkpoints
mode_state . accumulate_ordered_checkpoint ( & stage , convex_mesh_id . model_id . into ( ) ) ;
//insert model id in accumulated unordered checkpoints
mode_state . accumulate_unordered_checkpoint ( & stage , convex_mesh_id . model_id . into ( ) ) ;
2023-11-30 09:51:17 +00:00
} ,
2023-10-18 23:18:17 +00:00
}
2024-01-30 06:37:05 +00:00
}
}
match wormhole {
& Some ( gameplay_attributes ::Wormhole { destination_model } ) = > {
let origin_model = models . model ( convex_mesh_id . model_id ) ;
let destination_model = models . model ( destination_model . into ( ) ) ;
2023-10-18 23:15:42 +00:00
//ignore the transform for now
2024-01-30 06:37:05 +00:00
Some ( teleport ( body , touching , models , style , hitbox_mesh , body . position - origin_model . transform . vertex . translation + destination_model . transform . vertex . translation ) )
2023-10-18 23:18:17 +00:00
}
None = > None ,
}
}
2024-01-30 06:37:05 +00:00
fn atomic_state_update ( state :& mut PhysicsState , data :& PhysicsData , ins :TimedInstruction < PhysicsInstruction > ) {
2023-11-30 09:51:17 +00:00
match & ins . instruction {
2023-10-05 03:04:04 +00:00
PhysicsInstruction ::Input ( PhysicsInputInstruction ::Idle )
| PhysicsInstruction ::Input ( PhysicsInputInstruction ::SetNextMouse ( _ ) )
| PhysicsInstruction ::Input ( PhysicsInputInstruction ::ReplaceMouse ( _ , _ ) )
2023-11-30 09:51:17 +00:00
| PhysicsInstruction ::StrafeTick = > ( ) ,
2023-10-05 06:51:39 +00:00
_ = > println! ( " {} | {:?} " , ins . time , ins . instruction ) ,
2023-09-19 06:36:14 +00:00
}
//selectively update body
2023-11-30 09:51:17 +00:00
match & ins . instruction {
2024-01-30 06:37:05 +00:00
PhysicsInstruction ::Input ( PhysicsInputInstruction ::Idle ) = > state . time = ins . time , //idle simply updates time
2023-10-04 21:01:06 +00:00
PhysicsInstruction ::Input ( _ )
| PhysicsInstruction ::ReachWalkTargetVelocity
| PhysicsInstruction ::CollisionStart ( _ )
| PhysicsInstruction ::CollisionEnd ( _ )
2024-01-30 06:37:05 +00:00
| PhysicsInstruction ::StrafeTick = > state . advance_time ( ins . time ) ,
2023-09-19 06:36:14 +00:00
}
2023-11-30 09:51:17 +00:00
match ins . instruction {
PhysicsInstruction ::CollisionStart ( c ) = > {
2024-01-30 06:37:05 +00:00
let convex_mesh_id = c . convex_mesh_id ( ) ;
match ( data . models . attr ( convex_mesh_id . model_id ) , & c ) {
2023-11-30 09:51:17 +00:00
( PhysicsCollisionAttributes ::Contact { contacting , general } , Collision ::Contact ( contact ) ) = > {
2024-01-30 06:37:05 +00:00
let mut v = state . body . velocity ;
let normal = contact_normal ( & data . models , & data . hitbox_mesh , contact ) ;
2023-10-17 07:17:54 +00:00
match & contacting . contact_behaviour {
2024-01-30 06:37:05 +00:00
Some ( gameplay_attributes ::ContactingBehaviour ::Surf ) = > println! ( " I'm surfing! " ) ,
Some ( gameplay_attributes ::ContactingBehaviour ::Cling ) = > println! ( " Unimplemented! " ) ,
& Some ( gameplay_attributes ::ContactingBehaviour ::Elastic ( elasticity ) ) = > {
2023-11-30 09:51:17 +00:00
//velocity and normal are facing opposite directions so this is inherently negative.
let d = normal . dot ( v ) * ( Planar64 ::ONE + Planar64 ::raw ( elasticity as i64 + 1 ) ) ;
v + = normal * ( d / normal . dot ( normal ) ) ;
2023-10-18 02:12:25 +00:00
} ,
2024-01-30 06:37:05 +00:00
Some ( gameplay_attributes ::ContactingBehaviour ::Ladder ( contacting_ladder ) ) = > {
2023-10-17 07:17:54 +00:00
if contacting_ladder . sticky {
//kill v
2023-11-30 09:51:17 +00:00
//actually you could do this with a booster attribute :thinking:
2023-10-18 02:12:25 +00:00
v = Planar64Vec3 ::ZERO ; //model.velocity
2023-10-17 07:17:54 +00:00
}
//ladder walkstate
2024-01-30 06:37:05 +00:00
let gravity = state . touching . base_acceleration ( & data . models , & state . style , & state . camera , state . controls , & state . next_mouse , state . time ) ;
let mut target_velocity = state . style . get_ladder_target_velocity ( & state . camera , state . controls , & state . next_mouse , state . time , & normal ) ;
state . touching . constrain_velocity ( & data . models , & data . hitbox_mesh , & mut target_velocity ) ;
let ( walk_state , a ) = WalkState ::ladder ( & state . body , & state . style , gravity , target_velocity , contact . clone ( ) , & normal ) ;
state . move_state = MoveState ::Ladder ( walk_state ) ;
set_acceleration ( & mut state . body , & state . touching , & data . models , & data . hitbox_mesh , a ) ;
} ,
Some ( gameplay_attributes ::ContactingBehaviour ::NoJump ) = > todo! ( " nyi " ) ,
None = > if state . style . surf_slope . map_or ( true , | s | contact_normal ( & data . models , & data . hitbox_mesh , contact ) . walkable ( s , Planar64Vec3 ::Y ) ) {
2023-11-30 09:51:17 +00:00
//ground
2024-01-30 06:37:05 +00:00
let gravity = state . touching . base_acceleration ( & data . models , & state . style , & state . camera , state . controls , & state . next_mouse , state . time ) ;
let mut target_velocity = state . style . get_walk_target_velocity ( & state . camera , state . controls , & state . next_mouse , state . time , & normal ) ;
state . touching . constrain_velocity ( & data . models , & data . hitbox_mesh , & mut target_velocity ) ;
let ( walk_state , a ) = WalkState ::ground ( & state . body , & state . style , gravity , target_velocity , contact . clone ( ) , & normal ) ;
state . move_state = MoveState ::Walk ( walk_state ) ;
set_acceleration ( & mut state . body , & state . touching , & data . models , & data . hitbox_mesh , a ) ;
2023-10-04 21:15:04 +00:00
} ,
}
2023-10-05 03:04:04 +00:00
//check ground
2024-01-30 06:37:05 +00:00
state . touching . insert ( c ) ;
2023-10-18 23:18:17 +00:00
//I love making functions with 10 arguments to dodge the borrow checker
2024-01-30 06:37:05 +00:00
run_teleport_behaviour ( & general . wormhole , & data . models , & data . modes . get_mode ( state . mode_state . get_mode_id ( ) ) . unwrap ( ) , & state . style , & data . hitbox_mesh , & mut state . mode_state , & mut state . touching , & mut state . body , convex_mesh_id ) ;
2023-10-04 21:15:04 +00:00
//flatten v
2024-01-30 06:37:05 +00:00
state . touching . constrain_velocity ( & data . models , & data . hitbox_mesh , & mut v ) ;
2023-10-05 03:04:04 +00:00
match & general . booster {
Some ( booster ) = > {
2023-11-30 09:51:17 +00:00
//DELETE THIS when boosters get converted to height machines
2023-10-18 02:12:25 +00:00
match booster {
2024-01-30 06:37:05 +00:00
//&gameplay_attributes::Booster::Affine(transform)=>v=transform.transform_point3(v),
& gameplay_attributes ::Booster ::Velocity ( velocity ) = > v + = velocity ,
& gameplay_attributes ::Booster ::Energy { direction : _ , energy : _ } = > todo! ( ) ,
2023-10-18 02:12:25 +00:00
}
} ,
None = > ( ) ,
}
2024-01-30 06:37:05 +00:00
let calc_move = if state . style . get_control ( StyleModifiers ::CONTROL_JUMP , state . controls ) {
if let Some ( walk_state ) = get_walk_state ( & state . move_state ) {
jumped_velocity ( & data . models , & state . style , & data . hitbox_mesh , walk_state , & mut v ) ;
set_velocity_cull ( & mut state . body , & mut state . touching , & data . models , & data . hitbox_mesh , v )
2023-11-30 09:51:17 +00:00
} else { false }
} else { false } ;
2023-10-18 02:12:25 +00:00
match & general . trajectory {
Some ( trajectory ) = > {
match trajectory {
2024-01-30 06:37:05 +00:00
gameplay_attributes ::SetTrajectory ::AirTime ( _ ) = > todo! ( ) ,
gameplay_attributes ::SetTrajectory ::Height ( _ ) = > todo! ( ) ,
gameplay_attributes ::SetTrajectory ::TargetPointTime { target_point : _ , time : _ } = > todo! ( ) ,
gameplay_attributes ::SetTrajectory ::TargetPointSpeed { target_point : _ , speed : _ , trajectory_choice : _ } = > todo! ( ) ,
& gameplay_attributes ::SetTrajectory ::Velocity ( velocity ) = > v = velocity ,
gameplay_attributes ::SetTrajectory ::DotVelocity { direction : _ , dot : _ } = > todo! ( ) ,
2023-10-18 02:12:25 +00:00
}
2023-10-05 03:04:04 +00:00
} ,
None = > ( ) ,
}
2024-01-30 06:37:05 +00:00
set_velocity ( & mut state . body , & state . touching , & data . models , & data . hitbox_mesh , v ) ;
2023-11-30 09:51:17 +00:00
//not sure if or is correct here
if calc_move | | Planar64 ::ZERO < normal . dot ( v ) {
2024-01-30 06:37:05 +00:00
( state . move_state , state . body . acceleration ) = state . touching . get_move_state ( & state . body , & data . models , & state . style , & data . hitbox_mesh , & state . camera , state . controls , & state . next_mouse , state . time ) ;
2023-10-20 20:47:26 +00:00
}
2024-01-30 06:37:05 +00:00
let a = refresh_walk_target ( state , data ) ;
set_acceleration ( & mut state . body , & state . touching , & data . models , & data . hitbox_mesh , a ) ;
2023-10-04 21:15:04 +00:00
} ,
2023-11-30 09:51:17 +00:00
( PhysicsCollisionAttributes ::Intersect { intersecting : _ , general } , Collision ::Intersect ( intersect ) ) = > {
2023-10-04 21:15:04 +00:00
//I think that setting the velocity to 0 was preventing surface contacts from entering an infinite loop
2024-01-30 06:37:05 +00:00
state . touching . insert ( c ) ;
run_teleport_behaviour ( & general . wormhole , & data . models , & data . modes . get_mode ( state . mode_state . get_mode_id ( ) ) . unwrap ( ) , & state . style , & data . hitbox_mesh , & mut state . mode_state , & mut state . touching , & mut state . body , convex_mesh_id ) ;
2023-10-04 21:15:04 +00:00
} ,
2023-11-30 09:51:17 +00:00
_ = > panic! ( " invalid pair " ) ,
2023-09-20 00:53:29 +00:00
}
2023-09-18 20:20:51 +00:00
} ,
2024-01-30 06:37:05 +00:00
PhysicsInstruction ::CollisionEnd ( c ) = > {
match data . models . attr ( c . convex_mesh_id ( ) . model_id ) {
2023-11-30 09:51:17 +00:00
PhysicsCollisionAttributes ::Contact { contacting :_ , general :_ } = > {
2024-01-30 06:37:05 +00:00
state . touching . remove ( & c ) ; //remove contact before calling contact_constrain_acceleration
2023-10-04 21:15:04 +00:00
//check ground
2024-01-30 06:37:05 +00:00
( state . move_state , state . body . acceleration ) = state . touching . get_move_state ( & state . body , & data . models , & state . style , & data . hitbox_mesh , & state . camera , state . controls , & state . next_mouse , state . time ) ;
2023-10-04 21:15:04 +00:00
} ,
2023-11-30 09:51:17 +00:00
PhysicsCollisionAttributes ::Intersect { intersecting :_ , general :_ } = > {
2024-01-30 06:37:05 +00:00
state . touching . remove ( & c ) ;
2023-10-04 21:15:04 +00:00
} ,
}
2023-09-18 20:20:51 +00:00
} ,
2024-01-30 06:37:05 +00:00
PhysicsInstruction ::StrafeTick = > {
let control_dir = state . style . get_control_dir ( state . controls ) ;
2023-11-30 09:51:17 +00:00
if control_dir ! = Planar64Vec3 ::ZERO {
2024-01-30 06:37:05 +00:00
let camera_mat = state . camera . simulate_move_rotation_y ( state . camera . mouse . lerp ( & state . next_mouse , state . time ) . x ) ;
2023-11-30 09:51:17 +00:00
let control_dir = camera_mat * control_dir ;
//normalize but careful for zero
2024-01-30 06:37:05 +00:00
let d = state . body . velocity . dot ( control_dir ) ;
if d < state . style . mv {
let v = state . body . velocity + control_dir * ( state . style . mv - d ) ;
2023-11-30 09:51:17 +00:00
//this is wrong but will work ig
//need to note which push planes activate in push solve and keep those
2024-01-30 06:37:05 +00:00
if set_velocity_cull ( & mut state . body , & mut state . touching , & data . models , & data . hitbox_mesh , v ) {
( state . move_state , state . body . acceleration ) = state . touching . get_move_state ( & state . body , & data . models , & state . style , & data . hitbox_mesh , & state . camera , state . controls , & state . next_mouse , state . time ) ;
2023-11-30 09:51:17 +00:00
}
}
2023-09-09 03:14:18 +00:00
}
}
2024-01-30 06:37:05 +00:00
PhysicsInstruction ::ReachWalkTargetVelocity = > {
match & mut state . move_state {
2023-10-13 02:44:46 +00:00
MoveState ::Air | MoveState ::Water = > ( ) ,
MoveState ::Walk ( walk_state ) | MoveState ::Ladder ( walk_state ) = > {
2023-10-17 07:17:54 +00:00
match & mut walk_state . state {
WalkEnum ::Reached = > ( ) ,
WalkEnum ::Transient ( walk_target ) = > {
//precisely set velocity
2023-11-30 09:51:17 +00:00
let a = Planar64Vec3 ::ZERO ; //ignore gravity for now.
2024-01-30 06:37:05 +00:00
set_acceleration ( & mut state . body , & state . touching , & data . models , & data . hitbox_mesh , a ) ;
2023-11-30 09:51:17 +00:00
let v = walk_target . velocity ;
2024-01-30 06:37:05 +00:00
set_velocity ( & mut state . body , & state . touching , & data . models , & data . hitbox_mesh , v ) ;
2023-10-17 07:17:54 +00:00
walk_state . state = WalkEnum ::Reached ;
} ,
}
2023-10-13 02:44:46 +00:00
}
}
2023-09-19 06:36:14 +00:00
} ,
2024-01-30 06:37:05 +00:00
PhysicsInstruction ::Input ( input_instruction ) = > {
let mut b_refresh_walk_target = true ;
2023-09-20 00:53:29 +00:00
match input_instruction {
2024-01-30 06:37:05 +00:00
PhysicsInputInstruction ::SetNextMouse ( m ) = > {
state . camera . move_mouse ( state . next_mouse . pos ) ;
( state . camera . mouse , state . next_mouse ) = ( state . next_mouse . clone ( ) , m ) ;
2023-10-05 03:04:04 +00:00
} ,
2024-01-30 06:37:05 +00:00
PhysicsInputInstruction ::ReplaceMouse ( m0 , m1 ) = > {
state . camera . move_mouse ( m0 . pos ) ;
( state . camera . mouse , state . next_mouse ) = ( m0 , m1 ) ;
2023-09-20 00:53:29 +00:00
} ,
2024-01-30 06:37:05 +00:00
PhysicsInputInstruction ::SetMoveForward ( s ) = > state . set_control ( StyleModifiers ::CONTROL_MOVEFORWARD , s ) ,
PhysicsInputInstruction ::SetMoveLeft ( s ) = > state . set_control ( StyleModifiers ::CONTROL_MOVELEFT , s ) ,
PhysicsInputInstruction ::SetMoveBack ( s ) = > state . set_control ( StyleModifiers ::CONTROL_MOVEBACK , s ) ,
PhysicsInputInstruction ::SetMoveRight ( s ) = > state . set_control ( StyleModifiers ::CONTROL_MOVERIGHT , s ) ,
PhysicsInputInstruction ::SetMoveUp ( s ) = > state . set_control ( StyleModifiers ::CONTROL_MOVEUP , s ) ,
PhysicsInputInstruction ::SetMoveDown ( s ) = > state . set_control ( StyleModifiers ::CONTROL_MOVEDOWN , s ) ,
PhysicsInputInstruction ::SetJump ( s ) = > {
state . set_control ( StyleModifiers ::CONTROL_JUMP , s ) ;
if let Some ( walk_state ) = get_walk_state ( & state . move_state ) {
let mut v = state . body . velocity ;
jumped_velocity ( & data . models , & state . style , & data . hitbox_mesh , walk_state , & mut v ) ;
if set_velocity_cull ( & mut state . body , & mut state . touching , & data . models , & data . hitbox_mesh , v ) {
( state . move_state , state . body . acceleration ) = state . touching . get_move_state ( & state . body , & data . models , & state . style , & data . hitbox_mesh , & state . camera , state . controls , & state . next_mouse , state . time ) ;
2023-11-30 09:51:17 +00:00
}
}
2024-01-30 06:37:05 +00:00
b_refresh_walk_target = false ;
2023-09-20 00:53:29 +00:00
} ,
2024-01-30 06:37:05 +00:00
PhysicsInputInstruction ::SetZoom ( s ) = > {
state . set_control ( StyleModifiers ::CONTROL_ZOOM , s ) ;
b_refresh_walk_target = false ;
2023-09-20 00:53:29 +00:00
} ,
2024-01-30 06:37:05 +00:00
PhysicsInputInstruction ::Reset = > {
2023-11-30 09:51:17 +00:00
//it matters which of these runs first, but I have not thought it through yet as it doesn't matter yet
2024-01-30 06:37:05 +00:00
state . mode_state . clear ( ) ;
state . mode_state . set_stage_id ( gameplay_modes ::StageId ::FIRST ) ;
let spawn_point = data . modes . get_mode ( state . mode_state . get_mode_id ( ) ) . and_then ( | mode |
//TODO: spawn at the bottom of the start zone plus the hitbox size
Some ( data . models . model ( mode . get_start ( ) . into ( ) ) . transform . vertex . translation )
) . unwrap_or ( Planar64Vec3 ::ZERO ) ;
set_position ( & mut state . body , & mut state . touching , spawn_point ) ;
set_velocity ( & mut state . body , & state . touching , & data . models , & data . hitbox_mesh , Planar64Vec3 ::ZERO ) ;
( state . move_state , state . body . acceleration ) = state . touching . get_move_state ( & state . body , & data . models , & state . style , & data . hitbox_mesh , & state . camera , state . controls , & state . next_mouse , state . time ) ;
b_refresh_walk_target = false ;
2023-09-20 00:53:29 +00:00
} ,
2024-01-30 06:37:05 +00:00
PhysicsInputInstruction ::Idle = > { b_refresh_walk_target = false ; } , //literally idle!
2023-09-20 00:53:29 +00:00
}
2024-01-30 06:37:05 +00:00
if b_refresh_walk_target {
let a = refresh_walk_target ( state , data ) ;
if set_acceleration_cull ( & mut state . body , & mut state . touching , & data . models , & data . hitbox_mesh , a ) {
( state . move_state , state . body . acceleration ) = state . touching . get_move_state ( & state . body , & data . models , & state . style , & data . hitbox_mesh , & state . camera , state . controls , & state . next_mouse , state . time ) ;
2023-10-20 20:47:26 +00:00
}
2023-09-19 04:10:07 +00:00
}
2023-09-19 06:36:14 +00:00
} ,
2023-09-09 03:14:18 +00:00
}
2023-09-09 00:15:49 +00:00
}
2023-11-30 09:51:17 +00:00
2024-01-30 06:37:05 +00:00
#[ cfg(test) ]
mod test {
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 ( Planar64Mat3 ::from_diagonal ( Planar64Vec3 ::int ( 5 , 1 , 5 ) / 2 ) , Planar64Vec3 ::ZERO ) ) ;
let h1 = StyleModifiers ::roblox_bhop ( ) . calculate_mesh ( ) ;
let hitbox_mesh = h1 . transformed_mesh ( ) ;
let platform_mesh = h0 . transformed_mesh ( ) ;
let minkowski = model_physics ::MinkowskiMesh ::minkowski_sum ( platform_mesh , hitbox_mesh ) ;
let collision = minkowski . predict_collision_in ( & relative_body , Time ::MAX ) ;
assert_eq! ( collision . map ( | tup | tup . 1 ) , expected_collision_time , " Incorrect time of collision " ) ;
}
fn test_collision_rotated ( relative_body :Body , expected_collision_time :Option < Time > ) {
let h0 = HitboxMesh ::new ( PhysicsMesh ::unit_cube ( ) ,
integer ::Planar64Affine3 ::new (
integer ::Planar64Mat3 ::from_cols (
Planar64Vec3 ::int ( 5 , 0 , 1 ) / 2 ,
Planar64Vec3 ::int ( 0 , 1 , 0 ) / 2 ,
Planar64Vec3 ::int ( - 1 , 0 , 5 ) / 2 ,
) ,
Planar64Vec3 ::ZERO ,
)
) ;
let h1 = StyleModifiers ::roblox_bhop ( ) . calculate_mesh ( ) ;
let hitbox_mesh = h1 . transformed_mesh ( ) ;
let platform_mesh = h0 . transformed_mesh ( ) ;
let minkowski = model_physics ::MinkowskiMesh ::minkowski_sum ( platform_mesh , hitbox_mesh ) ;
let collision = minkowski . predict_collision_in ( & relative_body , Time ::MAX ) ;
assert_eq! ( collision . map ( | tup | tup . 1 ) , expected_collision_time , " Incorrect time of collision " ) ;
}
fn test_collision ( relative_body :Body , expected_collision_time :Option < Time > ) {
test_collision_axis_aligned ( relative_body . clone ( ) , expected_collision_time ) ;
test_collision_rotated ( relative_body , expected_collision_time ) ;
}
#[ test ]
fn test_collision_degenerate ( ) {
test_collision ( Body ::new (
Planar64Vec3 ::int ( 0 , 5 , 0 ) ,
Planar64Vec3 ::int ( 0 , - 1 , 0 ) ,
2023-11-30 09:51:17 +00:00
Planar64Vec3 ::ZERO ,
2024-01-30 06:37:05 +00:00
Time ::ZERO
) , Some ( Time ::from_secs ( 2 ) ) ) ;
}
#[ test ]
fn test_collision_degenerate_east ( ) {
test_collision ( Body ::new (
Planar64Vec3 ::int ( 3 , 5 , 0 ) ,
Planar64Vec3 ::int ( 0 , - 1 , 0 ) ,
Planar64Vec3 ::ZERO ,
Time ::ZERO
) , Some ( Time ::from_secs ( 2 ) ) ) ;
}
#[ test ]
fn test_collision_degenerate_south ( ) {
test_collision ( Body ::new (
Planar64Vec3 ::int ( 0 , 5 , 3 ) ,
Planar64Vec3 ::int ( 0 , - 1 , 0 ) ,
Planar64Vec3 ::ZERO ,
Time ::ZERO
) , Some ( Time ::from_secs ( 2 ) ) ) ;
}
#[ test ]
fn test_collision_degenerate_west ( ) {
test_collision ( Body ::new (
Planar64Vec3 ::int ( - 3 , 5 , 0 ) ,
Planar64Vec3 ::int ( 0 , - 1 , 0 ) ,
Planar64Vec3 ::ZERO ,
Time ::ZERO
) , Some ( Time ::from_secs ( 2 ) ) ) ;
}
#[ test ]
fn test_collision_degenerate_north ( ) {
test_collision ( Body ::new (
Planar64Vec3 ::int ( 0 , 5 , - 3 ) ,
Planar64Vec3 ::int ( 0 , - 1 , 0 ) ,
Planar64Vec3 ::ZERO ,
Time ::ZERO
) , Some ( Time ::from_secs ( 2 ) ) ) ;
}
#[ test ]
fn test_collision_parabola_edge_east_from_west ( ) {
test_collision ( VirtualBody ::relative ( & Body ::default ( ) , & Body ::new (
Planar64Vec3 ::int ( 3 , 3 , 0 ) ,
Planar64Vec3 ::int ( 100 , - 1 , 0 ) ,
Planar64Vec3 ::int ( 0 , - 1 , 0 ) ,
Time ::ZERO
) ) . body ( Time ::from_secs ( - 1 ) ) , Some ( Time ::from_secs ( 0 ) ) ) ;
}
#[ test ]
fn test_collision_parabola_edge_south_from_north ( ) {
test_collision ( VirtualBody ::relative ( & Body ::default ( ) , & Body ::new (
Planar64Vec3 ::int ( 0 , 3 , 3 ) ,
Planar64Vec3 ::int ( 0 , - 1 , 100 ) ,
Planar64Vec3 ::int ( 0 , - 1 , 0 ) ,
Time ::ZERO
) ) . body ( Time ::from_secs ( - 1 ) ) , Some ( Time ::from_secs ( 0 ) ) ) ;
}
#[ test ]
fn test_collision_parabola_edge_west_from_east ( ) {
test_collision ( VirtualBody ::relative ( & Body ::default ( ) , & Body ::new (
Planar64Vec3 ::int ( - 3 , 3 , 0 ) ,
Planar64Vec3 ::int ( - 100 , - 1 , 0 ) ,
Planar64Vec3 ::int ( 0 , - 1 , 0 ) ,
Time ::ZERO
) ) . body ( Time ::from_secs ( - 1 ) ) , Some ( Time ::from_secs ( 0 ) ) ) ;
}
#[ test ]
fn test_collision_parabola_edge_north_from_south ( ) {
test_collision ( VirtualBody ::relative ( & Body ::default ( ) , & Body ::new (
Planar64Vec3 ::int ( 0 , 3 , - 3 ) ,
Planar64Vec3 ::int ( 0 , - 1 , - 100 ) ,
Planar64Vec3 ::int ( 0 , - 1 , 0 ) ,
Time ::ZERO
) ) . body ( Time ::from_secs ( - 1 ) ) , Some ( Time ::from_secs ( 0 ) ) ) ;
}
#[ test ]
fn test_collision_parabola_edge_north_from_ne ( ) {
test_collision ( VirtualBody ::relative ( & Body ::default ( ) , & Body ::new (
Planar64Vec3 ::int ( 0 , 6 , - 7 ) / 2 ,
Planar64Vec3 ::int ( - 10 , - 1 , 1 ) ,
Planar64Vec3 ::int ( 0 , - 1 , 0 ) ,
Time ::ZERO
) ) . body ( Time ::from_secs ( - 1 ) ) , Some ( Time ::from_secs ( 0 ) ) ) ;
}
#[ test ]
fn test_collision_parabola_edge_north_from_nw ( ) {
test_collision ( VirtualBody ::relative ( & Body ::default ( ) , & Body ::new (
Planar64Vec3 ::int ( 0 , 6 , - 7 ) / 2 ,
Planar64Vec3 ::int ( 10 , - 1 , 1 ) ,
Planar64Vec3 ::int ( 0 , - 1 , 0 ) ,
Time ::ZERO
) ) . body ( Time ::from_secs ( - 1 ) ) , Some ( Time ::from_secs ( 0 ) ) ) ;
}
#[ test ]
fn test_collision_parabola_edge_east_from_se ( ) {
test_collision ( VirtualBody ::relative ( & Body ::default ( ) , & Body ::new (
Planar64Vec3 ::int ( 7 , 6 , 0 ) / 2 ,
Planar64Vec3 ::int ( - 1 , - 1 , - 10 ) ,
Planar64Vec3 ::int ( 0 , - 1 , 0 ) ,
Time ::ZERO
) ) . body ( Time ::from_secs ( - 1 ) ) , Some ( Time ::from_secs ( 0 ) ) ) ;
}
#[ test ]
fn test_collision_parabola_edge_east_from_ne ( ) {
test_collision ( VirtualBody ::relative ( & Body ::default ( ) , & Body ::new (
Planar64Vec3 ::int ( 7 , 6 , 0 ) / 2 ,
Planar64Vec3 ::int ( - 1 , - 1 , 10 ) ,
Planar64Vec3 ::int ( 0 , - 1 , 0 ) ,
Time ::ZERO
) ) . body ( Time ::from_secs ( - 1 ) ) , Some ( Time ::from_secs ( 0 ) ) ) ;
}
#[ test ]
fn test_collision_parabola_edge_south_from_se ( ) {
test_collision ( VirtualBody ::relative ( & Body ::default ( ) , & Body ::new (
Planar64Vec3 ::int ( 0 , 6 , 7 ) / 2 ,
Planar64Vec3 ::int ( - 10 , - 1 , - 1 ) ,
Planar64Vec3 ::int ( 0 , - 1 , 0 ) ,
Time ::ZERO
) ) . body ( Time ::from_secs ( - 1 ) ) , Some ( Time ::from_secs ( 0 ) ) ) ;
}
#[ test ]
fn test_collision_parabola_edge_south_from_sw ( ) {
test_collision ( VirtualBody ::relative ( & Body ::default ( ) , & Body ::new (
Planar64Vec3 ::int ( 0 , 6 , 7 ) / 2 ,
Planar64Vec3 ::int ( 10 , - 1 , - 1 ) ,
Planar64Vec3 ::int ( 0 , - 1 , 0 ) ,
Time ::ZERO
) ) . body ( Time ::from_secs ( - 1 ) ) , Some ( Time ::from_secs ( 0 ) ) ) ;
}
#[ test ]
fn test_collision_parabola_edge_west_from_se ( ) {
test_collision ( VirtualBody ::relative ( & Body ::default ( ) , & Body ::new (
Planar64Vec3 ::int ( - 7 , 6 , 0 ) / 2 ,
Planar64Vec3 ::int ( 1 , - 1 , - 10 ) ,
Planar64Vec3 ::int ( 0 , - 1 , 0 ) ,
Time ::ZERO
) ) . body ( Time ::from_secs ( - 1 ) ) , Some ( Time ::from_secs ( 0 ) ) ) ;
}
#[ test ]
fn test_collision_parabola_edge_west_from_ne ( ) {
test_collision ( VirtualBody ::relative ( & Body ::default ( ) , & Body ::new (
Planar64Vec3 ::int ( - 7 , 6 , 0 ) / 2 ,
Planar64Vec3 ::int ( 1 , - 1 , 10 ) ,
Planar64Vec3 ::int ( 0 , - 1 , 0 ) ,
Time ::ZERO
) ) . body ( Time ::from_secs ( - 1 ) ) , Some ( Time ::from_secs ( 0 ) ) ) ;
}
#[ test ]
fn test_collision_oblique ( ) {
test_collision ( Body ::new (
Planar64Vec3 ::int ( 0 , 5 , 0 ) ,
Planar64Vec3 ::int ( 1 , - 64 , 2 ) / 64 ,
Planar64Vec3 ::ZERO ,
Time ::ZERO
) , Some ( Time ::from_secs ( 2 ) ) ) ;
}
#[ test ]
fn zoom_hit_nothing ( ) {
test_collision ( Body ::new (
Planar64Vec3 ::int ( 0 , 10 , 0 ) ,
Planar64Vec3 ::int ( 1 , 0 , 0 ) ,
Planar64Vec3 ::int ( 0 , 1 , 0 ) ,
Time ::ZERO
) , None ) ;
}
#[ test ]
fn already_inside_hit_nothing ( ) {
test_collision ( Body ::new (
Planar64Vec3 ::ZERO ,
Planar64Vec3 ::int ( 1 , 0 , 0 ) ,
Planar64Vec3 ::int ( 0 , 1 , 0 ) ,
Time ::ZERO
) , None ) ;
}
2023-11-30 09:51:17 +00:00
}