2024-07-30 20:34:34 +00:00
use std ::collections ::{ HashMap , HashSet } ;
2024-01-30 06:37:05 +00:00
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 ::model ::{ MeshId , ModelId } ;
2024-02-16 08:19:44 +00:00
use strafesnet_common ::gameplay_attributes ::{ self , CollisionAttributesId } ;
use strafesnet_common ::gameplay_modes ::{ self , StageId } ;
2024-01-30 06:37:05 +00:00
use strafesnet_common ::gameplay_style ::{ self , StyleModifiers } ;
2024-02-16 08:19:44 +00:00
use strafesnet_common ::controls_bitflag ::Controls ;
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 ) ,
2024-02-16 08:19:44 +00:00
SetSensitivity ( Ratio64Vec2 ) ,
2023-10-05 03:04:04 +00:00
}
#[ 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.
2024-02-16 08:19:44 +00:00
PracticeFly ,
2023-09-09 00:00:50 +00:00
}
2023-10-19 00:17:21 +00:00
2024-02-16 08:19:44 +00:00
#[ derive(Clone,Copy,Debug,Default,Hash) ]
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
}
}
2024-02-16 08:19:44 +00:00
#[ derive(Clone,Debug,Default) ]
pub struct InputState {
mouse :MouseState ,
next_mouse :MouseState ,
controls :strafesnet_common ::controls_bitflag ::Controls ,
}
impl InputState {
pub const fn get_next_mouse ( & self ) ->& MouseState {
& self . next_mouse
}
fn set_next_mouse ( & mut self , next_mouse :MouseState ) {
2024-07-31 02:36:03 +00:00
//I like your functions magic language
self . mouse = std ::mem ::replace ( & mut self . next_mouse , next_mouse ) ;
//equivalently:
//(self.next_mouse,self.mouse)=(next_mouse,self.next_mouse.clone());
2024-02-16 08:19:44 +00:00
}
fn replace_mouse ( & mut self , mouse :MouseState , next_mouse :MouseState ) {
( self . next_mouse , self . mouse ) = ( next_mouse , mouse ) ;
}
fn set_control ( & mut self , control :Controls , state :bool ) {
self . controls . set ( control , state )
}
fn time_delta ( & self ) ->Time {
self . next_mouse . time - self . mouse . time
}
fn mouse_delta ( & self ) ->glam ::IVec2 {
self . next_mouse . pos - self . mouse . pos
}
fn lerp_delta ( & self , time :Time ) ->glam ::IVec2 {
//these are deltas
let dm = self . mouse_delta ( ) . as_i64vec2 ( ) ;
let t = ( time - self . mouse . time ) . nanos ( ) ;
let dt = self . time_delta ( ) . nanos ( ) ;
( ( dm * t ) / dt ) . as_ivec2 ( )
}
}
#[ derive(Clone,Debug) ]
2023-11-30 09:51:17 +00:00
enum JumpDirection {
Exactly ( Planar64Vec3 ) ,
FromContactNormal ,
}
2024-02-16 08:19:44 +00:00
impl JumpDirection {
fn direction ( & self , models :& PhysicsModels , hitbox_mesh :& HitboxMesh , contact :& ContactCollision ) ->Planar64Vec3 {
match self {
JumpDirection ::FromContactNormal = > contact_normal ( models , hitbox_mesh , contact ) ,
& JumpDirection ::Exactly ( dir ) = > dir ,
}
}
2023-09-19 06:36:14 +00:00
}
2024-02-16 08:19:44 +00:00
#[ derive(Clone,Debug) ]
enum TransientAcceleration {
Reached ,
Reachable {
acceleration :Planar64Vec3 ,
time :Time ,
} ,
//walk target will never be reached
Unreachable {
acceleration :Planar64Vec3 ,
}
2023-09-19 04:10:07 +00:00
}
2024-02-16 08:19:44 +00:00
#[ derive(Clone,Debug) ]
struct ContactMoveState {
2023-11-30 09:51:17 +00:00
jump_direction :JumpDirection ,
contact :ContactCollision ,
2024-02-16 08:19:44 +00:00
target :TransientAcceleration ,
}
impl TransientAcceleration {
fn with_target_diff ( target_diff :Planar64Vec3 , accel :Planar64 , time :Time ) ->Self {
2023-10-17 07:17:54 +00:00
if target_diff = = Planar64Vec3 ::ZERO {
2024-02-16 08:19:44 +00:00
TransientAcceleration ::Reached
2023-10-17 07:17:54 +00:00
} else {
//normal friction acceleration is clippedAcceleration.dot(normal)*friction
2024-02-16 08:19:44 +00:00
TransientAcceleration ::Reachable {
acceleration :target_diff . with_length ( accel ) ,
time :time + Time ::from ( target_diff . length ( ) / accel )
}
}
}
fn ground ( walk_settings :& gameplay_style ::WalkSettings , body :& Body , gravity :Planar64Vec3 , target_velocity :Planar64Vec3 ) ->Self {
let target_diff = target_velocity - body . velocity ;
//precalculate accel
let accel = walk_settings . accel ( target_diff , gravity ) ;
Self ::with_target_diff ( target_diff , accel , body . time )
}
fn ladder ( ladder_settings :& gameplay_style ::LadderSettings , body :& Body , gravity :Planar64Vec3 , target_velocity :Planar64Vec3 ) ->Self {
let target_diff = target_velocity - body . velocity ;
let accel = ladder_settings . accel ( target_diff , gravity ) ;
Self ::with_target_diff ( target_diff , accel , body . time )
}
fn acceleration ( & self ) ->Planar64Vec3 {
match self {
TransientAcceleration ::Reached = > Planar64Vec3 ::ZERO ,
& TransientAcceleration ::Reachable { acceleration , time :_ } = > acceleration ,
& TransientAcceleration ::Unreachable { acceleration } = > acceleration ,
2023-09-19 04:10:07 +00:00
}
}
}
2024-02-16 08:19:44 +00:00
impl ContactMoveState {
fn ground ( walk_settings :& gameplay_style ::WalkSettings , body :& Body , gravity :Planar64Vec3 , target_velocity :Planar64Vec3 , contact :ContactCollision ) ->Self {
Self {
target :TransientAcceleration ::ground ( walk_settings , body , gravity , target_velocity ) ,
2023-11-30 09:51:17 +00:00
contact ,
jump_direction :JumpDirection ::Exactly ( Planar64Vec3 ::Y ) ,
2024-02-16 08:19:44 +00:00
}
2023-10-17 07:17:54 +00:00
}
2024-02-16 08:19:44 +00:00
fn ladder ( ladder_settings :& gameplay_style ::LadderSettings , body :& Body , gravity :Planar64Vec3 , target_velocity :Planar64Vec3 , contact :ContactCollision ) ->Self {
Self { //,style,velocity,normal,style.ladder_speed,style.ladder_accel
target :TransientAcceleration ::ladder ( ladder_settings , body , gravity , target_velocity ) ,
2023-11-30 09:51:17 +00:00
contact ,
jump_direction :JumpDirection ::FromContactNormal ,
2024-02-16 08:19:44 +00:00
}
2023-10-17 07:17:54 +00:00
}
}
2024-02-16 08:19:44 +00:00
fn ground_things ( walk_settings :& gameplay_style ::WalkSettings , contact :& ContactCollision , touching :& TouchingState , models :& PhysicsModels , hitbox_mesh :& HitboxMesh , style :& StyleModifiers , camera :& PhysicsCamera , input_state :& InputState ) ->( Planar64Vec3 , Planar64Vec3 ) {
let normal = contact_normal ( models , hitbox_mesh , contact ) ;
let gravity = touching . base_acceleration ( models , style , camera , input_state ) ;
let control_dir = style . get_y_control_dir ( camera , input_state . controls ) ;
let mut target_velocity = walk_settings . get_walk_target_velocity ( control_dir , normal ) ;
touching . constrain_velocity ( models , hitbox_mesh , & mut target_velocity ) ;
( gravity , target_velocity )
}
fn ladder_things ( ladder_settings :& gameplay_style ::LadderSettings , contact :& ContactCollision , touching :& TouchingState , models :& PhysicsModels , hitbox_mesh :& HitboxMesh , style :& StyleModifiers , camera :& PhysicsCamera , input_state :& InputState ) ->( Planar64Vec3 , Planar64Vec3 ) {
let normal = contact_normal ( models , hitbox_mesh , contact ) ;
let gravity = touching . base_acceleration ( models , style , camera , input_state ) ;
let control_dir = style . get_y_control_dir ( camera , input_state . controls ) ;
let mut target_velocity = ladder_settings . get_ladder_target_velocity ( control_dir , normal ) ;
touching . constrain_velocity ( models , hitbox_mesh , & mut target_velocity ) ;
( gravity , target_velocity )
}
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-07-31 05:12:21 +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
}
}
2024-02-16 08:19:44 +00:00
#[ derive(Clone,Copy,Debug) ]
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
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 ;
2024-02-16 08:19:44 +00:00
pub fn move_mouse ( & mut self , mouse_delta :glam ::IVec2 ) {
let mut unclamped_mouse_pos = self . clamped_mouse_pos + mouse_delta ;
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
}
2024-02-16 08:19:44 +00:00
pub fn simulate_move_angles ( & self , mouse_delta :glam ::IVec2 ) ->glam ::Vec2 {
let a = - self . sensitivity . mul_int ( ( self . clamped_mouse_pos + mouse_delta ) . as_i64vec2 ( ) ) ;
2023-09-27 09:12:20 +00:00
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 ( ) ) ;
}
2024-02-16 08:19:44 +00:00
#[ inline ]
fn get_rotation ( & self , mouse_pos :glam ::IVec2 ) ->Planar64Mat3 {
let a = - self . sensitivity . mul_int ( mouse_pos . as_i64vec2 ( ) ) ;
2023-10-17 06:20:38 +00:00
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 )
}
2024-02-16 08:19:44 +00:00
fn rotation ( & self ) ->Planar64Mat3 {
self . get_rotation ( self . clamped_mouse_pos )
}
fn simulate_move_rotation ( & self , mouse_delta :glam ::IVec2 ) ->Planar64Mat3 {
self . get_rotation ( self . clamped_mouse_pos + mouse_delta )
}
fn get_rotation_y ( & self , mouse_pos_x :i32 ) ->Planar64Mat3 {
let ax = - self . sensitivity . x . mul_int ( mouse_pos_x as i64 ) ;
2023-09-27 09:12:20 +00:00
Planar64Mat3 ::from_rotation_y ( Angle32 ::wrap_from_i64 ( ax ) )
2023-09-20 00:53:29 +00:00
}
2024-02-16 08:19:44 +00:00
fn rotation_y ( & self ) ->Planar64Mat3 {
self . get_rotation_y ( self . clamped_mouse_pos . x )
}
fn simulate_move_rotation_y ( & self , mouse_delta_x :i32 ) ->Planar64Mat3 {
self . get_rotation_y ( self . clamped_mouse_pos . x + mouse_delta_x )
}
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 ,
clamped_mouse_pos :glam ::IVec2 ::ZERO ,
}
}
}
2024-01-30 06:37:05 +00:00
mod gameplay {
use super ::{ gameplay_modes , HashSet , HashMap , ModelId } ;
2024-02-16 08:19:44 +00:00
#[ derive(Clone,Debug) ]
2024-01-30 06:37:05 +00:00
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
}
}
}
2024-02-16 08:19:44 +00:00
#[ derive(Clone,Debug) ]
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 {
2024-02-16 08:19:44 +00:00
fn get_control ( & self , control :Controls , controls :Controls ) ->bool ;
fn get_control_dir ( & self , controls :Controls ) ->Planar64Vec3 ;
fn get_y_control_dir ( & self , camera :& PhysicsCamera , controls :Controls ) ->Planar64Vec3 ;
fn get_propulsion_control_dir ( & self , camera :& PhysicsCamera , controls :Controls ) ->Planar64Vec3 ;
2024-01-30 06:37:05 +00:00
fn calculate_mesh ( & self ) ->HitboxMesh ;
}
impl StyleHelper for StyleModifiers {
2024-02-16 08:19:44 +00:00
fn get_control ( & self , control :Controls , controls :Controls ) ->bool {
controls . intersection ( self . controls_mask ) . contains ( control )
2023-10-31 21:11:39 +00:00
}
2024-02-16 08:19:44 +00:00
fn get_control_dir ( & self , controls :Controls ) ->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
2024-02-16 08:19:44 +00:00
let controls = controls . intersection ( self . controls_mask ) ;
if controls . contains ( Controls ::MoveForward ) {
2023-10-03 23:52:45 +00:00
control_dir + = Self ::FORWARD_DIR ;
}
2024-02-16 08:19:44 +00:00
if controls . contains ( Controls ::MoveBackward ) {
2023-09-27 09:12:20 +00:00
control_dir - = Self ::FORWARD_DIR ;
2023-10-03 23:52:45 +00:00
}
2024-02-16 08:19:44 +00:00
if controls . contains ( Controls ::MoveLeft ) {
2023-09-27 09:12:20 +00:00
control_dir - = Self ::RIGHT_DIR ;
2023-10-03 23:52:45 +00:00
}
2024-02-16 08:19:44 +00:00
if controls . contains ( Controls ::MoveRight ) {
2023-10-03 23:52:45 +00:00
control_dir + = Self ::RIGHT_DIR ;
}
2024-02-16 08:19:44 +00:00
if controls . contains ( Controls ::MoveUp ) {
2023-10-03 23:52:45 +00:00
control_dir + = Self ::UP_DIR ;
}
2024-02-16 08:19:44 +00:00
if controls . contains ( Controls ::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
2024-02-16 08:19:44 +00:00
fn get_y_control_dir ( & self , camera :& PhysicsCamera , controls :Controls ) ->Planar64Vec3 {
camera . rotation_y ( ) * self . get_control_dir ( controls )
2023-10-17 07:17:54 +00:00
}
2024-02-16 08:19:44 +00:00
fn get_propulsion_control_dir ( & self , camera :& PhysicsCamera , controls :Controls ) ->Planar64Vec3 {
//don't interpolate this! discrete mouse movement, constant acceleration
camera . rotation ( ) * self . get_control_dir ( controls )
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
}
2024-02-16 08:19:44 +00:00
#[ derive(Clone,Debug) ]
2023-10-13 02:44:46 +00:00
enum MoveState {
Air ,
2024-02-16 08:19:44 +00:00
Walk ( ContactMoveState ) ,
Ladder ( ContactMoveState ) ,
2023-10-13 02:44:46 +00:00
Water ,
2024-02-16 08:19:44 +00:00
Fly ,
}
impl MoveState {
//call this after state.move_state is changed
fn apply_enum ( & self , body :& mut Body , touching :& TouchingState , models :& PhysicsModels , hitbox_mesh :& HitboxMesh , style :& StyleModifiers , camera :& PhysicsCamera , input_state :& InputState ) {
match self {
MoveState ::Fly = > body . acceleration = Planar64Vec3 ::ZERO ,
MoveState ::Air = > {
//calculate base acceleration
let a = touching . base_acceleration ( models , style , camera , input_state ) ;
//set_acceleration clips according to contacts
set_acceleration ( body , touching , models , hitbox_mesh , a ) ;
} ,
_ = > ( ) ,
}
}
//function to coerce &mut self into &self
fn apply_to_body ( & self , body :& mut Body , touching :& TouchingState , models :& PhysicsModels , hitbox_mesh :& HitboxMesh , style :& StyleModifiers , camera :& PhysicsCamera , input_state :& InputState ) {
match self {
MoveState ::Air = > ( ) ,
MoveState ::Water = > ( ) ,
MoveState ::Fly = > {
//set velocity according to current control state
let v = style . get_propulsion_control_dir ( camera , input_state . controls ) * 80 ;
//set_velocity clips velocity according to current touching state
set_velocity ( body , touching , models , hitbox_mesh , v ) ;
} ,
MoveState ::Walk ( walk_state )
| MoveState ::Ladder ( walk_state )
= > {
//accelerate towards walk target or do nothing
let a = walk_state . target . acceleration ( ) ;
set_acceleration ( body , touching , models , hitbox_mesh , a ) ;
} ,
}
}
/// changes the move state
fn apply_input ( & mut self , body :& Body , touching :& TouchingState , models :& PhysicsModels , hitbox_mesh :& HitboxMesh , style :& StyleModifiers , camera :& PhysicsCamera , input_state :& InputState ) {
match self {
MoveState ::Fly
| MoveState ::Air
| MoveState ::Water = > ( ) ,
MoveState ::Walk ( ContactMoveState { target , contact , jump_direction :_ } ) = > {
if let Some ( walk_settings ) = & style . walk {
let ( gravity , target_velocity ) = ground_things ( walk_settings , contact , touching , models , hitbox_mesh , style , camera , input_state ) ;
* target = TransientAcceleration ::ground ( walk_settings , body , gravity , target_velocity ) ;
} else {
panic! ( " ContactMoveState exists in style which does not allow walking! " ) ;
}
} ,
MoveState ::Ladder ( ContactMoveState { target , contact , jump_direction :_ } ) = > {
if let Some ( ladder_settings ) = & style . ladder {
let ( gravity , target_velocity ) = ladder_things ( ladder_settings , contact , touching , models , hitbox_mesh , style , camera , input_state ) ;
* target = TransientAcceleration ::ladder ( ladder_settings , body , gravity , target_velocity ) ;
} else {
panic! ( " ContactMoveState exists in style which does not allow walking! " ) ;
}
} ,
}
}
fn get_walk_state ( & self ) ->Option < & ContactMoveState > {
match self {
MoveState ::Walk ( walk_state )
| MoveState ::Ladder ( walk_state )
= > Some ( walk_state ) ,
MoveState ::Air
| MoveState ::Water
| MoveState ::Fly
= > None ,
}
}
fn next_move_instruction ( & self , strafe :& Option < gameplay_style ::StrafeSettings > , time :Time ) ->Option < TimedInstruction < PhysicsInstruction > > {
//check if you have a valid walk state and create an instruction
match self {
MoveState ::Walk ( walk_state ) | MoveState ::Ladder ( walk_state ) = > match & walk_state . target {
& TransientAcceleration ::Reachable { acceleration :_ , time } = > Some ( TimedInstruction {
time ,
instruction :PhysicsInstruction ::ReachWalkTargetVelocity
} ) ,
TransientAcceleration ::Unreachable { acceleration :_ }
| TransientAcceleration ::Reached
= > None ,
}
MoveState ::Air = > strafe . as_ref ( ) . map ( | strafe | {
TimedInstruction {
time :strafe . next_tick ( time ) ,
//only poll the physics if there is a before and after mouse event
instruction :PhysicsInstruction ::StrafeTick
}
} ) ,
MoveState ::Water = > None , //TODO
MoveState ::Fly = > None ,
}
}
2023-10-13 02:44:46 +00:00
}
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 ,
2024-02-16 08:19:44 +00:00
mouse_pos :glam ::IVec2 ,
2023-10-05 03:04:04 +00:00
}
impl PhysicsOutputState {
2024-02-16 08:19:44 +00:00
pub fn extrapolate ( & self , mouse :MouseState ) ->( glam ::Vec3 , glam ::Vec2 ) {
( ( self . body . extrapolated_position ( mouse . time ) + self . camera_offset ) . into ( ) , self . camera . simulate_move_angles ( mouse . pos - self . 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
}
2024-02-16 08:19:44 +00:00
#[ derive(Debug,Clone,Copy,Eq,Hash,PartialEq) ]
pub 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) ]
2024-02-16 08:19:44 +00:00
pub 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) ]
2024-02-16 08:19:44 +00:00
pub enum Collision {
2023-11-30 09:51:17 +00:00
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
}
}
2024-02-16 08:19:44 +00:00
#[ derive(Clone,Debug,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
}
2024-02-16 08:19:44 +00:00
fn base_acceleration ( & self , models :& PhysicsModels , style :& StyleModifiers , camera :& PhysicsCamera , input_state :& InputState ) ->Planar64Vec3 {
2023-11-30 09:51:17 +00:00
let mut a = style . gravity ;
2024-02-16 08:19:44 +00:00
if let Some ( rocket_settings ) = & style . rocket {
a + = rocket_settings . acceleration ( style . get_propulsion_control_dir ( camera , input_state . controls ) ) ;
2023-11-30 09:51:17 +00:00
}
//add accelerators
for contact in & self . contacts {
2024-01-30 06:37:05 +00:00
match models . attr ( contact . convex_mesh_id . model_id ) {
2024-02-16 08:19:44 +00:00
PhysicsCollisionAttributes ::Contact { contacting :_ , general } = > {
2023-11-30 09:51:17 +00:00
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 ) {
2024-02-16 08:19:44 +00:00
PhysicsCollisionAttributes ::Intersect { intersecting :_ , general } = > {
2023-11-30 09:51:17 +00:00
match & general . accelerator {
Some ( accelerator ) = > a + = accelerator . acceleration ,
None = > ( ) ,
}
} ,
_ = > panic! ( " impossible touching state " ) ,
}
}
2024-02-16 08:19:44 +00:00
//TODO: add water
2023-11-30 09:51:17 +00:00
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 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-02-16 08:19:44 +00:00
#[ derive(Clone,Debug) ]
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
2024-02-16 08:19:44 +00:00
input_state :InputState ,
2024-01-30 06:37:05 +00:00
//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 ( ) ,
2024-02-16 08:19:44 +00:00
input_state :InputState ::default ( ) ,
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 {
2024-02-16 08:19:44 +00:00
fn clear ( & mut self ) {
2023-10-13 02:44:46 +00:00
self . touching . clear ( ) ;
2023-10-05 03:04:04 +00:00
}
2024-02-16 08:19:44 +00:00
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
}
2024-02-16 08:19:44 +00:00
fn next_move_instruction ( & self ) ->Option < TimedInstruction < PhysicsInstruction > > {
self . move_state . next_move_instruction ( & self . style . strafe , self . time )
}
//lmao idk this is convenient
fn apply_enum_and_input_and_body ( & mut self , data :& PhysicsData ) {
self . move_state . apply_enum ( & mut self . body , & self . touching , & data . models , & data . hitbox_mesh , & self . style , & self . camera , & self . input_state ) ;
self . move_state . apply_input ( & self . body , & self . touching , & data . models , & data . hitbox_mesh , & self . style , & self . camera , & self . input_state ) ;
self . move_state . apply_to_body ( & mut self . body , & self . touching , & data . models , & data . hitbox_mesh , & self . style , & self . camera , & self . input_state ) ;
}
fn apply_enum_and_body ( & mut self , data :& PhysicsData ) {
self . move_state . apply_enum ( & mut self . body , & self . touching , & data . models , & data . hitbox_mesh , & self . style , & self . camera , & self . input_state ) ;
self . move_state . apply_to_body ( & mut self . body , & self . touching , & data . models , & data . hitbox_mesh , & self . style , & self . camera , & self . input_state ) ;
}
fn apply_input_and_body ( & mut self , data :& PhysicsData ) {
self . move_state . apply_input ( & self . body , & self . touching , & data . models , & data . hitbox_mesh , & self . style , & self . camera , & self . input_state ) ;
self . move_state . apply_to_body ( & mut self . body , & self . touching , & data . models , & data . hitbox_mesh , & self . style , & self . camera , & self . input_state ) ;
}
fn set_move_state ( & mut self , data :& PhysicsData , move_state :MoveState ) {
self . move_state = move_state ;
//this function call reads the above state that was just set
self . apply_enum_and_body ( data ) ;
}
fn cull_velocity ( & mut self , data :& PhysicsData , velocity :Planar64Vec3 ) {
//TODO: be more precise about contacts
if set_velocity_cull ( & mut self . body , & mut self . touching , & data . models , & data . hitbox_mesh , velocity ) {
//TODO do better
match self . move_state . get_walk_state ( ) {
//did you stop touching the thing you were walking on?
Some ( walk_state ) = > if ! self . touching . contacts . contains ( & walk_state . contact ) {
self . set_move_state ( data , MoveState ::Air ) ;
} ,
None = > self . apply_enum_and_body ( data ) ,
2023-10-20 20:46:59 +00:00
}
2024-02-16 08:19:44 +00:00
}
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-09-08 18:33:20 +00:00
}
2024-02-16 08:19:44 +00:00
2024-01-30 06:37:05 +00:00
#[ derive(Default) ]
pub struct PhysicsContext {
2024-02-16 08:19:44 +00:00
state :PhysicsState , //this captures the entire state of the physics.
2024-01-30 06:37:05 +00:00
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 {
2024-02-16 08:19:44 +00:00
pub fn clear ( & mut self ) {
self . state . clear ( ) ;
}
pub fn load_user_settings ( & mut self , user_settings :& crate ::settings ::UserSettings ) {
self . process_instruction ( TimedInstruction {
time :self . state . time ,
instruction :PhysicsInstruction ::SetSensitivity ( user_settings . calculate_sensitivity ( ) ) ,
} ) ;
}
2024-01-30 06:37:05 +00:00
pub fn spawn ( & mut self ) {
2024-02-16 08:19:44 +00:00
self . process_instruction ( TimedInstruction {
2024-01-30 06:37:05 +00:00
time :self . state . time ,
2024-02-16 08:19:44 +00:00
instruction :PhysicsInstruction ::Input ( PhysicsInputInstruction ::Reset ) ,
2024-01-30 06:37:05 +00:00
} ) ;
}
2024-02-16 08:19:44 +00:00
pub const fn output ( & self ) ->PhysicsOutputState {
PhysicsOutputState {
body :self . state . body ,
camera :self . state . camera ,
camera_offset :self . state . style . camera_offset ,
mouse_pos :self . state . input_state . mouse . pos ,
}
}
pub const fn get_next_mouse ( & self ) ->& MouseState {
self . state . input_state . get_next_mouse ( )
}
2024-01-30 06:37:05 +00:00
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 ) | {
2024-02-16 14:04:24 +00:00
//TODO: use .entry().or_insert_with(||{
2024-01-30 06:37:05 +00:00
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 | {
2024-02-14 07:30:05 +00:00
match PhysicsMesh ::try_from ( mesh ) {
Ok ( physics_mesh ) = > {
let mesh_id = PhysicsMeshId ::new ( used_meshes . len ( ) as u32 ) ;
used_meshes . push ( physics_mesh ) ;
physics_mesh_id_from_model_mesh_id . insert ( model . mesh , mesh_id ) ;
Some ( mesh_id )
} ,
Err ( e ) = > {
println! ( " Failed to build PhysicsMesh: {e} " ) ;
None
}
}
2024-01-30 06:37:05 +00:00
} ) {
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
2024-02-16 08:19:44 +00:00
fn run ( & mut self , time_limit :Time ) {
2024-01-30 06:37:05 +00:00
//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
}
}
2024-02-16 08:19:44 +00:00
pub fn run_input_instruction ( & mut self , instruction :TimedInstruction < PhysicsInputInstruction > ) {
self . run ( instruction . time ) ;
self . process_instruction ( TimedInstruction {
time :instruction . time ,
instruction :PhysicsInstruction ::Input ( instruction . instruction ) ,
} ) ;
2024-01-30 06:37:05 +00:00
}
2024-02-16 08:19:44 +00:00
}
2024-01-30 06:37:05 +00:00
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 ) ;
2024-07-25 20:53:45 +00:00
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
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-02-16 08:19:44 +00:00
fn set_velocity ( body :& mut Body , touching :& TouchingState , models :& PhysicsModels , hitbox_mesh :& HitboxMesh , mut v :Planar64Vec3 ) {
2024-01-30 06:37:05 +00:00
touching . constrain_velocity ( models , hitbox_mesh , & mut v ) ;
2023-11-30 09:51:17 +00:00
body . velocity = 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-02-16 08:19:44 +00:00
fn set_acceleration ( body :& mut Body , touching :& TouchingState , models :& PhysicsModels , hitbox_mesh :& HitboxMesh , mut a :Planar64Vec3 ) {
2024-01-30 06:37:05 +00:00
touching . constrain_acceleration ( models , hitbox_mesh , & mut a ) ;
2023-11-30 09:51:17 +00:00
body . acceleration = 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 > {
2024-07-31 05:12:21 +00:00
let model = models . model ( mode . get_spawn_model_id ( stage_id ) ? . into ( ) ) ;
2024-01-30 06:37:05 +00:00
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 } ) = > {
2024-07-31 05:12:21 +00:00
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-02-16 08:19:44 +00:00
| PhysicsInstruction ::StrafeTick
| PhysicsInstruction ::SetSensitivity ( _ )
= > state . advance_time ( ins . time ) ,
2023-09-19 06:36:14 +00:00
}
2023-11-30 09:51:17 +00:00
match ins . instruction {
2024-02-16 08:19:44 +00:00
PhysicsInstruction ::CollisionStart ( collision ) = > {
let convex_mesh_id = collision . convex_mesh_id ( ) ;
match ( data . models . attr ( convex_mesh_id . model_id ) , & collision ) {
( PhysicsCollisionAttributes ::Contact { contacting , general } , & Collision ::Contact ( contact ) ) = > {
let incident_velocity = state . body . velocity ;
//add to touching
state . touching . insert ( collision ) ;
//clip v
set_velocity ( & mut state . body , & state . touching , & data . models , & data . hitbox_mesh , incident_velocity ) ;
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 ) ) = > {
2024-02-16 08:19:44 +00:00
let reflected_velocity = state . body . velocity + ( state . body . velocity - incident_velocity ) * Planar64 ::raw ( elasticity as i64 + 1 ) ;
set_velocity ( & mut state . body , & state . touching , & data . models , & data . hitbox_mesh , reflected_velocity ) ;
2023-10-18 02:12:25 +00:00
} ,
2024-02-16 08:19:44 +00:00
Some ( gameplay_attributes ::ContactingBehaviour ::Ladder ( contacting_ladder ) ) = >
if let Some ( ladder_settings ) = & state . style . 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:
2024-02-16 08:19:44 +00:00
//it's a little bit different because maybe you want to chain ladders together
set_velocity ( & mut state . body , & state . touching , & data . models , & data . hitbox_mesh , Planar64Vec3 ::ZERO ) ; //model.velocity
2023-10-17 07:17:54 +00:00
}
//ladder walkstate
2024-02-16 08:19:44 +00:00
let ( gravity , target_velocity ) = ladder_things ( ladder_settings , & contact , & state . touching , & data . models , & data . hitbox_mesh , & state . style , & state . camera , & state . input_state ) ;
let walk_state = ContactMoveState ::ladder ( ladder_settings , & state . body , gravity , target_velocity , contact ) ;
state . set_move_state ( data , MoveState ::Ladder ( walk_state ) ) ;
2024-01-30 06:37:05 +00:00
} ,
Some ( gameplay_attributes ::ContactingBehaviour ::NoJump ) = > todo! ( " nyi " ) ,
2024-02-16 08:19:44 +00:00
None = > if let Some ( walk_settings ) = & state . style . walk {
if walk_settings . is_slope_walkable ( contact_normal ( & data . models , & data . hitbox_mesh , & contact ) , Planar64Vec3 ::Y ) {
//ground
let ( gravity , target_velocity ) = ground_things ( walk_settings , & contact , & state . touching , & data . models , & data . hitbox_mesh , & state . style , & state . camera , & state . input_state ) ;
let walk_state = ContactMoveState ::ground ( walk_settings , & state . body , gravity , target_velocity , contact ) ;
state . set_move_state ( data , MoveState ::Walk ( walk_state ) ) ;
}
2023-10-04 21:15:04 +00:00
} ,
}
2023-10-18 23:18:17 +00:00
//I love making functions with 10 arguments to dodge the borrow checker
2024-02-14 07:16:11 +00:00
if let Some ( mode ) = data . modes . get_mode ( state . mode_state . get_mode_id ( ) ) {
run_teleport_behaviour ( & general . wormhole , & data . models , mode , & state . style , & data . hitbox_mesh , & mut state . mode_state , & mut state . touching , & mut state . body , convex_mesh_id ) ;
}
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),
2024-02-16 08:19:44 +00:00
& gameplay_attributes ::Booster ::Velocity ( velocity ) = > {
let boosted_velocity = state . body . velocity + velocity ;
//fall through boosters
state . cull_velocity ( data , boosted_velocity ) ;
} ,
2024-01-30 06:37:05 +00:00
& gameplay_attributes ::Booster ::Energy { direction : _ , energy : _ } = > todo! ( ) ,
2023-10-18 02:12:25 +00:00
}
} ,
None = > ( ) ,
}
2024-02-16 08:19:44 +00:00
if state . style . get_control ( Controls ::Jump , state . input_state . controls ) {
if let ( Some ( jump_settings ) , Some ( walk_state ) ) = ( & state . style . jump , state . move_state . get_walk_state ( ) ) {
let jump_dir = walk_state . jump_direction . direction ( & data . models , & data . hitbox_mesh , & walk_state . contact ) ;
let jumped_velocity = jump_settings . jumped_velocity ( & state . style , jump_dir , state . body . velocity ) ;
state . cull_velocity ( data , jumped_velocity ) ;
}
}
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! ( ) ,
2024-02-16 08:19:44 +00:00
& gameplay_attributes ::SetTrajectory ::Velocity ( velocity ) = > {
state . cull_velocity ( data , velocity ) ;
} ,
2024-01-30 06:37:05 +00:00
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-02-16 08:19:44 +00:00
//doing enum to set the acceleration when surfing
//doing input_and_body to refresh the walk state if you hit a wall while accelerating
state . apply_enum_and_input_and_body ( data ) ;
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-02-16 08:19:44 +00:00
state . touching . insert ( collision ) ;
2024-02-14 07:16:11 +00:00
if let Some ( mode ) = data . modes . get_mode ( state . mode_state . get_mode_id ( ) ) {
run_teleport_behaviour ( & general . wormhole , & data . models , mode , & 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-02-16 08:19:44 +00:00
PhysicsInstruction ::CollisionEnd ( collision ) = > {
match ( data . models . attr ( collision . convex_mesh_id ( ) . model_id ) , & collision ) {
( PhysicsCollisionAttributes ::Contact { contacting :_ , general :_ } , & Collision ::Contact ( contact ) ) = > {
state . touching . remove ( & collision ) ; //remove contact before calling contact_constrain_acceleration
2023-10-04 21:15:04 +00:00
//check ground
2024-02-16 08:19:44 +00:00
//TODO do better
//this is inner code from state.cull_velocity
match state . move_state . get_walk_state ( ) {
//did you stop touching the thing you were walking on?
Some ( walk_state ) = > if walk_state . contact = = contact {
state . set_move_state ( data , MoveState ::Air ) ;
} ,
None = > state . apply_enum_and_body ( data ) ,
}
2023-10-04 21:15:04 +00:00
} ,
2024-02-16 08:19:44 +00:00
( PhysicsCollisionAttributes ::Intersect { intersecting : _ , general :_ } , Collision ::Intersect ( _ ) ) = > {
state . touching . remove ( & collision ) ;
2023-10-04 21:15:04 +00:00
} ,
2024-02-16 08:19:44 +00:00
_ = > panic! ( " invalid pair " ) ,
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 = > {
2024-02-16 08:19:44 +00:00
//TODO make this less huge
if let Some ( strafe_settings ) = & state . style . strafe {
let controls = state . input_state . controls ;
if strafe_settings . activates ( controls ) {
let masked_controls = strafe_settings . mask ( controls ) ;
let control_dir = state . style . get_control_dir ( masked_controls ) ;
if control_dir ! = Planar64Vec3 ::ZERO {
let camera_mat = state . camera . simulate_move_rotation_y ( state . input_state . lerp_delta ( state . time ) . x ) ;
if let Some ( ticked_velocity ) = strafe_settings . tick_velocity ( state . body . velocity , ( camera_mat * control_dir ) . with_length ( Planar64 ::ONE ) ) {
//this is wrong but will work ig
//need to note which push planes activate in push solve and keep those
state . cull_velocity ( data , ticked_velocity ) ;
}
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 {
2024-02-16 08:19:44 +00:00
MoveState ::Air
| MoveState ::Water
| MoveState ::Fly
= > println! ( " ReachWalkTargetVelocity fired for non-walking MoveState " ) ,
2023-10-13 02:44:46 +00:00
MoveState ::Walk ( walk_state ) | MoveState ::Ladder ( walk_state ) = > {
2024-02-16 08:19:44 +00:00
match & walk_state . target {
//you are not supposed to reach a walk target which is already reached!
TransientAcceleration ::Reached = > unreachable! ( ) ,
TransientAcceleration ::Reachable { acceleration :_ , time :_ } = > {
//velocity is already handled by advance_time
//we know that the acceleration is precisely zero because the walk target is known to be reachable
//which means that gravity can be fully cancelled
//ignore moving platforms for now
set_acceleration ( & mut state . body , & state . touching , & data . models , & data . hitbox_mesh , Planar64Vec3 ::ZERO ) ;
walk_state . target = TransientAcceleration ::Reached ;
2023-10-17 07:17:54 +00:00
} ,
2024-02-16 08:19:44 +00:00
//you are not supposed to reach an unreachable walk target!
TransientAcceleration ::Unreachable { acceleration :_ } = > unreachable! ( ) ,
2023-10-17 07:17:54 +00:00
}
2023-10-13 02:44:46 +00:00
}
}
2023-09-19 06:36:14 +00:00
} ,
2024-02-16 08:19:44 +00:00
PhysicsInstruction ::SetSensitivity ( sensitivity ) = > state . camera . sensitivity = sensitivity ,
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 ) = > {
2024-02-16 08:19:44 +00:00
state . camera . move_mouse ( state . input_state . mouse_delta ( ) ) ;
state . input_state . set_next_mouse ( m ) ;
2023-10-05 03:04:04 +00:00
} ,
2024-01-30 06:37:05 +00:00
PhysicsInputInstruction ::ReplaceMouse ( m0 , m1 ) = > {
2024-02-16 08:19:44 +00:00
state . camera . move_mouse ( m0 . pos - state . input_state . mouse . pos ) ;
state . input_state . replace_mouse ( m0 , m1 ) ;
2023-09-20 00:53:29 +00:00
} ,
2024-02-16 08:19:44 +00:00
PhysicsInputInstruction ::SetMoveForward ( s ) = > state . input_state . set_control ( Controls ::MoveForward , s ) ,
PhysicsInputInstruction ::SetMoveLeft ( s ) = > state . input_state . set_control ( Controls ::MoveLeft , s ) ,
PhysicsInputInstruction ::SetMoveBack ( s ) = > state . input_state . set_control ( Controls ::MoveBackward , s ) ,
PhysicsInputInstruction ::SetMoveRight ( s ) = > state . input_state . set_control ( Controls ::MoveRight , s ) ,
PhysicsInputInstruction ::SetMoveUp ( s ) = > state . input_state . set_control ( Controls ::MoveUp , s ) ,
PhysicsInputInstruction ::SetMoveDown ( s ) = > state . input_state . set_control ( Controls ::MoveDown , s ) ,
2024-01-30 06:37:05 +00:00
PhysicsInputInstruction ::SetJump ( s ) = > {
2024-02-16 08:19:44 +00:00
b_refresh_walk_target = false ;
state . input_state . set_control ( Controls ::Jump , s ) ;
if let Some ( walk_state ) = state . move_state . get_walk_state ( ) {
if let Some ( jump_settings ) = & state . style . jump {
let jump_dir = walk_state . jump_direction . direction ( & data . models , & data . hitbox_mesh , & walk_state . contact ) ;
let jumped_velocity = jump_settings . jumped_velocity ( & state . style , jump_dir , state . body . velocity ) ;
state . cull_velocity ( & data , jumped_velocity ) ;
2023-11-30 09:51:17 +00:00
}
}
2023-09-20 00:53:29 +00:00
} ,
2024-01-30 06:37:05 +00:00
PhysicsInputInstruction ::SetZoom ( s ) = > {
2024-02-16 08:19:44 +00:00
state . input_state . set_control ( Controls ::Zoom , s ) ;
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 ::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 ) ;
2024-07-31 05:12:21 +00:00
let spawn_point = data . modes . get_mode ( state . mode_state . get_mode_id ( ) ) . map ( | mode |
2024-01-30 06:37:05 +00:00
//TODO: spawn at the bottom of the start zone plus the hitbox size
2024-07-31 05:12:21 +00:00
data . models . model ( mode . get_start ( ) . into ( ) ) . transform . vertex . translation
2024-01-30 06:37:05 +00:00
) . 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 ) ;
2024-02-16 08:19:44 +00:00
state . set_move_state ( data , MoveState ::Air ) ;
b_refresh_walk_target = false ;
} ,
PhysicsInputInstruction ::PracticeFly = > {
match & state . move_state {
MoveState ::Fly = > {
state . set_move_state ( data , MoveState ::Air ) ;
} ,
_ = > {
state . set_move_state ( data , MoveState ::Fly ) ;
} ,
}
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 ::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 {
2024-02-16 08:19:44 +00:00
state . apply_input_and_body ( data ) ;
state . cull_velocity ( data , state . body . velocity ) ;
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 ) ;
}
2024-07-25 20:53:45 +00:00
}