diff --git a/src/gameplay_attributes.rs b/src/gameplay_attributes.rs new file mode 100644 index 0000000..6756e45 --- /dev/null +++ b/src/gameplay_attributes.rs @@ -0,0 +1,152 @@ +use crate::integer::{Time,Planar64,Planar64Vec3}; + +//you have this effect while in contact +#[derive(Clone,Hash,Eq,PartialEq)] +pub struct ContactingLadder{ + pub sticky:bool +} +#[derive(Clone,Hash,Eq,PartialEq)] +pub enum ContactingBehaviour{ + Surf, + Cling,//usable as a zipline, or other weird and wonderful things + Ladder(ContactingLadder), + Elastic(u32),//[1/2^32,1] 0=None (elasticity+1)/2^32 +} +//you have this effect while intersecting +#[derive(Clone,Hash,Eq,PartialEq)] +pub struct IntersectingWater{ + pub viscosity:Planar64, + pub density:Planar64, + pub velocity:Planar64Vec3, +} +//All models can be given these attributes +#[derive(Clone,Hash,Eq,PartialEq)] +pub struct Accelerator{ + pub acceleration:Planar64Vec3 +} +#[derive(Clone,Hash,Eq,PartialEq)] +pub enum Booster{ + //Affine(crate::integer::Planar64Affine3),//capable of SetVelocity,DotVelocity,normal booster,bouncy part,redirect velocity, and much more + Velocity(Planar64Vec3),//straight up boost velocity adds to your current velocity + Energy{direction:Planar64Vec3,energy:Planar64},//increase energy in direction +} +#[derive(Clone,Hash,Eq,PartialEq)] +pub enum TrajectoryChoice{ + HighArcLongDuration,//underhand lob at target: less horizontal speed and more air time + LowArcShortDuration,//overhand throw at target: more horizontal speed and less air time +} +#[derive(Clone,Hash,Eq,PartialEq)] +pub enum SetTrajectory{ + //Speed-type SetTrajectory + AirTime(Time),//air time (relative to gravity direction) is invariant across mass and gravity changes + Height(Planar64),//boost height (relative to gravity direction) is invariant across mass and gravity changes + DotVelocity{direction:Planar64Vec3,dot:Planar64},//set your velocity in a specific direction without touching other directions + //Velocity-type SetTrajectory + TargetPointTime{//launch on a trajectory that will land at a target point in a set amount of time + target_point:Planar64Vec3, + time:Time,//short time = fast and direct, long time = launch high in the air, negative time = wrong way + }, + TargetPointSpeed{//launch at a fixed speed and land at a target point + target_point:Planar64Vec3, + speed:Planar64,//if speed is too low this will fail to reach the target. The closest-passing trajectory will be chosen instead + trajectory_choice:TrajectoryChoice, + }, + Velocity(Planar64Vec3),//SetVelocity +} +impl SetTrajectory{ + fn is_velocity(&self)->bool{ + match self{ + SetTrajectory::AirTime(_) + |SetTrajectory::Height(_) + |SetTrajectory::DotVelocity{direction:_,dot:_}=>false, + SetTrajectory::TargetPointTime{target_point:_,time:_} + |SetTrajectory::TargetPointSpeed{target_point:_,speed:_,trajectory_choice:_} + |SetTrajectory::Velocity(_)=>true, + } + } +} +// enum TrapCondition{ +// FasterThan(Planar64), +// SlowerThan(Planar64), +// InRange(Planar64,Planar64), +// OutsideRange(Planar64,Planar64), +// } +#[derive(Clone,Hash,Eq,PartialEq)] +pub struct Wormhole{ + //destination does not need to be another wormhole + //this defines a one way portal to a destination model transform + //two of these can create a two way wormhole + pub destination_model_id:u32, + //(position,angles)*=origin.transform.inverse()*destination.transform +} +//attributes listed in order of handling +#[derive(Default,Clone,Hash,Eq,PartialEq)] +pub struct GeneralAttributes{ + pub booster:Option, + pub trajectory:Option, + pub wormhole:Option, + pub accelerator:Option, +} +impl GeneralAttributes{ + pub fn any(&self)->bool{ + self.booster.is_some() + ||self.trajectory.is_some() + ||self.wormhole.is_some() + ||self.accelerator.is_some() + } + pub fn is_wrcp(&self)->bool{ + self.trajectory.as_ref().map_or(false,|t|t.is_velocity()) + /* + &&match &self.teleport_behaviour{ + Some(TeleportBehaviour::StageElement( + StageElement{ + mode_id, + stage_id:_, + force:true, + behaviour:StageElementBehaviour::Trigger|StageElementBehaviour::Teleport + } + ))=>current_mode_id==*mode_id, + _=>false, + } + */ + } +} +#[derive(Default,Clone,Hash,Eq,PartialEq)] +pub struct ContactingAttributes{ + //friction? + pub contact_behaviour:Option, +} +impl ContactingAttributes{ + pub fn any(&self)->bool{ + self.contact_behaviour.is_some() + } +} +#[derive(Default,Clone,Hash,Eq,PartialEq)] +pub struct IntersectingAttributes{ + pub water:Option, +} +impl IntersectingAttributes{ + pub fn any(&self)->bool{ + self.water.is_some() + } +} +pub struct CollisionAttributesId(u32); +pub enum CollisionAttributes{ + Decoration,//visual only + Contact{//track whether you are contacting the object + contacting:ContactingAttributes, + general:GeneralAttributes, + }, + Intersect{//track whether you are intersecting the object + intersecting:IntersectingAttributes, + general:GeneralAttributes, + }, +} +impl std::default::Default for CollisionAttributes{ + fn default()->Self{ + Self::Contact{ + contacting:ContactingAttributes::default(), + general:GeneralAttributes::default() + } + } +} diff --git a/src/gameplay_modes.rs b/src/gameplay_modes.rs new file mode 100644 index 0000000..a06fbb2 --- /dev/null +++ b/src/gameplay_modes.rs @@ -0,0 +1,90 @@ +use std::collections::{HashSet,HashMap}; +use crate::model::ModelId; +use crate::gameplay_style; + +pub struct StageElement{ + stage:StageId,//which stage spawn to send to + force:bool,//allow setting to lower spawn id i.e. 7->3 + behaviour:StageElementBehaviour +} + +#[derive(Clone,Hash,Eq,PartialEq)] +pub enum StageElementBehaviour{ + SpawnAt,//must be standing on top to get effect. except cancollide false + Trigger, + Teleport, + Platform, + //Check(point) acts like a trigger if you haven't hit all the checkpoints on previous stages yet. + //Note that all stage elements act like this, this is just the isolated behaviour. + Check, + Checkpoint,//this is a combined behaviour for Ordered & Unordered in case a model is used multiple times or for both. +} + +pub struct StageId(u32); +pub struct Stage{ + spawn:ModelId, + //other behaviour models of this stage can have + ordered_checkpoints:Vec, + unordered_checkpoints:HashSet, +} + +#[derive(Clone,Hash,Eq,PartialEq)] +pub enum ZoneBehaviour{ + Finish, + Anitcheat, +} +pub struct ModeId(u32); +pub struct Mode{ + style:gameplay_style::StyleModifiers, + start:ModelId, + zones:HashMap, + stages:Vec, + //mutually exlusive stage element behaviour + elements:HashMap, + jump_limit:HashMap, +} +impl Mode{ + pub fn get_spawn_model_id(&self,stage:StageId)->Option{ + self.stages.get(stage.0 as usize).map(|s|s.spawn) + } + pub fn denormalize_data(&mut self){ + //expand and index normalized data + for (stage_id,stage) in self.stages.iter().enumerate(){ + self.elements.insert(stage.spawn,StageElement{ + stage:StageId(stage_id as u32), + force:false, + behaviour:StageElementBehaviour::SpawnAt, + }); + for &model_id in &stage.ordered_checkpoints{ + self.elements.insert(model_id,StageElement{ + stage:StageId(stage_id as u32), + force:false, + behaviour:StageElementBehaviour::Checkpoint, + }); + } + for &model_id in &stage.unordered_checkpoints{ + self.elements.insert(model_id,StageElement{ + stage:StageId(stage_id as u32), + force:false, + behaviour:StageElementBehaviour::Checkpoint, + }); + } + } + } +} + +#[derive(Default)] +pub struct Modes{ + modes:Vec, +} +impl Modes{ + pub fn clear(&mut self){ + self.modes.clear(); + } + pub fn get_mode(&self,mode:ModeId)->Option<&Mode>{ + self.modes.get(mode.0 as usize) + } + pub fn insert(&mut self,mode:Mode){ + self.modes.push(mode); + } +} \ No newline at end of file diff --git a/src/gameplay_style.rs b/src/gameplay_style.rs new file mode 100644 index 0000000..297f71f --- /dev/null +++ b/src/gameplay_style.rs @@ -0,0 +1,290 @@ +use crate::integer::{Time,Ratio64,Planar64,Planar64Vec3}; + +pub struct StyleModifiers{ + controls_used:u32,//controls which are allowed to pass into gameplay + controls_mask:u32,//controls which are masked from control state (e.g. jump in scroll style) + strafe:Option, + jump_impulse:JumpImpulse, + jump_calculation:JumpCalculation, + static_friction:Planar64, + kinetic_friction:Planar64, + walk_speed:Planar64, + walk_accel:Planar64, + ladder_speed:Planar64, + ladder_accel:Planar64, + ladder_dot:Planar64, + swim_speed:Planar64, + mass:Planar64, + mv:Planar64, + surf_slope:Option, + rocket_force:Option, + gravity:Planar64Vec3, + hitbox:Hitbox, + camera_offset:Planar64Vec3, +} +impl std::default::Default for StyleModifiers{ + fn default()->Self{ + Self::roblox_bhop() + } +} +impl StyleModifiers{ + const CONTROL_MOVEFORWARD:u32=0b00000001; + const CONTROL_MOVEBACK:u32=0b00000010; + const CONTROL_MOVERIGHT:u32=0b00000100; + const CONTROL_MOVELEFT:u32=0b00001000; + const CONTROL_MOVEUP:u32=0b00010000; + const CONTROL_MOVEDOWN:u32=0b00100000; + const CONTROL_JUMP:u32=0b01000000; + const CONTROL_ZOOM:u32=0b10000000; + + const RIGHT_DIR:Planar64Vec3=Planar64Vec3::X; + const UP_DIR:Planar64Vec3=Planar64Vec3::Y; + const FORWARD_DIR:Planar64Vec3=Planar64Vec3::NEG_Z; + + fn neo()->Self{ + Self{ + controls_used:!0, + controls_mask:!0,//&!(Self::CONTROL_MOVEUP|Self::CONTROL_MOVEDOWN), + strafe:Some(StrafeSettings{ + enable:EnableStrafe::Always, + air_accel_limit:None, + tick_rate:Ratio64::new(64,Time::ONE_SECOND.nanos() as u64).unwrap(), + }), + jump_impulse:JumpImpulse::FromEnergy(Planar64::int(512)), + jump_calculation:JumpCalculation::Energy, + gravity:Planar64Vec3::int(0,-80,0), + static_friction:Planar64::int(2), + kinetic_friction:Planar64::int(3),//unrealistic: kinetic friction is typically lower than static + mass:Planar64::int(1), + mv:Planar64::int(3), + rocket_force:None, + walk_speed:Planar64::int(16), + walk_accel:Planar64::int(80), + ladder_speed:Planar64::int(16), + ladder_accel:Planar64::int(160), + ladder_dot:(Planar64::int(1)/2).sqrt(), + swim_speed:Planar64::int(12), + surf_slope:Some(Planar64::raw(7)/8), + hitbox:Hitbox::roblox(), + camera_offset:Planar64Vec3::int(0,2,0),//4.5-2.5=2 + } + } + + fn roblox_bhop()->Self{ + Self{ + controls_used:!0, + controls_mask:!0,//&!(Self::CONTROL_MOVEUP|Self::CONTROL_MOVEDOWN), + strafe:Some(StrafeSettings{ + enable:EnableStrafe::Always, + air_accel_limit:None, + tick_rate:Ratio64::new(100,Time::ONE_SECOND.nanos() as u64).unwrap(), + }), + jump_impulse:JumpImpulse::FromTime(Time::from_micros(715_588)), + jump_calculation:JumpCalculation::Capped, + gravity:Planar64Vec3::int(0,-100,0), + static_friction:Planar64::int(2), + kinetic_friction:Planar64::int(3),//unrealistic: kinetic friction is typically lower than static + mass:Planar64::int(1), + mv:Planar64::int(27)/10, + rocket_force:None, + walk_speed:Planar64::int(18), + walk_accel:Planar64::int(90), + ladder_speed:Planar64::int(18), + ladder_accel:Planar64::int(180), + ladder_dot:(Planar64::int(1)/2).sqrt(), + swim_speed:Planar64::int(12), + surf_slope:Some(Planar64::raw(3787805118)),// normal.y=0.75 + hitbox:Hitbox::roblox(), + camera_offset:Planar64Vec3::int(0,2,0),//4.5-2.5=2 + } + } + fn roblox_surf()->Self{ + Self{ + controls_used:!0, + controls_mask:!0,//&!(Self::CONTROL_MOVEUP|Self::CONTROL_MOVEDOWN), + strafe:Some(StrafeSettings{ + enable:EnableStrafe::Always, + air_accel_limit:None, + tick_rate:Ratio64::new(100,Time::ONE_SECOND.nanos() as u64).unwrap(), + }), + jump_impulse:JumpImpulse::FromTime(Time::from_micros(715_588)), + jump_calculation:JumpCalculation::Capped, + gravity:Planar64Vec3::int(0,-50,0), + static_friction:Planar64::int(2), + kinetic_friction:Planar64::int(3),//unrealistic: kinetic friction is typically lower than static + mass:Planar64::int(1), + mv:Planar64::int(27)/10, + rocket_force:None, + walk_speed:Planar64::int(18), + walk_accel:Planar64::int(90), + ladder_speed:Planar64::int(18), + ladder_accel:Planar64::int(180), + ladder_dot:(Planar64::int(1)/2).sqrt(), + swim_speed:Planar64::int(12), + surf_slope:Some(Planar64::raw(3787805118)),// normal.y=0.75 + hitbox:Hitbox::roblox(), + camera_offset:Planar64Vec3::int(0,2,0),//4.5-2.5=2 + } + } + + fn source_bhop()->Self{ + Self{ + controls_used:!0, + controls_mask:!0,//&!(Self::CONTROL_MOVEUP|Self::CONTROL_MOVEDOWN), + strafe:Some(StrafeSettings{ + enable:EnableStrafe::Always, + air_accel_limit:Some(Planar64::raw(150<<28)*100), + tick_rate:Ratio64::new(100,Time::ONE_SECOND.nanos() as u64).unwrap(), + }), + jump_impulse:JumpImpulse::FromHeight(Planar64::raw(52<<28)), + jump_calculation:JumpCalculation::Linear, + gravity:Planar64Vec3::raw(0,-800<<28,0), + static_friction:Planar64::int(2),//? + kinetic_friction:Planar64::int(3),//? + mass:Planar64::int(1), + mv:Planar64::raw(30<<28), + rocket_force:None, + walk_speed:Planar64::int(18),//? + walk_accel:Planar64::int(90),//? + ladder_speed:Planar64::int(18),//? + ladder_accel:Planar64::int(180),//? + ladder_dot:(Planar64::int(1)/2).sqrt(),//? + swim_speed:Planar64::int(12),//? + surf_slope:Some(Planar64::raw(3787805118)),// normal.y=0.75 + hitbox:Hitbox::source(), + camera_offset:Planar64Vec3::raw(0,(64<<28)-(73<<27),0), + } + } + fn source_surf()->Self{ + Self{ + controls_used:!0, + controls_mask:!0,//&!(Self::CONTROL_MOVEUP|Self::CONTROL_MOVEDOWN), + strafe:Some(StrafeSettings{ + enable:EnableStrafe::Always, + air_accel_limit:Some(Planar64::raw(150<<28)*66), + tick_rate:Ratio64::new(66,Time::ONE_SECOND.nanos() as u64).unwrap(), + }), + jump_impulse:JumpImpulse::FromHeight(Planar64::raw(52<<28)), + jump_calculation:JumpCalculation::Linear, + gravity:Planar64Vec3::raw(0,-800<<28,0), + static_friction:Planar64::int(2),//? + kinetic_friction:Planar64::int(3),//? + mass:Planar64::int(1), + mv:Planar64::raw(30<<28), + rocket_force:None, + walk_speed:Planar64::int(18),//? + walk_accel:Planar64::int(90),//? + ladder_speed:Planar64::int(18),//? + ladder_accel:Planar64::int(180),//? + ladder_dot:(Planar64::int(1)/2).sqrt(),//? + swim_speed:Planar64::int(12),//? + surf_slope:Some(Planar64::raw(3787805118)),// normal.y=0.75 + hitbox:Hitbox::source(), + camera_offset:Planar64Vec3::raw(0,(64<<28)-(73<<27),0), + } + } + fn roblox_rocket()->Self{ + Self{ + controls_used:!0, + controls_mask:!0, + strafe:None, + jump_impulse:JumpImpulse::FromTime(Time::from_micros(715_588)), + jump_calculation:JumpCalculation::Capped, + gravity:Planar64Vec3::int(0,-100,0), + static_friction:Planar64::int(2), + kinetic_friction:Planar64::int(3),//unrealistic: kinetic friction is typically lower than static + mass:Planar64::int(1), + mv:Planar64::int(27)/10, + rocket_force:Some(Planar64::int(200)), + walk_speed:Planar64::int(18), + walk_accel:Planar64::int(90), + ladder_speed:Planar64::int(18), + ladder_accel:Planar64::int(180), + ladder_dot:(Planar64::int(1)/2).sqrt(), + swim_speed:Planar64::int(12), + surf_slope:Some(Planar64::raw(3787805118)),// normal.y=0.75 + hitbox:Hitbox::roblox(), + camera_offset:Planar64Vec3::int(0,2,0),//4.5-2.5=2 + } + } +} + +enum JumpCalculation{ + Capped,//roblox + Energy,//new + Linear,//source +} + +enum JumpImpulse{ + FromTime(Time),//jump time is invariant across mass and gravity changes + FromHeight(Planar64),//jump height is invariant across mass and gravity changes + FromDeltaV(Planar64),//jump velocity is invariant across mass and gravity changes + FromEnergy(Planar64),// :) +} +//Jumping acts on dot(walks_state.normal,body.velocity) +//Capped means it increases the dot to the cap +//Energy means it adds energy +//Linear means it linearly adds on + +enum EnableStrafe{ + Always, + MaskAny(u32),//hsw, shsw + MaskAll(u32), + //Function(Boxbool>), +} + +struct StrafeSettings{ + enable:EnableStrafe, + air_accel_limit:Option, + tick_rate:Ratio64, +} + +//Why have a dedicated type for hitbox? surely it can just be a TransformedMesh or something. +struct Hitbox{ + halfsize:Planar64Vec3, + mesh:PhysicsMesh, + transform:integer::Planar64Affine3, + normal_transform:Planar64Mat3, + transform_det:Planar64, +} +impl Hitbox{ + fn new(mesh:PhysicsMesh,transform:integer::Planar64Affine3)->Self{ + //calculate extents + let mut aabb=aabb::Aabb::default(); + for vert in mesh.verts(){ + aabb.grow(transform.transform_point3(vert)); + } + Self{ + halfsize:aabb.size()/2, + mesh, + transform, + normal_transform:transform.matrix3.inverse_times_det().transpose(), + transform_det:transform.matrix3.determinant(), + } + } + fn from_mesh_scale(mesh:PhysicsMesh,scale:Planar64Vec3)->Self{ + let matrix3=Planar64Mat3::from_diagonal(scale); + Self{ + halfsize:scale, + mesh, + normal_transform:matrix3.inverse_times_det().transpose(), + transform:integer::Planar64Affine3::new(matrix3,Planar64Vec3::ZERO), + transform_det:matrix3.determinant(),//scale.x*scale.y*scale.z but whatever + } + } + fn from_mesh_scale_offset(mesh:PhysicsMesh,scale:Planar64Vec3,offset:Planar64Vec3)->Self{ + let matrix3=Planar64Mat3::from_diagonal(scale); + Self{ + halfsize:scale, + mesh, + normal_transform:matrix3.inverse_times_det().transpose(), + transform:integer::Planar64Affine3::new(matrix3,offset), + transform_det:matrix3.determinant(), + } + } + #[inline] + fn transformed_mesh(&self)->TransformedMesh{ + TransformedMesh::new(&self.mesh,&self.transform,&self.normal_transform,self.transform_det) + } +} + diff --git a/src/lib.rs b/src/lib.rs index 188abe1..99b060b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,9 @@ pub mod bvh; pub mod aabb; +pub mod model; pub mod zeroes; pub mod integer; -pub mod instruction; \ No newline at end of file +pub mod instruction; +pub mod gameplay_modes; +pub mod gameplay_style; +pub mod gameplay_attributes; \ No newline at end of file diff --git a/src/model.rs b/src/model.rs new file mode 100644 index 0000000..cbf7370 --- /dev/null +++ b/src/model.rs @@ -0,0 +1,54 @@ +use crate::integer::{Planar64Vec3,Planar64Affine3}; +use crate::gameplay_attributes; + +pub type TextureCoordinate=glam::Vec2; +pub type Color4=glam::Vec4; +#[derive(Clone,Hash,PartialEq,Eq)] +pub struct IndexedVertex{ + pub pos:u32, + pub tex:u32, + pub normal:u32, + pub color:u32, +} +pub struct VertexId(u32); +pub struct IndexedVertexList{ + pub vertices:Vec, +} +pub struct GroupId(u32); +pub enum IndexedGroup{ + PolygonList(Vec), + //TriangleStrip(Vec), +} +pub struct RenderId(u32); +pub struct IndexedGraphicsGroup{ + //Render pattern material/texture/shader/flat color + pub render:RenderId, + pub groups:Vec, +} +pub struct IndexedPhysicsGroup{ + //the polygons in this group are guaranteed to make a closed convex shape + pub groups:Vec, +} +//This is a superset of PhysicsModel and GraphicsModel +pub struct IndexedModel{ + pub unique_pos:Vec,//Unit32Vec3 + pub unique_normal:Vec,//Unit32Vec3 + pub unique_tex:Vec, + pub unique_color:Vec, + pub unique_vertices:Vec, + //groups are constant texture AND convexity slices + pub groups:Vec, + //graphics indexed (by texture) + pub graphics_sets:Vec, + //physics indexed (by convexity) + pub physics_sets:Vec, +} + +#[derive(Clone,Copy,Hash,Eq,PartialEq)] +pub struct ModelId(u32); +pub struct Model{ + pub model:ModelId, + pub attributes:gameplay_attributes::CollisionAttributesId, + pub color:Color4,//transparency is in here + pub transform:Planar64Affine3, +}