group2 0.1.0
CSE 125 Group 2
Loading...
Searching...
No Matches
physics Namespace Reference

Pure physics math — no ECS types, no registry. More...

Namespaces

namespace  broadphase
namespace  events
namespace  cook
namespace  debug
namespace  diag
namespace  forces
namespace  inertia
namespace  perf
namespace  simd
namespace  detail
 Active world (runtime-switchable).

Classes

class  ContactCache
struct  ContactFeatureId
 Identifier built from "which feature of A" and "which feature of B" met at this contact. More...
struct  ContactPoint
 One sub-contact in a multi-point manifold. More...
struct  ContactManifold
 A contact manifold between exactly two entities. More...
struct  PointJoint
 Spherical/ball joint — locks the world-space anchor points of two bodies together while permitting arbitrary rotation. More...
struct  HingeJoint
 Single-axis hinge — locks the bodies' anchors together AND constrains their relative rotation to a single axis. More...
struct  ConeTwistJoint
 Cone-twist joint — locks anchors AND constrains relative rotation to a swing-cone + twist-limit. More...
struct  Joint6DOF
 Generic 6-DOF joint — per-axis lock / limit / free + optional motor and spring on each axis. More...
struct  KccFrameResult
 Collision-owned result for one player KCC step. More...
struct  MapCollisionData
 Collision data. More...
struct  MapLoadOptions
 Load options. More...
struct  RagdollPbdJoint
 One articulated joint in a PBD ragdoll skeleton. More...
struct  HitscanHit
 Result of a hitscan raycast. More...
struct  HitboxHit
 Skeleton-driven hitbox raycast (capsule-based). More...
struct  SleepConfig
struct  SolverConfig
struct  Plane
 An infinite plane dividing free space from solid geometry. More...
struct  WorldAABB
 An axis-aligned box in world space, used as static collision geometry. More...
struct  WorldBrush
 A convex volume defined by bounding planes (for ramps, angled walls, etc.). More...
struct  WorldCylinder
 A vertical (Y-axis) cylinder in world space. More...
struct  WorldSphere
 A sphere in world space. More...
struct  BVHNode
 A single BVH node for spatial acceleration of triangle meshes. More...
struct  WorldTriMesh
 A triangle mesh with BVH acceleration for collision queries. More...
struct  StaticWorldBroadphase
 Immutable world-level BVH over WorldTriMesh bounds. More...
struct  WorldGeometry
 All world collision geometry for one tick. More...
struct  CapsuleShape
 Capsule shape input for swept-collision queries. More...
struct  HitResult
 Result of a swept AABB collision query. More...
struct  ClearanceResult
 Result of a shape-vs-geometry closest-point clearance query. More...
struct  GroundProbeResult
 Result of a downward ground probe — what's directly under a character's feet, classified for walkability. More...
struct  DepenContact
 Single deepest contact from a capsule against a primitive or the world. More...
struct  DepenetrationOptions
struct  DepenetrationResult
struct  SphereHitResult
 Result of a sphere-cast query (includes world-space hit point). More...
struct  TriMeshValidationReport
 Map-cooking validation counters for authored collision triangle meshes. More...
struct  TriMeshValidationTotals
 Aggregate validation counters across a whole authored collision set. More...
struct  TriMeshCookStats
 Runtime/cook summary for authored static collision triangle meshes. More...
struct  ClosestPointOnMeshResult
 Result of a closest-point-on-mesh query. More...
struct  WallDetectionResult
 Results of wall detection probes. More...
struct  WallAttachmentResult
 Stable wallrun attachment target on authored triangle meshes. More...

Enumerations

enum class  TriRegion : uint8_t {
  Face = 0 , Edge0 = 1 , Edge1 = 2 , Edge2 = 3 ,
  Vert0 = 4 , Vert1 = 5 , Vert2 = 6
}
 Voronoi region of a triangle. More...

Functions

void solveJoints (Registry &registry, const SolverConfig &cfg, float dt)
 Solve every joint in the registry using sequential impulses.
void clampVelocities (Registry &registry, float maxAngular=30.0f, float maxLinear=1500.0f)
 Clamp every dynamic body's angular and linear velocity to a safe upper bound.
bool loadMapCollision (const std::string &path, MapCollisionData &out, const MapLoadOptions &opts={})
 API.
bool loadPropCollision (const std::string &path, MapCollisionData &out, glm::vec3 position, float scale, bool decomposeNonConvex=false)
 Load collision for a standalone prop GLB and append to existing collision data.
glm::vec3 applyGravity (glm::vec3 vel, float dt, bool flipped=false)
 Apply gravity for one tick.
glm::vec3 applyPlayerGravity (glm::vec3 vel, float dt, bool flipped=false)
 Apply the player's gravity for one tick (k_playerGravity).
glm::vec3 applyGroundFriction (glm::vec3 vel, float dt)
 Apply Quake-style ground friction to horizontal (XZ) velocity.
glm::vec3 accelerate (glm::vec3 vel, glm::vec3 wishDir, float wishSpeed, float accel, float dt)
 Quake PM_Accelerate: accelerate toward wishDir up to wishSpeed.
float airWishSpeedForHorizSpeed (float currentHorizSpeed)
 Compute the air wish-speed for the player's current horizontal speed.
glm::vec3 clipVelocity (glm::vec3 vel, glm::vec3 normal, float overbounce)
 Project velocity onto a collision surface to slide along it.
glm::vec3 computeWishDir (float yaw, bool forward, bool back, bool left, bool right)
 Compute the horizontal wish direction from yaw angle and WASD key state.
void enforceRagdollConnectivity (Registry &registry, float dt, int iterations=8)
 Enforce ragdoll connectivity + angular limits via N PBD iterations.
bool raycastAABB (glm::vec3 origin, glm::vec3 direction, const WorldAABB &box, float maxDistance, float &outDistance, glm::vec3 &outNormal)
 Ray vs axis-aligned box intersection (slab method).
bool raycastCylinder (glm::vec3 origin, glm::vec3 direction, const WorldCylinder &cyl, float maxDistance, float &outDistance, glm::vec3 &outNormal)
 Ray vs vertical cylinder intersection.
bool raycastSphere (glm::vec3 origin, glm::vec3 direction, const WorldSphere &sph, float maxDistance, float &outDistance, glm::vec3 &outNormal)
 Ray vs sphere intersection.
bool raycastTriMeshIndexed (glm::vec3 origin, glm::vec3 direction, const WorldTriMesh &mesh, float maxDistance, float &outDistance, glm::vec3 &outNormal, uint32_t &outTriIdx)
 Raycast against a triangle mesh using BVH-accelerated Möller-Trumbore.
bool raycastTriMesh (glm::vec3 origin, glm::vec3 direction, const WorldTriMesh &mesh, float maxDistance, float &outDistance, glm::vec3 &outNormal)
 Convenience wrapper for callers that don't care which triangle was hit.
bool raycastBrush (glm::vec3 origin, glm::vec3 direction, const WorldBrush &brush, float maxDistance, float &outDistance, glm::vec3 &outNormal)
 Ray vs convex brush intersection (generalised slab method).
HitscanHit raycastWorld (glm::vec3 origin, glm::vec3 direction, const WorldGeometry &world)
 Raycast against all static world geometry (planes + boxes + cylinders + spheres).
HitscanHit raycastPlayers (Registry &registry, entt::entity shooter, glm::vec3 origin, glm::vec3 direction, float maxDistance)
 Raycast against all player hitboxes (axis-aligned capsule approximation).
HitscanHit resolveHitscan (Registry &registry, entt::entity shooter, glm::vec3 origin, glm::vec3 direction)
 Full hitscan resolution: world geometry first, then players (closest wins).
bool raycastCapsule (glm::vec3 origin, glm::vec3 dir, glm::vec3 A, glm::vec3 B, float r, float maxDist, float &outDist, glm::vec3 &outNormal)
 Ray vs capsule intersection.
HitboxHit raycastPlayerHitboxes (Registry &registry, entt::entity shooter, glm::vec3 origin, glm::vec3 direction, float maxDistance, float bulletRadius=0.0f)
 Raycast against all player hitbox capsules (skeleton-driven).
HitboxHit resolveHitscanHitbox (Registry &registry, entt::entity shooter, glm::vec3 origin, glm::vec3 direction, float bulletRadius=0.0f)
 Full hitscan with skeleton-driven hitboxes.
void updateSleep (Registry &registry, const SleepConfig &cfg)
 Update each body's sleep state from its current velocities.
void wakeBody (Registry &registry, entt::entity e)
 Wake a single body (e.g.
void wakeIslandOf (Registry &registry, const ContactCache &cache, entt::entity e)
 Wake every body that's currently in the same contact island as e.
void solveContacts (Registry &registry, ContactCache &cache, const SolverConfig &cfg, float dt)
 Solve every cached contact manifold for the current tick.
void buildStaticWorldBroadphase (StaticWorldBroadphase &broadphase, std::span< const WorldTriMesh > triMeshes)
 Rebuild the immutable broadphase over all static triangle meshes.
void queryStaticWorldBroadphase (const StaticWorldBroadphase &broadphase, const WorldAABB &query, const std::function< bool(uint32_t meshIndex)> &visit)
 Visit triangle-mesh indices whose mesh bounds overlap query.
HitResult sweepAABB (glm::vec3 halfExtents, glm::vec3 start, glm::vec3 end, std::span< const Plane > planes)
 Sweep an AABB along the path [start, end] against a list of infinite planes.
HitResult sweepAABBvsBox (glm::vec3 halfExtents, glm::vec3 start, glm::vec3 end, const WorldAABB &box)
 Sweep an AABB against a static axis-aligned box.
HitResult sweepAABBvsBrush (glm::vec3 halfExtents, glm::vec3 start, glm::vec3 end, const WorldBrush &brush)
 Sweep an AABB against a convex brush (set of bounding planes).
HitResult sweepAABBvsCylinder (glm::vec3 halfExtents, glm::vec3 start, glm::vec3 end, const WorldCylinder &cyl)
 Sweep an AABB against a vertical cylinder.
HitResult sweepAABBvsSphere (glm::vec3 halfExtents, glm::vec3 start, glm::vec3 end, const WorldSphere &sph)
 Sweep an AABB against a sphere.
HitResult sweepCapsuleVsPlanes (CapsuleShape capsule, glm::vec3 start, glm::vec3 end, std::span< const Plane > planes)
 Sweep a capsule along [start, end] against a list of infinite planes.
HitResult sweepCapsuleVsBox (CapsuleShape capsule, glm::vec3 start, glm::vec3 end, const WorldAABB &box)
 Sweep a capsule against a static axis-aligned box. Conservative.
HitResult sweepCapsuleVsBrush (CapsuleShape capsule, glm::vec3 start, glm::vec3 end, const WorldBrush &brush)
 Sweep a capsule against a convex brush. Exact (per-plane Minkowski extent).
HitResult sweepCapsuleVsCylinder (CapsuleShape capsule, glm::vec3 start, glm::vec3 end, const WorldCylinder &cyl)
 Sweep a capsule against a vertical cylinder. Conservative.
HitResult sweepCapsuleVsSphere (CapsuleShape capsule, glm::vec3 start, glm::vec3 end, const WorldSphere &sph)
 Sweep a capsule against a sphere. Conservative.
HitResult sweepAll (glm::vec3 halfExtents, glm::vec3 start, glm::vec3 end, const WorldGeometry &world)
 Sweep an AABB against all world geometry, returning the earliest hit.
HitResult sweepAll (CapsuleShape capsule, glm::vec3 start, glm::vec3 end, const WorldGeometry &world)
 Sweep a capsule against all world geometry, returning the earliest hit.
ClearanceResult clearanceCapsuleVsPlanes (CapsuleShape capsule, glm::vec3 pos, std::span< const Plane > planes)
ClearanceResult clearanceCapsuleVsBox (CapsuleShape capsule, glm::vec3 pos, const WorldAABB &box)
ClearanceResult clearanceCapsuleVsBrush (CapsuleShape capsule, glm::vec3 pos, const WorldBrush &brush)
ClearanceResult clearanceCapsuleVsCylinder (CapsuleShape capsule, glm::vec3 pos, const WorldCylinder &cyl)
ClearanceResult clearanceCapsuleVsSphere (CapsuleShape capsule, glm::vec3 pos, const WorldSphere &sph)
ClearanceResult clearanceCapsuleVsWorld (CapsuleShape capsule, glm::vec3 pos, const WorldGeometry &world, float maxMeshSearchRadius=1024.0f)
 Scene-wide minimum clearance.
GroundProbeResult probeGround (CapsuleShape capsule, glm::vec3 pos, float maxDistance, const WorldGeometry &world)
 Downward sweep that classifies the ground under a capsule.
DepenContact deepestCapsuleContact (CapsuleShape capsule, glm::vec3 pos, glm::vec3 vel, const WorldGeometry &world)
 Scene-wide deepest single contact (per-primitive, per-feature).
DepenetrationResult depenetrateCapsuleVsWorldDetailed (glm::vec3 &pos, glm::vec3 &vel, CapsuleShape capsule, const WorldGeometry &world, DepenetrationOptions options)
void depenetrateCapsuleVsWorld (glm::vec3 &pos, glm::vec3 &vel, CapsuleShape capsule, const WorldGeometry &world)
 Per-pass-deepest-first capsule depenetration against the whole world.
bool emergencyUnstick (glm::vec3 &pos, glm::vec3 &vel, CapsuleShape capsule, const WorldGeometry &world)
 Last-resort recovery for a capsule embedded in geometry with no clear depen direction.
SphereHitResult sphereCast (float radius, glm::vec3 start, glm::vec3 end, const WorldGeometry &world)
 Cast a sphere along the path [start, end] against all world geometry.
bool broadphaseAabbOverlap (const WorldAABB &a, const WorldAABB &b) noexcept
template<class Visit>
void queryStaticWorldBroadphaseFast (const StaticWorldBroadphase &broadphase, const WorldAABB &query, Visit &&visit)
void buildTriMeshBVH (WorldTriMesh &mesh)
 Build the BVH for a WorldTriMesh.
void weldTriMesh (WorldTriMesh &mesh, float coplanarTolerance=0.0349065850f)
 Compute face normals + active edge / vertex flags for a triangle mesh.
TriMeshValidationReport validateTriMesh (const WorldTriMesh &mesh, float positionEpsilon=1e-4f)
 Validate authored collision mesh topology before/after cooking.
TriMeshValidationTotals validateTriMeshes (std::span< const WorldTriMesh > meshes, float positionEpsilon=1e-4f)
 Validate every triangle mesh in an authored collision set.
TriMeshCookStats collectTriMeshCookStats (std::span< const WorldTriMesh > meshes)
 Collect aggregate map-cooking diagnostics for already-cooked meshes.
HitResult sweepAABBvsTriMesh (glm::vec3 halfExtents, glm::vec3 start, glm::vec3 end, const WorldTriMesh &mesh)
 Sweep an AABB against a triangle mesh using Voronoi-clipped per-triangle tests.
void depenetrateAABBvsTriMesh (glm::vec3 &pos, glm::vec3 &vel, glm::vec3 halfExtents, const WorldTriMesh &mesh, float pushback=0.03125f)
 Push an AABB out of a triangle mesh using Voronoi-clipped face-normal MTVs.
HitResult sweepCapsuleVsTriMesh (CapsuleShape capsule, glm::vec3 start, glm::vec3 end, const WorldTriMesh &mesh)
 Sweep a capsule against a triangle mesh.
DepenContact deepestCapsuleContactVsTriMesh (CapsuleShape capsule, glm::vec3 pos, glm::vec3 vel, const WorldTriMesh &mesh)
 Single deepest surface contact of a capsule against any triangle in this mesh.
ClosestPointOnMeshResult closestPointOnMesh (glm::vec3 segA, glm::vec3 segB, float maxDist, const WorldTriMesh &mesh)
 Find the closest point on the mesh's surface to a query segment, considering only points within maxDist.
ClosestPointOnMeshResult closestPointOnMesh (CapsuleShape capsule, glm::vec3 center, float maxDist, const WorldTriMesh &mesh)
 Convenience overload — uses the capsule's inner axis as the query segment at the given centre position.
ClosestPointOnMeshResult closestPointOnMeshTriangle (CapsuleShape capsule, glm::vec3 center, float maxDist, const WorldTriMesh &mesh, uint32_t triId)
 Closest point from a capsule axis to one specific cooked triangle.
ClearanceResult clearanceCapsuleVsTriMesh (CapsuleShape capsule, glm::vec3 pos, float maxReach, const WorldTriMesh &mesh)
 Capsule-vs-trimesh clearance.
GroundProbeResult groundProbeCapsuleVsTriMesh (CapsuleShape capsule, glm::vec3 pos, float maxDistance, float minWalkableDot, const WorldTriMesh &mesh)
 Downward ground probe against walkable triangle faces only.
WallAttachmentResult findWallRunAttachment (CapsuleShape capsule, glm::vec3 pos, const WorldGeometry &world, glm::vec3 continuityNormal, glm::vec3 travelDir=glm::vec3{0.0f}, float lookaheadDist=0.0f, float checkDist=24.0f, uint32_t previousMeshIndex=UINT32_MAX, uint32_t previousTriId=UINT32_MAX, TriRegion previousRegion=TriRegion::Face)
 Find the best triangle-mesh wallrun attachment, with optional lookahead along the current travel direction.
WallDetectionResult detectWalls (glm::vec3 pos, float yaw, glm::vec3 halfExtents, const WorldGeometry &world, float checkDist, float sphereRadius, glm::vec3 prevWallNormal=glm::vec3(0.0f), bool gravityFlipped=false, bool includeGroundDistance=true)
 Detect walls to the left, right, and front of the player.
float probeWallrunGroundDistance (glm::vec3 pos, glm::vec3 halfExtents, const WorldGeometry &world, float sphereRadius, bool gravityFlipped=false)
 Probe only the downward ground distance used by wallrun entry gates.
bool isWallNormal (glm::vec3 normal)
 Check if a surface normal represents a wall (not floor/ceiling).
void setActiveWorld (const WorldGeometry &geo)
 Set the world geometry that activeWorld() returns.
const WorldGeometryactiveWorld ()
 Return the world geometry most recently set via setActiveWorld(), or fall back to testWorld() if none has been set.
WorldBrush makeRamp (float xMin, float xMax, float zMin, float zMax, float height)
 Create a ramp brush that rises along +Z.
WorldBrush makeDiagonalWall (glm::vec3 center, float halfLen, float halfThick, float height, glm::vec3 dir)
 Create a diagonal wall brush from a centre, direction, and dimensions.
const WorldGeometrytestWorld ()
 The physics test playground.

Variables

constexpr int k_maxContactPoints = 4
 Up to 4 points per body pair — enough for any flat-face contact (box-on-box, box-on-ground, etc.).
constexpr float k_gravity = 1000.0f
 Downward acceleration (units/s^2) for projectiles / dynamics.
constexpr float k_playerGravity
 Player-specific gravity (units/s^2).
constexpr float k_jumpSpeed = 660.0f
 Initial upward velocity on jump (units/s).
constexpr float k_groundAccel = 10.0f
 Ground acceleration constant. Higher = reaches max speed faster.
constexpr float k_airAccel
 Air acceleration constant. Higher than Quake (0.7) for Titanfall-style air control.
constexpr float k_airMaxSpeed = 30.0f
 Wish-speed FLOOR in air (units/s).
constexpr float k_airMaxWishLowSpeed = 120.0f
 Wish-speed CEILING in air (units/s) when stationary.
constexpr float k_airWishCurveTop = 250.0f
 Horizontal speed (u/s) at which the curve plateaus at k_airMaxSpeed.
constexpr float k_airWishCurveExponent = 0.4f
 Power-curve exponent for wish-speed falloff (<1 = sharp early drop).
constexpr float k_friction = 7.5f
 Ground friction coefficient. Higher = crisper stops, easier-to-track movement.
constexpr float k_stopSpeed = 150.0f
 Friction is amplified below this speed for a crisp stop.
constexpr float k_overbounceWall = 1.001f
 Separation impulse for walls/ceilings; prevents corner-sticking.
constexpr float k_overbounceFloor = 1.0f
 Floor overbounce — exactly 1.0 means no bounce.
constexpr float k_stepHeight = 18.0f
 Maximum obstacle height auto-stepped over without jumping (units).
constexpr float k_floorAngleCos = 0.7f
 dot(surfaceNormal, up) threshold above which a surface counts as walkable floor.
constexpr float k_groundSnapDistance = 8.0f
 Distance the ground probe extends below the capsule foot to snap to descending slopes / steps.
constexpr float k_emergencyUnstickRadius = 64.0f
 Maximum radius of the emergency-unstick free-space search.
constexpr int k_maxDepenPasses = 6
 Maximum sequential passes the deepest-first capsule depen attempts before falling through to emergency unstick.
constexpr float k_explosionGroundPopOffset = 10.0f
 Upward teleport (units) at epicenter; lifts the victim past k_groundSnapDistance so the KCC won't re-anchor them this tick.
constexpr float k_explosionGroundVerticalBoost
 Minimum upward velocity (units/s) at epicenter.
constexpr float k_gravityFlipCooldown = 0.5f
 Minimum time between gravity flips (s).
constexpr bool k_enableSubstepping = true
 Master toggle for Phase-C sub-stepping.
constexpr float k_substepSafetyRatio = 0.5f
 Sub-step when |v|·dt > min_shape_radius · this.
constexpr int k_maxSubsteps = 8
 Clamp on sub-step count to bound worst-case cost.
constexpr float k_hitscanRange = 5000.0f
 Maximum hitscan distance in world units.
constexpr float k_parallelEpsilon = 1e-6f
 Epsilon for parallel-ray checks.

Detailed Description

Pure physics math — no ECS types, no registry.

Wall detection.

Pure swept-collision math — no ECS types, no registry.

All physics tuning values in one place.

All functions take values in and return values out (no mutation via pointer). Constants come from PhysicsConstants.hpp.

Units: Quake units (1 unit ≈ 1 inch), Y-up coordinate system.

Starting values target a Titanfall-to-Quake movement feel. Tune iteratively — k_playerGravity and k_jumpSpeed must always be tuned together: jump height = k_jumpSpeed^2 / (2 × k_playerGravity).

Plane convention: dot(normal, p) > distance is free space; dot(normal, p) < distance is solid. The normal always points into free space.

Example planes (Y-up coordinate system):

  • Floor at y=0: { normal=(0,1,0), distance=0 }
  • Ceiling at y=512: { normal=(0,-1,0), distance=-512 }
  • Wall at x=256 (solid right): { normal=(-1,0,0), distance=-256 }

Used by the movement system each tick to detect nearby surfaces for wallrunning.

Enumeration Type Documentation

◆ TriRegion

enum class physics::TriRegion : uint8_t
strong

Voronoi region of a triangle.

Identifies which feature (face, one of three edges, or one of three vertices) a closest-point query landed on. Used by depenetration / sweep / closest-point queries that need to clip contacts against the cooked welding-active masks, and by the Phase D wallrun manifold walk to decide whether an edge crossing hops to a neighbour triangle.

Enumerator
Face 
Edge0 

Edge v0 → v1.

Edge1 

Edge v1 → v2.

Edge2 

Edge v2 → v0.

Vert0 
Vert1 
Vert2 

Function Documentation

◆ accelerate()

glm::vec3 physics::accelerate ( glm::vec3 vel,
glm::vec3 wishDir,
float wishSpeed,
float accel,
float dt )

Quake PM_Accelerate: accelerate toward wishDir up to wishSpeed.

Does not cap total speed — only the projection of velocity onto wishDir is capped at wishSpeed. Existing momentum in any other direction is untouched. This property is what makes strafe jumping possible.

Parameters
velCurrent velocity.
wishDirNormalised desired movement direction (from InputSnapshot + yaw).
wishSpeedTarget speed (systems::currentWishSpeed on ground, airWishSpeedForHorizSpeed in air).
accelAcceleration constant (k_groundAccel or k_airAccel).
dtDelta time in seconds.
Returns
New velocity with acceleration applied.
Here is the caller graph for this function:

◆ activeWorld()

const WorldGeometry & physics::activeWorld ( )
inline

Return the world geometry most recently set via setActiveWorld(), or fall back to testWorld() if none has been set.

This is the single entry point that all physics systems should use.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ airWishSpeedForHorizSpeed()

float physics::airWishSpeedForHorizSpeed ( float currentHorizSpeed)

Compute the air wish-speed for the player's current horizontal speed.

Returns a value that interpolates from k_airMaxWishLowSpeed (when stationary) down to k_airMaxSpeed (the floor, once horizontal speed reaches k_airWishCurveTop). Uses a power curve pow(t, k_airWishCurveExponent) with exponent < 1 so the high-wish region is concentrated near zero speed — gentle on classic strafe-jump physics, generous on stall recovery.

Parameters
currentHorizSpeedCurrent horizontal speed (sqrt(vx² + vz²)).
Returns
Wish speed to feed into accelerate() for the air branch.
Here is the caller graph for this function:

◆ applyGravity()

glm::vec3 physics::applyGravity ( glm::vec3 vel,
float dt,
bool flipped = false )

Apply gravity for one tick.

In normal mode subtracts k_gravity * dt from Y; when flipped, adds it.

Parameters
velCurrent velocity.
dtDelta time in seconds.
flippedTrue when the player's gravity is inverted.
Returns
New velocity with gravity applied.
Note
Use for projectiles / dynamics. For the player KCC use applyPlayerGravity so the corrected single-integration KCC produces the same apex/air-time as the legacy code.

◆ applyGroundFriction()

glm::vec3 physics::applyGroundFriction ( glm::vec3 vel,
float dt )

Apply Quake-style ground friction to horizontal (XZ) velocity.

Uses k_stopSpeed as a minimum control speed so entities stop crisply rather than asymptotically approaching zero.

Parameters
velCurrent velocity.
dtDelta time in seconds.
Returns
New velocity with friction applied to XZ; Y is unchanged.
Note
Call every tick when the entity is grounded.
Here is the caller graph for this function:

◆ applyPlayerGravity()

glm::vec3 physics::applyPlayerGravity ( glm::vec3 vel,
float dt,
bool flipped = false )

Apply the player's gravity for one tick (k_playerGravity).

Same shape as applyGravity but uses the player-specific constant so projectile and grenade tuning is unaffected by player jump tuning.

Here is the caller graph for this function:

◆ broadphaseAabbOverlap()

bool physics::broadphaseAabbOverlap ( const WorldAABB & a,
const WorldAABB & b )
inlinenodiscardnoexcept
Here is the caller graph for this function:

◆ buildStaticWorldBroadphase()

void physics::buildStaticWorldBroadphase ( StaticWorldBroadphase & broadphase,
std::span< const WorldTriMesh > triMeshes )

Rebuild the immutable broadphase over all static triangle meshes.

Here is the caller graph for this function:

◆ buildTriMeshBVH()

void physics::buildTriMeshBVH ( WorldTriMesh & mesh)

Build the BVH for a WorldTriMesh.

Must be called after vertices and indices are populated. Fills in bvhNodes, triIndices, boundsMin, and boundsMax. Does NOT populate the welding data — call weldTriMesh() after this.

◆ clampVelocities()

void physics::clampVelocities ( Registry & registry,
float maxAngular = 30.0f,
float maxLinear = 1500.0f )

Clamp every dynamic body's angular and linear velocity to a safe upper bound.

PhysX defaults: maxAngularVelocity = 100 rad/s for rigid bodies, ~50 rad/s for articulation links; we target a tighter 30 rad/s for ragdolls so the corpse never spins like a helicopter when the joint solver overshoots. Linear is capped at a generous 1500 u/s. No-op for bodies whose velocity is already within bounds.

Here is the caller graph for this function:

◆ clearanceCapsuleVsBox()

ClearanceResult physics::clearanceCapsuleVsBox ( CapsuleShape capsule,
glm::vec3 pos,
const WorldAABB & box )
Here is the call graph for this function:
Here is the caller graph for this function:

◆ clearanceCapsuleVsBrush()

ClearanceResult physics::clearanceCapsuleVsBrush ( CapsuleShape capsule,
glm::vec3 pos,
const WorldBrush & brush )
Here is the call graph for this function:
Here is the caller graph for this function:

◆ clearanceCapsuleVsCylinder()

ClearanceResult physics::clearanceCapsuleVsCylinder ( CapsuleShape capsule,
glm::vec3 pos,
const WorldCylinder & cyl )
Here is the call graph for this function:
Here is the caller graph for this function:

◆ clearanceCapsuleVsPlanes()

ClearanceResult physics::clearanceCapsuleVsPlanes ( CapsuleShape capsule,
glm::vec3 pos,
std::span< const Plane > planes )
Here is the call graph for this function:
Here is the caller graph for this function:

◆ clearanceCapsuleVsSphere()

ClearanceResult physics::clearanceCapsuleVsSphere ( CapsuleShape capsule,
glm::vec3 pos,
const WorldSphere & sph )
Here is the call graph for this function:
Here is the caller graph for this function:

◆ clearanceCapsuleVsTriMesh()

ClearanceResult physics::clearanceCapsuleVsTriMesh ( CapsuleShape capsule,
glm::vec3 pos,
float maxReach,
const WorldTriMesh & mesh )

Capsule-vs-trimesh clearance.

Wraps closestPointOnMesh and converts axis-to-mesh distance into surface-to-surface clearance by subtracting capsule.radius.

Used by the Phase-C scene-wide clearanceCapsuleVsWorld aggregator. maxReach bounds the BVH search radius — pass at least the per-tick motion magnitude plus capsule.radius to ensure no contacts are missed within the integration step.

Here is the call graph for this function:

◆ clearanceCapsuleVsWorld()

ClearanceResult physics::clearanceCapsuleVsWorld ( CapsuleShape capsule,
glm::vec3 pos,
const WorldGeometry & world,
float maxMeshSearchRadius = 1024.0f )

Scene-wide minimum clearance.

Returns the nearest geometry feature in any direction. This is the closest-point query the conservative-advancement integrator drives off of every iteration.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ clipVelocity()

glm::vec3 physics::clipVelocity ( glm::vec3 vel,
glm::vec3 normal,
float overbounce )

Project velocity onto a collision surface to slide along it.

Parameters
velCurrent velocity.
normalSurface normal at the contact point.
overbounceSeparation scalar: use k_overbounceFloor for floors, k_overbounceWall for walls/ceilings.
Returns
Clipped velocity that slides along the surface.
Here is the caller graph for this function:

◆ closestPointOnMesh() [1/2]

ClosestPointOnMeshResult physics::closestPointOnMesh ( CapsuleShape capsule,
glm::vec3 center,
float maxDist,
const WorldTriMesh & mesh )

Convenience overload — uses the capsule's inner axis as the query segment at the given centre position.

Here is the call graph for this function:

◆ closestPointOnMesh() [2/2]

ClosestPointOnMeshResult physics::closestPointOnMesh ( glm::vec3 segA,
glm::vec3 segB,
float maxDist,
const WorldTriMesh & mesh )

Find the closest point on the mesh's surface to a query segment, considering only points within maxDist.

BVH-accelerated.

For wallrun (Phase D) the query segment is the capsule's inner axis (capsule.segA(pos) to capsule.segB(pos)). The result identifies "what wall am I on" without the heuristic dot-product reassignment that the current sphere-cast-based WallDetection uses. At edges and vertices, region plus triId plus the mesh's edgeNeighbor array is enough to walk the surface manifold across triangle seams.

Returns found = false if no triangle is within maxDist.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ closestPointOnMeshTriangle()

ClosestPointOnMeshResult physics::closestPointOnMeshTriangle ( CapsuleShape capsule,
glm::vec3 center,
float maxDist,
const WorldTriMesh & mesh,
uint32_t triId )

Closest point from a capsule axis to one specific cooked triangle.

Used by systems that already track triangle identity, such as wallrun manifold traversal. Returns found=false for invalid triangle ids, degenerate triangles, or distances beyond maxDist.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ collectTriMeshCookStats()

TriMeshCookStats physics::collectTriMeshCookStats ( std::span< const WorldTriMesh > meshes)

Collect aggregate map-cooking diagnostics for already-cooked meshes.

Intended for load-time logging and tests. weldedHalfEdges counts inactive manifold half-edges, so a flat seam shared by two triangles contributes two.

Here is the caller graph for this function:

◆ computeWishDir()

glm::vec3 physics::computeWishDir ( float yaw,
bool forward,
bool back,
bool left,
bool right )

Compute the horizontal wish direction from yaw angle and WASD key state.

Parameters
yawPlayer's current yaw in radians.
forwardTrue when W is held.
backTrue when S is held.
leftTrue when A is held.
rightTrue when D is held.
Returns
Normalised XZ direction vector, or (0,0,0) if no keys are pressed.
Note
Y component is always 0 — vertical movement is handled separately.
Here is the caller graph for this function:

◆ deepestCapsuleContact()

DepenContact physics::deepestCapsuleContact ( CapsuleShape capsule,
glm::vec3 pos,
glm::vec3 vel,
const WorldGeometry & world )

Scene-wide deepest single contact (per-primitive, per-feature).

Used by the modern depen as the per-pass oracle: pick the deepest violation across the whole world, push out of it, re-probe.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ deepestCapsuleContactVsTriMesh()

DepenContact physics::deepestCapsuleContactVsTriMesh ( CapsuleShape capsule,
glm::vec3 pos,
glm::vec3 vel,
const WorldTriMesh & mesh )

Single deepest surface contact of a capsule against any triangle in this mesh.

BVH-accelerated. Used by the world-level depenetrateCapsuleVsWorld to pick the deepest violator per pass.

Reports capsule surface penetration depth: capsule.radius - distance where distance is the closest distance from the capsule axis segment to the bounded triangle. The normal points from the triangle feature toward the capsule, with the face normal used only for exactly coincident pairs.

Here is the call graph for this function:

◆ depenetrateAABBvsTriMesh()

void physics::depenetrateAABBvsTriMesh ( glm::vec3 & pos,
glm::vec3 & vel,
glm::vec3 halfExtents,
const WorldTriMesh & mesh,
float pushback = 0.03125f )

Push an AABB out of a triangle mesh using Voronoi-clipped face-normal MTVs.

For each triangle the AABB overlaps (BVH-accelerated leaf search), the closest feature is found and Voronoi-clipped against the mesh's active-edge/active-vertex flags. Surviving contacts contribute one face- normal MTV (depth × face normal); contributions across multiple triangles are aggregated and applied once.

This replaces the older SAT-MTV implementation, which fired on internal edges of triangulated planar surfaces ("ghost contacts") and required 4 averaging passes to mask the noise. With Voronoi clipping, ghosts are suppressed at the source — one pass suffices.

Velocity is updated to cancel the component flowing into the surface, so the entity slides along the contact rather than re-penetrating next frame.

Parameters
posAABB centre — modified in place to push out of overlaps.
velVelocity — modified in place to cancel inward motion.
halfExtentsAABB half-extents.
meshTriangle mesh to depenetrate from (must be welded).
pushbackTiny extra distance added to each push to avoid hairline contact (matches Quake's DIST_EPSILON of 1/32 unit).
Here is the call graph for this function:
Here is the caller graph for this function:

◆ depenetrateCapsuleVsWorld()

void physics::depenetrateCapsuleVsWorld ( glm::vec3 & pos,
glm::vec3 & vel,
CapsuleShape capsule,
const WorldGeometry & world )

Per-pass-deepest-first capsule depenetration against the whole world.

Replaces the legacy AABB depen + per-feature MTV summation. Each pass:

  1. Find the single deepest contact via deepestCapsuleContact.
  2. Push by that contact's depth (no per-tick cap — the loop converges in O(touched-features) passes for normal shallow overlaps).
  3. Clip velocity component into the surface, repeat.

Oscillation detector: if pass N's contact normal points opposite to pass N-1's (dot < -k_floorAngleCos), the player straddles a 2-sided thin volume with no single consistent ejection direction. Fall straight through to emergencyUnstick() rather than ping-ponging.

Parameters
posCapsule centre — modified in place to push out of overlaps.
velVelocity — modified in place to cancel components into surfaces.
capsulePlayer capsule shape.
worldWorld collision geometry.
Here is the call graph for this function:

◆ depenetrateCapsuleVsWorldDetailed()

DepenetrationResult physics::depenetrateCapsuleVsWorldDetailed ( glm::vec3 & pos,
glm::vec3 & vel,
CapsuleShape capsule,
const WorldGeometry & world,
DepenetrationOptions options )
Here is the call graph for this function:
Here is the caller graph for this function:

◆ detectWalls()

WallDetectionResult physics::detectWalls ( glm::vec3 pos,
float yaw,
glm::vec3 halfExtents,
const WorldGeometry & world,
float checkDist,
float sphereRadius,
glm::vec3 prevWallNormal = glm::vec3(0.0f),
bool gravityFlipped = false,
bool includeGroundDistance = true )

Detect walls to the left, right, and front of the player.

Also probes downward to measure ground distance (used for wallrun min-height gates).

Parameters
posPlayer AABB centre position.
yawPlayer facing direction (radians).
halfExtentsPlayer AABB half-extents (for offset calculations).
worldWorld collision geometry.
checkDistHow far sideways/forward to trace (u).
sphereRadiusRadius of the trace sphere (u).
prevWallNormalPrevious tick's wall normal (zero if not wallrunning). When non-zero, an additional trace is cast toward -prevWallNormal to track curved surfaces (cylinders, concave walls) whose normal rotates as the player moves.
gravityFlippedTrue when the player's local up axis is -Y.
Returns
Detection results for all directions.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ emergencyUnstick()

bool physics::emergencyUnstick ( glm::vec3 & pos,
glm::vec3 & vel,
CapsuleShape capsule,
const WorldGeometry & world )

Last-resort recovery for a capsule embedded in geometry with no clear depen direction.

Probes outward in axis-aligned cardinal directions at increasing radii looking for any position where the capsule has positive clearance. When found, teleports the capsule centre there and zeros velocity. When nothing within k_emergencyUnstickRadius works, the position is left unchanged (game code should fall back to a respawn).

Returns
True if a clear position was found and pos updated.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ enforceRagdollConnectivity()

void physics::enforceRagdollConnectivity ( Registry & registry,
float dt,
int iterations = 8 )

Enforce ragdoll connectivity + angular limits via N PBD iterations.

Each iteration walks every RagdollPbdJoint in deterministic order, computes the world-space anchor error, and translates the two bodies to close the gap (split by inverse mass). Then a separate pass clamps the relative rotation between parent and child into the joint's angular limit. After all iterations, per-body velocities are derived from the net position change (PBD-style) so contact response in the next tick still sees realistic motion.

Typical configuration: 8 iterations per tick is overkill for a 15-body humanoid; 4 already gives sub-millimetre anchor error.

Here is the caller graph for this function:

◆ findWallRunAttachment()

WallAttachmentResult physics::findWallRunAttachment ( CapsuleShape capsule,
glm::vec3 pos,
const WorldGeometry & world,
glm::vec3 continuityNormal,
glm::vec3 travelDir = glm::vec3{0.0f},
float lookaheadDist = 0.0f,
float checkDist = 24.0f,
uint32_t previousMeshIndex = UINT32_MAX,
uint32_t previousTriId = UINT32_MAX,
TriRegion previousRegion = TriRegion::Face )

Find the best triangle-mesh wallrun attachment, with optional lookahead along the current travel direction.

Current-position attachment keeps ordinary wallruns stable. The lookahead sample lets convex/outside corners select the upcoming perpendicular wall before the old wall's closest point flips the tangent backward.

Here is the call graph for this function:

◆ groundProbeCapsuleVsTriMesh()

GroundProbeResult physics::groundProbeCapsuleVsTriMesh ( CapsuleShape capsule,
glm::vec3 pos,
float maxDistance,
float minWalkableDot,
const WorldTriMesh & mesh )

Downward ground probe against walkable triangle faces only.

This is stricter than general capsule CCD: finite edges and vertices block movement, but they are not valid ground support by themselves. The support point of the capsule must project onto a walkable triangle face, preventing stair treads from launching the player upward while the capsule is still in front of the tread edge.

Here is the call graph for this function:

◆ isWallNormal()

bool physics::isWallNormal ( glm::vec3 normal)
inline

Check if a surface normal represents a wall (not floor/ceiling).

Walls have normals that are roughly horizontal (|normal.y| < 0.3).

Here is the caller graph for this function:

◆ loadMapCollision()

bool physics::loadMapCollision ( const std::string & path,
MapCollisionData & out,
const MapLoadOptions & opts = {} )

API.

Extract collision geometry from a map .glb file.

Walks the Assimp scene graph. For each mesh node, determines whether it belongs to the collision collection (by checking ancestor node names) or, in all-mesh mode, always. Collision meshes are preserved as WorldTriMesh vertex-for-vertex after Assimp triangulation.

This function does not produce visual / renderable data — use the existing Renderer::loadSceneModel() path for that.

Parameters
pathAbsolute or relative path to the .glb file.
outFilled with extracted collision geometry on success.
optsLoading options (scale, collection name, all-mesh mode).
Returns
True on success; false on any Assimp load error (logged via SDL_Log).
Here is the call graph for this function:
Here is the caller graph for this function:

◆ loadPropCollision()

bool physics::loadPropCollision ( const std::string & path,
MapCollisionData & out,
glm::vec3 position,
float scale,
bool decomposeNonConvex = false )

Load collision for a standalone prop GLB and append to existing collision data.

Loads the GLB, applies the given transform (position + uniform scale), runs auto-detection on each mesh, and appends the resulting primitives to out. Call setActiveWorld(out.geometry()) after all props are loaded to update the physics world.

Parameters
pathAbsolute path to the .glb file.
outExisting collision data to append to.
positionWorld-space position of the prop.
scaleUniform scale factor.
decomposeNonConvexLegacy opt-in: when true and GROUP2_ENABLE_VHACD is enabled at configure time, non-convex meshes inside the prop are run through V-HACD convex decomposition. In normal builds this request logs and falls back to WorldTriMesh.
Returns
True on success.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ makeDiagonalWall()

WorldBrush physics::makeDiagonalWall ( glm::vec3 center,
float halfLen,
float halfThick,
float height,
glm::vec3 dir )
inline

Create a diagonal wall brush from a centre, direction, and dimensions.

Parameters
centerCentre of the wall base (y=0).
halfLenHalf-length along dir.
halfThickHalf-thickness perpendicular to dir in XZ.
heightWall height (from y=0 to y=height).
dirNormalised direction along the wall in XZ.
Here is the caller graph for this function:

◆ makeRamp()

WorldBrush physics::makeRamp ( float xMin,
float xMax,
float zMin,
float zMax,
float height )
inline

Create a ramp brush that rises along +Z.

The wedge shape goes from (xMin, 0, zMin) at ground level to (xMax, height, zMax) at the back-top edge.

Here is the caller graph for this function:

◆ probeGround()

GroundProbeResult physics::probeGround ( CapsuleShape capsule,
glm::vec3 pos,
float maxDistance,
const WorldGeometry & world )

Downward sweep that classifies the ground under a capsule.

Sweeps the capsule along -up * maxDistance from pos, then evaluates whether the first contact's normal qualifies as a floor. This is the query the modern two-capsule character controller uses each tick to (a) decide grounded vs airborne and (b) settle the foot onto the surface — replacing the legacy lift→horiz→drop swept-AABB step-up.

Parameters
capsuleThe full capsule (not the walk-shape) — the foot direction is -capsule.up.
posCapsule centre at the start of the probe.
maxDistanceHow far to probe along the foot direction. Should be effectiveStepHeight + k_groundSnapDistance for a grounded player, k_groundSnapDistance for an airborne landing check.
worldWorld collision geometry.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ probeWallrunGroundDistance()

float physics::probeWallrunGroundDistance ( glm::vec3 pos,
glm::vec3 halfExtents,
const WorldGeometry & world,
float sphereRadius,
bool gravityFlipped )

Probe only the downward ground distance used by wallrun entry gates.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ queryStaticWorldBroadphase()

void physics::queryStaticWorldBroadphase ( const StaticWorldBroadphase & broadphase,
const WorldAABB & query,
const std::function< bool(uint32_t meshIndex)> & visit )

Visit triangle-mesh indices whose mesh bounds overlap query.

The visitor returns false to stop early, true to continue.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ queryStaticWorldBroadphaseFast()

template<class Visit>
void physics::queryStaticWorldBroadphaseFast ( const StaticWorldBroadphase & broadphase,
const WorldAABB & query,
Visit && visit )
inline
Here is the call graph for this function:
Here is the caller graph for this function:

◆ raycastAABB()

bool physics::raycastAABB ( glm::vec3 origin,
glm::vec3 direction,
const WorldAABB & box,
float maxDistance,
float & outDistance,
glm::vec3 & outNormal )
inline

Ray vs axis-aligned box intersection (slab method).

Here is the caller graph for this function:

◆ raycastBrush()

bool physics::raycastBrush ( glm::vec3 origin,
glm::vec3 direction,
const WorldBrush & brush,
float maxDistance,
float & outDistance,
glm::vec3 & outNormal )
inline

Ray vs convex brush intersection (generalised slab method).

A WorldBrush is the intersection of half-spaces defined by planes with outward normals; the solid interior satisfies dot(n, p) <= distance for every plane. A ray hits the brush at the latest plane it enters from outside, provided that t doesn't exceed the earliest plane it exits. Mirrors the logic in sweepAABBvsBrush but for a point-ray instead of a swept AABB.

Here is the caller graph for this function:

◆ raycastCapsule()

bool physics::raycastCapsule ( glm::vec3 origin,
glm::vec3 dir,
glm::vec3 A,
glm::vec3 B,
float r,
float maxDist,
float & outDist,
glm::vec3 & outNormal )
inline

Ray vs capsule intersection.

A capsule is the Minkowski sum of the line segment AB and a sphere of radius r. Decomposed into: (1) ray vs infinite cylinder along AB, clamped to the segment, then (2) ray vs hemisphere endcaps.

Returns
True if the ray hits the capsule within maxDist.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ raycastCylinder()

bool physics::raycastCylinder ( glm::vec3 origin,
glm::vec3 direction,
const WorldCylinder & cyl,
float maxDistance,
float & outDistance,
glm::vec3 & outNormal )
inline

Ray vs vertical cylinder intersection.

Here is the caller graph for this function:

◆ raycastPlayerHitboxes()

HitboxHit physics::raycastPlayerHitboxes ( Registry & registry,
entt::entity shooter,
glm::vec3 origin,
glm::vec3 direction,
float maxDistance,
float bulletRadius = 0.0f )
inline

Raycast against all player hitbox capsules (skeleton-driven).

Uses a broad-phase AABB check (CollisionShape) before testing individual capsules, so only nearby players pay the narrow-phase cost.

PR-20.6 (root-cause fix): pre-PR-20.6 the view required the Player tag. That worked on the server (which emplaces Player per connected client), but not on the CLIENT — Player is not in the replicated Synced tuple (network/RegistrySerialization.cpp) and the client never emplaces it locally. Result: client-side resolveHitscanHitbox walked an empty view, no capsule hits ever registered, and the shot-debug visualizer's blue tracer flew straight through every enemy. HitboxInstance is only added by systems::updateHitboxes to entities with JointMatrices — i.e. animated characters — so it's a stronger gate than Player anyway and the filter is now strictly more permissive without picking up any non-character entities.

Parameters
bulletRadiusCylinder/swept-sphere radius (world units) added to every capsule's radius. A thick ray of radius R vs a capsule of radius r is the thin ray vs a capsule of radius (R + r) — so the whole "cylinder hitreg" reduces to inflating the capsule. 0 = exact ray (legacy). The reported hit distance is the distance to the bullet CENTRE at first contact (slightly in front of the surface).
Here is the call graph for this function:
Here is the caller graph for this function:

◆ raycastPlayers()

HitscanHit physics::raycastPlayers ( Registry & registry,
entt::entity shooter,
glm::vec3 origin,
glm::vec3 direction,
float maxDistance )
inline

Raycast against all player hitboxes (axis-aligned capsule approximation).

Here is the call graph for this function:
Here is the caller graph for this function:

◆ raycastSphere()

bool physics::raycastSphere ( glm::vec3 origin,
glm::vec3 direction,
const WorldSphere & sph,
float maxDistance,
float & outDistance,
glm::vec3 & outNormal )
inline

Ray vs sphere intersection.

Here is the caller graph for this function:

◆ raycastTriMesh()

bool physics::raycastTriMesh ( glm::vec3 origin,
glm::vec3 direction,
const WorldTriMesh & mesh,
float maxDistance,
float & outDistance,
glm::vec3 & outNormal )
inline

Convenience wrapper for callers that don't care which triangle was hit.

Here is the call graph for this function:

◆ raycastTriMeshIndexed()

bool physics::raycastTriMeshIndexed ( glm::vec3 origin,
glm::vec3 direction,
const WorldTriMesh & mesh,
float maxDistance,
float & outDistance,
glm::vec3 & outNormal,
uint32_t & outTriIdx )
inline

Raycast against a triangle mesh using BVH-accelerated Möller-Trumbore.

Indexed variant — also reports the canonical triangle index that was hit, so callers can look up per-triangle materials in the mesh.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ raycastWorld()

HitscanHit physics::raycastWorld ( glm::vec3 origin,
glm::vec3 direction,
const WorldGeometry & world )
inline

Raycast against all static world geometry (planes + boxes + cylinders + spheres).

Here is the call graph for this function:
Here is the caller graph for this function:

◆ resolveHitscan()

HitscanHit physics::resolveHitscan ( Registry & registry,
entt::entity shooter,
glm::vec3 origin,
glm::vec3 direction )
inline

Full hitscan resolution: world geometry first, then players (closest wins).

Here is the call graph for this function:

◆ resolveHitscanHitbox()

HitboxHit physics::resolveHitscanHitbox ( Registry & registry,
entt::entity shooter,
glm::vec3 origin,
glm::vec3 direction,
float bulletRadius = 0.0f )
inline

Full hitscan with skeleton-driven hitboxes.

World geometry first, then player hitbox capsules (closest wins). Falls back to the old AABB path for players without HitboxInstance.

Parameters
bulletRadiusSwept-sphere "cylinder hitreg" radius (world units) applied to PLAYER hitboxes only. World geometry stays a thin ray so bullets still require crosshair line-of-sight past walls. 0 = ray.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ setActiveWorld()

void physics::setActiveWorld ( const WorldGeometry & geo)
inline

Set the world geometry that activeWorld() returns.

The caller retains ownership of the memory behind the spans — the pointed-to data must outlive every call to activeWorld(). Typically backed by a MapCollisionData whose vectors live for the duration of the game.

Both client and server must call this with identical data before the first physics tick to maintain prediction parity.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ solveContacts()

void physics::solveContacts ( Registry & registry,
ContactCache & cache,
const SolverConfig & cfg,
float dt )

Solve every cached contact manifold for the current tick.

Bodies' Velocity / AngularVelocity components are updated in place.

Caller responsibilities:

  1. ContactCache::endFrame() after this returns (cleans stale manifolds).
  2. Re-extract manifolds back into the cache so accumulated impulses persist (already handled by Solver internally — it writes back after each iteration).
Here is the call graph for this function:
Here is the caller graph for this function:

◆ solveJoints()

void physics::solveJoints ( Registry & registry,
const SolverConfig & cfg,
float dt )

Solve every joint in the registry using sequential impulses.

Called from the same physics step as solveContacts. Joints are iterated in stable order (sorted by entity id) for determinism.

Here is the caller graph for this function:

◆ sphereCast()

SphereHitResult physics::sphereCast ( float radius,
glm::vec3 start,
glm::vec3 end,
const WorldGeometry & world )

Cast a sphere along the path [start, end] against all world geometry.

Uses the Minkowski-sum approach: geometry is expanded by the sphere radius, then the sweep becomes a point (ray) test against the expanded geometry.

Parameters
radiusSphere radius (u).
startWorld-space start of sweep (sphere centre).
endWorld-space end of sweep (sphere centre).
worldWorld collision geometry to test against.
Returns
Earliest hit, or SphereHitResult{hit=false} if clear.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ sweepAABB()

HitResult physics::sweepAABB ( glm::vec3 halfExtents,
glm::vec3 start,
glm::vec3 end,
std::span< const Plane > planes )

Sweep an AABB along the path [start, end] against a list of infinite planes.

Uses the Minkowski-sum approach: each plane is expanded outward by the AABB half-extents, reducing the problem to a ray-vs-expanded-plane intersection.

Parameters
halfExtentsHalf-dimensions of the AABB.
startWorld-space start position (AABB centre).
endWorld-space end position (AABB centre).
planesWorld collision planes to test against.
Returns
Earliest hit within the sweep, or HitResult{hit=false} if the path is clear.
Note
Entities that start already inside a plane are skipped. Depenetration is handled separately by CollisionSystem before calling this.
Here is the caller graph for this function:

◆ sweepAABBvsBox()

HitResult physics::sweepAABBvsBox ( glm::vec3 halfExtents,
glm::vec3 start,
glm::vec3 end,
const WorldAABB & box )

Sweep an AABB against a static axis-aligned box.

Expands the static box by the moving AABB's half-extents (Minkowski sum) and performs a ray-slab intersection test on the swept centre point. Entities starting inside the box are skipped (depenetration handles that).

Here is the caller graph for this function:

◆ sweepAABBvsBrush()

HitResult physics::sweepAABBvsBrush ( glm::vec3 halfExtents,
glm::vec3 start,
glm::vec3 end,
const WorldBrush & brush )

Sweep an AABB against a convex brush (set of bounding planes).

Finds the time at which the AABB enters all half-spaces simultaneously. Entities starting inside the brush are skipped (depenetration handles that).

Here is the caller graph for this function:

◆ sweepAABBvsCylinder()

HitResult physics::sweepAABBvsCylinder ( glm::vec3 halfExtents,
glm::vec3 start,
glm::vec3 end,
const WorldCylinder & cyl )

Sweep an AABB against a vertical cylinder.

Minkowski-expands the cylinder by the AABB half-extents: the radius grows by the XZ extent and the height caps grow by the Y extent. The sweep then reduces to a 2D ray-vs-circle test (XZ) clamped by the expanded Y slab.

Here is the caller graph for this function:

◆ sweepAABBvsSphere()

HitResult physics::sweepAABBvsSphere ( glm::vec3 halfExtents,
glm::vec3 start,
glm::vec3 end,
const WorldSphere & sph )

Sweep an AABB against a sphere.

Minkowski-expands the sphere radius by the AABB half-extents (approximation: uses the max half-extent component, making it slightly conservative at corners). Then performs a ray-vs-sphere test on the swept centre.

Here is the caller graph for this function:

◆ sweepAABBvsTriMesh()

HitResult physics::sweepAABBvsTriMesh ( glm::vec3 halfExtents,
glm::vec3 start,
glm::vec3 end,
const WorldTriMesh & mesh )

Sweep an AABB against a triangle mesh using Voronoi-clipped per-triangle tests.

Returns the earliest contact whose closest feature on the hit triangle is an active Voronoi region (face interior or active edge / vertex). Contacts in inactive regions are discarded — the neighbour triangle whose active region actually owns that point will produce the correct face-normal contact instead.

For capsule shapes (Phase 5), pass halfExtents = (radius, radius + halfHeight, radius) — the resulting Minkowski hull is conservative but exact for axis-aligned face normals (the most common case for hand-authored maps). Truly diagonal triangle face normals (e.g. a 45° ramp triangle) produce a slight (sqrt(2)-1)*radius over-estimate of player size.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ sweepAll() [1/2]

HitResult physics::sweepAll ( CapsuleShape capsule,
glm::vec3 start,
glm::vec3 end,
const WorldGeometry & world )

Sweep a capsule against all world geometry, returning the earliest hit.

Here is the call graph for this function:

◆ sweepAll() [2/2]

HitResult physics::sweepAll ( glm::vec3 halfExtents,
glm::vec3 start,
glm::vec3 end,
const WorldGeometry & world )

Sweep an AABB against all world geometry, returning the earliest hit.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ sweepCapsuleVsBox()

HitResult physics::sweepCapsuleVsBox ( CapsuleShape capsule,
glm::vec3 start,
glm::vec3 end,
const WorldAABB & box )

Sweep a capsule against a static axis-aligned box. Conservative.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ sweepCapsuleVsBrush()

HitResult physics::sweepCapsuleVsBrush ( CapsuleShape capsule,
glm::vec3 start,
glm::vec3 end,
const WorldBrush & brush )

Sweep a capsule against a convex brush. Exact (per-plane Minkowski extent).

Here is the call graph for this function:
Here is the caller graph for this function:

◆ sweepCapsuleVsCylinder()

HitResult physics::sweepCapsuleVsCylinder ( CapsuleShape capsule,
glm::vec3 start,
glm::vec3 end,
const WorldCylinder & cyl )

Sweep a capsule against a vertical cylinder. Conservative.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ sweepCapsuleVsPlanes()

HitResult physics::sweepCapsuleVsPlanes ( CapsuleShape capsule,
glm::vec3 start,
glm::vec3 end,
std::span< const Plane > planes )

Sweep a capsule along [start, end] against a list of infinite planes.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ sweepCapsuleVsSphere()

HitResult physics::sweepCapsuleVsSphere ( CapsuleShape capsule,
glm::vec3 start,
glm::vec3 end,
const WorldSphere & sph )

Sweep a capsule against a sphere. Conservative.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ sweepCapsuleVsTriMesh()

HitResult physics::sweepCapsuleVsTriMesh ( CapsuleShape capsule,
glm::vec3 start,
glm::vec3 end,
const WorldTriMesh & mesh )

Sweep a capsule against a triangle mesh.

Uses capsule-axis vs bounded-triangle closest-point queries with conservative advancement. Finite face, edge, and vertex features block the capsule, and triangles are treated as two-sided thin surfaces by default.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ testWorld()

const WorldGeometry & physics::testWorld ( )
inline

The physics test playground.

Layout along the +Z axis (forward from spawn at origin):

z ~ 400 : Reference cube (64^3) z ~ 800 : Small steppable box (64x16x64) and large jumpable box (80x64x80) z ~ 1000 : Gentle ramp (15deg, left) and steep ramp (40deg, right) z ~ 1500 : Stairs (5 steps rising along +Z) z ~ 1900 : Axis-aligned wall, diagonal wall, pole z ~ 2100 : Elevated thin walkway

Here is the call graph for this function:
Here is the caller graph for this function:

◆ updateSleep()

void physics::updateSleep ( Registry & registry,
const SleepConfig & cfg )

Update each body's sleep state from its current velocities.

Called once per tick after the solver has converged.

Here is the caller graph for this function:

◆ validateTriMesh()

TriMeshValidationReport physics::validateTriMesh ( const WorldTriMesh & mesh,
float positionEpsilon = 1e-4f )

Validate authored collision mesh topology before/after cooking.

This does not mutate the mesh and only inspects vertices / indices, so it can run before BVH construction or after load. It catches the map pipeline issues that destabilize thin-surface KCC contacts: degenerate triangles, duplicated opposite-winding faces, non-manifold edges, and out-of-range indices.

Here is the caller graph for this function:

◆ validateTriMeshes()

TriMeshValidationTotals physics::validateTriMeshes ( std::span< const WorldTriMesh > meshes,
float positionEpsilon )

Validate every triangle mesh in an authored collision set.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ wakeBody()

void physics::wakeBody ( Registry & registry,
entt::entity e )

Wake a single body (e.g.

for an explicit force). Does NOT propagate through the contact graph; use wakeIslandOf for that.

Here is the caller graph for this function:

◆ wakeIslandOf()

void physics::wakeIslandOf ( Registry & registry,
const ContactCache & cache,
entt::entity e )

Wake every body that's currently in the same contact island as e.

Walks the contact graph in cache using BFS; O(island size).

Here is the call graph for this function:

◆ weldTriMesh()

void physics::weldTriMesh ( WorldTriMesh & mesh,
float coplanarTolerance = 0.0349065850f )

Compute face normals + active edge / vertex flags for a triangle mesh.

Must be called AFTER buildTriMeshBVH() (it only depends on vertices and indices, but conventionally pairs with the BVH build). Populates the mesh's faceNormals, edgeActive, and vertActive arrays.

Algorithm. Each shared edge between two triangles is classified by dihedral angle:

  • Convex edges (folds outward) with angle > coplanarTolerance are marked active — these are real corners contacts should fire on.
  • Concave and coplanar edges are marked inactive ("welded"), so the runtime depenetration discards Voronoi-region contacts on them. Boundary edges (used by exactly one triangle) and non-manifold edges (>2 triangles) are always active. Vertex flags are derived: a vertex is active in a triangle iff one of the two incident edges in that triangle is active.
Parameters
meshTriangle mesh to weld (modified in place).
coplanarToleranceDihedral angle (radians) below which an edge counts as flat. Default ~2° — matches Bullet's internal-edge utility and Jolt's MeshShape default.

Variable Documentation

◆ k_airAccel

float physics::k_airAccel
constexpr
Initial value:
=
700.0f

Air acceleration constant. Higher than Quake (0.7) for Titanfall-style air control.

◆ k_airMaxSpeed

float physics::k_airMaxSpeed = 30.0f
constexpr

Wish-speed FLOOR in air (units/s).

The wish-speed cap once horizontal speed exceeds k_airWishCurveTop. Preserves classic Quake strafe-jump physics at speed. Does NOT cap total speed — existing momentum is preserved.

◆ k_airMaxWishLowSpeed

float physics::k_airMaxWishLowSpeed = 120.0f
constexpr

Wish-speed CEILING in air (units/s) when stationary.

◆ k_airWishCurveExponent

float physics::k_airWishCurveExponent = 0.4f
constexpr

Power-curve exponent for wish-speed falloff (<1 = sharp early drop).

t' = pow(t, e).

◆ k_airWishCurveTop

float physics::k_airWishCurveTop = 250.0f
constexpr

Horizontal speed (u/s) at which the curve plateaus at k_airMaxSpeed.

◆ k_emergencyUnstickRadius

float physics::k_emergencyUnstickRadius = 64.0f
constexpr

Maximum radius of the emergency-unstick free-space search.

When per-pass depen fails to resolve penetration, we probe outward up to this far in cardinal directions for a clear teleport target.

◆ k_enableSubstepping

bool physics::k_enableSubstepping = true
constexpr

Master toggle for Phase-C sub-stepping.

◆ k_explosionGroundPopOffset

float physics::k_explosionGroundPopOffset = 10.0f
constexpr

Upward teleport (units) at epicenter; lifts the victim past k_groundSnapDistance so the KCC won't re-anchor them this tick.

◆ k_explosionGroundVerticalBoost

float physics::k_explosionGroundVerticalBoost
constexpr
Initial value:
=
250.0f

Minimum upward velocity (units/s) at epicenter.

Floored, not added — never reduces a stronger existing vertical knockback.

◆ k_floorAngleCos

float physics::k_floorAngleCos = 0.7f
constexpr

dot(surfaceNormal, up) threshold above which a surface counts as walkable floor.

Cos(45.6°) ≈ 0.7 — surfaces steeper than this are walls, not floors.

◆ k_friction

float physics::k_friction = 7.5f
constexpr

Ground friction coefficient. Higher = crisper stops, easier-to-track movement.

◆ k_gravity

float physics::k_gravity = 1000.0f
constexpr

Downward acceleration (units/s^2) for projectiles / dynamics.

Real-world-ish scale for grenades and rigid bodies. The player uses k_playerGravity instead.

◆ k_gravityFlipCooldown

float physics::k_gravityFlipCooldown = 0.5f
constexpr

Minimum time between gravity flips (s).

◆ k_groundAccel

float physics::k_groundAccel = 10.0f
constexpr

Ground acceleration constant. Higher = reaches max speed faster.

◆ k_groundSnapDistance

float physics::k_groundSnapDistance = 8.0f
constexpr

Distance the ground probe extends below the capsule foot to snap to descending slopes / steps.

Allows a grounded player to follow stair-downs and slope-downs without going airborne for a tick.

◆ k_hitscanRange

float physics::k_hitscanRange = 5000.0f
inlineconstexpr

Maximum hitscan distance in world units.

◆ k_jumpSpeed

float physics::k_jumpSpeed = 660.0f
constexpr

Initial upward velocity on jump (units/s).

Apex = k_jumpSpeed^2 / (2*k_playerGravity) ~ 54 units (~4.5 ft). Mirrored by tms::k_jumpSpeed.

◆ k_maxContactPoints

int physics::k_maxContactPoints = 4
inlineconstexpr

Up to 4 points per body pair — enough for any flat-face contact (box-on-box, box-on-ground, etc.).

Curved-on-curved (sphere-sphere) uses 1 point. Capsule-on-flat uses 1–2 points.

◆ k_maxDepenPasses

int physics::k_maxDepenPasses = 6
constexpr

Maximum sequential passes the deepest-first capsule depen attempts before falling through to emergency unstick.

◆ k_maxSubsteps

int physics::k_maxSubsteps = 8
constexpr

Clamp on sub-step count to bound worst-case cost.

◆ k_overbounceFloor

float physics::k_overbounceFloor = 1.0f
constexpr

Floor overbounce — exactly 1.0 means no bounce.

◆ k_overbounceWall

float physics::k_overbounceWall = 1.001f
constexpr

Separation impulse for walls/ceilings; prevents corner-sticking.

◆ k_parallelEpsilon

float physics::k_parallelEpsilon = 1e-6f
inlineconstexpr

Epsilon for parallel-ray checks.

◆ k_playerGravity

float physics::k_playerGravity
constexpr
Initial value:
=
2.0f * k_gravity
constexpr float k_gravity
Downward acceleration (units/s^2) for projectiles / dynamics.
Definition PhysicsConstants.hpp:17

Player-specific gravity (units/s^2).

Doubled vs. k_gravity so the player's apex height and air-time match what the previous (buggy) KCC integration produced — the older KCC double-integrated vertical motion per tick, so all jump-related velocities and the player's gravity are doubled here to preserve the established gameplay feel with the corrected single-integration KCC.

◆ k_stepHeight

float physics::k_stepHeight = 18.0f
constexpr

Maximum obstacle height auto-stepped over without jumping (units).

◆ k_stopSpeed

float physics::k_stopSpeed = 150.0f
constexpr

Friction is amplified below this speed for a crisp stop.

◆ k_substepSafetyRatio

float physics::k_substepSafetyRatio = 0.5f
constexpr

Sub-step when |v|·dt > min_shape_radius · this.