Specialized components
Physics and spatial systems
Physics system
22 min
overview the physics system simulates physical interactions between objects in the 3d environment it enables entities to be affected by gravity, collide with each other, and respond to forces built on the rapier physics engine, this system maintains a separate physics simulation that runs alongside the visual representation, calculating how objects should move and interact based on their physical properties core components the physics system consists of several key components that work together to create realistic physical behavior rigidbodycomponent the rigidbodycomponent designates an entity as a physical object within the simulation it defines the object's physical properties and how it should behave in the physics world key properties type determines how the object behaves in the physics simulation dynamic fully simulated objects affected by gravity and forces fixed immovable objects that serve as obstacles kinematic objects moved programmatically that can still collide with dynamic objects mass the object's mass (affects how forces impact it) linearvelocity the object's current velocity vector angularvelocity the object's current rotational velocity // simplified from src/physics/components/rigidbodycomponent tsx export const rigidbodycomponent = definecomponent({ name 'rigidbodycomponent', schema s object({ type s enum(bodytypes, { default bodytypes dynamic }), mass s number({ default 1 }), linearvelocity t vec3({ default { x 0, y 0, z 0 } }), angularvelocity t vec3({ default { x 0, y 0, z 0 } }), // additional properties }), // implementation details }); collidercomponent the collidercomponent defines the physical shape of an entity for collision detection this shape is often simpler than the visual model to optimize performance key properties shape the geometric shape used for collision detection (box, sphere, capsule, etc ) dimension properties size parameters specific to the chosen shape (radius, height, etc ) friction how much the object resists sliding against other objects restitution how "bouncy" the object is during collisions // simplified from src/physics/components/collidercomponent tsx export const collidercomponent = definecomponent({ name 'collidercomponent', schema s object({ shape s enum(shapes, { default shapes box }), boxsize t vec3({ default { x 1, y 1, z 1 } }), radius s number({ default 0 5 }), height s number({ default 1 }), friction s number({ default 0 5 }), restitution s number({ default 0 }), // additional properties }), // implementation details }); physics systems the physics simulation is managed by two primary systems physicssystem the physicssystem is the main coordinator that advances the physics simulation each frame its responsibilities include storing the current physics state for interpolation advancing the rapier physics simulation retrieving updated positions and rotations from rapier processing collision events // simplified concept from src/physics/systems/physicssystem tsx function execute() { // get all non fixed rigid bodies const allrigidbodies = nonfixedrigidbodyquery(); // store current physics pose as 'previous' for interpolation physics updatepreviousrigidbodypose(allrigidbodies); // get simulation parameters const { simulationtimestep } = getstate(ecsstate); const kinematicentities = kinematicquery(); // run the rapier physics simulation physics simulate(simulationtimestep, kinematicentities); // update rigidbodycomponent with new poses from rapier physics updaterigidbodypose(allrigidbodies); // process collision events // } physicspretransformsystem the physicspretransformsystem serves as the bridge between the physics simulation and the visual representation it runs before the main transform system and updates rapier rigid bodies when entities are manually moved updates entity transformcomponent s with the results of the physics simulation handles interpolation for smooth visual movement // simplified concept from src/physics/systems/physicspretransformsystem ts function execute() { // for entities whose transformcomponent was manually changed for (const entity of dirtyrigidbodyentities) { copytransformtorigidbody(entity); } // for entities moved by physics const alpha = calculateinterpolationfactor(); for (const entity of awakecleanrigidbodyentities) { lerptransformfromrigidbody(entity, alpha); } } function lerptransformfromrigidbody(entity, alpha) { // get rigidbodycomponent with physics calculated position/rotation const rigidbody = getcomponent(entity, rigidbodycomponent); // interpolate between previous and current physics poses const interpolatedposition = vector3 lerp( rigidbody previousposition, rigidbody position, alpha ); // update the entity's transformcomponent const transform = getcomponent(entity, transformcomponent); transform position copy(interpolatedposition); // similar for rotation // mark the transformcomponent as dirty for the transform system transformcomponent dirty\[entity] = 1; } physics wrapper the physics class ( src/physics/classes/physics ts ) serves as a wrapper around the rapier physics engine, providing a bridge between the ecs architecture and rapier's object oriented api key functions include createworld creates a new rapier physics world with specified gravity // simplified from src/physics/classes/physics ts function createworld(id entity, args = { gravity { x 0 0, y 9 81, z 0 0 }}) { const world = new rapier world(args gravity); // store in rapierworldstate return world; } createrigidbody creates a rapier rigid body from an entity's components // simplified from src/physics/classes/physics ts function createrigidbody(world rapier world, entity entity) { // get entity's transform and rigid body data const transform = getcomponent(entity, transformcomponent); const rigidbodydata = getcomponent(entity, rigidbodycomponent); // create appropriate rapier rigid body description let rapierbodydesc; if (rigidbodydata type === bodytypes dynamic) { rapierbodydesc = rapier rigidbodydesc dynamic(); } else if (rigidbodydata type === bodytypes fixed) { rapierbodydesc = rapier rigidbodydesc fixed(); } else if (rigidbodydata type === bodytypes kinematic) { rapierbodydesc = rapier rigidbodydesc kinematicpositionbased(); } // set position and rotation rapierbodydesc settranslation(transform position x, transform position y, transform position z); // set rotation from quaternion // create the actual body in the rapier world const body = world createrigidbody(rapierbodydesc); body entity = entity; // store entity id for later lookup world rigidbodies set(entity, body); // track the body } createcolliderdesc and attachcollider create and attach collision shapes to rigid bodies // simplified from src/physics/classes/physics ts function createcolliderdesc(world rapier world, entity entity) { const colliderdata = getcomponent(entity, collidercomponent); // create appropriate collider based on shape let rapiercolliderdesc; if (colliderdata shape === shapes box) { rapiercolliderdesc = rapier colliderdesc cuboid( colliderdata boxsize x/2, colliderdata boxsize y/2, colliderdata boxsize z/2 ); } else if (colliderdata shape === shapes sphere) { rapiercolliderdesc = rapier colliderdesc ball(colliderdata radius); } // additional shapes // set physical properties rapiercolliderdesc setfriction(colliderdata friction); rapiercolliderdesc setrestitution(colliderdata restitution); return rapiercolliderdesc; } function attachcollider(world rapier world, desc rapier colliderdesc, rbentity entity, colentity entity) { const rigidbody = world rigidbodies get(rbentity); const collider = world createcollider(desc, rigidbody); collider entity = colentity; world colliders set(colentity, collider); } simulate advances the physics simulation by one time step // simplified from src/physics/classes/physics ts function simulate(simulationtimestep number, kinematicentities entity\[]) { // for each physics world for (const \[worldid, world] of rapierworldstate worlds) { // calculate time step const timestep = simulationtimestep / 1000 / world substeps; // run multiple substeps for stability for (let i = 0; i < world substeps; i++) { // handle kinematic body updates // advance the rapier simulation world step(world collisioneventqueue); // process collision events } } } physics workflow example the following example demonstrates how to create a ball that falls onto a floor 1\ creating the floor (fixed rigid body) const floorentity = createentity(); // position the floor at the origin setcomponent(floorentity, transformcomponent, { position { x 0, y 0, z 0 } }); // make it a physical object that doesn't move setcomponent(floorentity, rigidbodycomponent, { type bodytypes fixed }); // define its physical shape as a box setcomponent(floorentity, collidercomponent, { shape shapes box, boxsize { x 10, y 0 1, z 10 } }); 2\ creating the ball (dynamic rigid body) const ballentity = createentity(); // position the ball above the floor setcomponent(ballentity, transformcomponent, { position { x 0, y 5, z 0 } }); // make it a physical object affected by gravity setcomponent(ballentity, rigidbodycomponent, { type bodytypes dynamic }); // define its physical shape as a sphere setcomponent(ballentity, collidercomponent, { shape shapes sphere, radius 0 5, restitution 0 5 // make it bounce }); 3\ physics simulation flow when the engine runs component reactors register the entities with rapier each frame, the physicssystem advances the simulation gravity pulls the ball downward rapier detects the collision between the ball and floor rapier resolves the collision, preventing penetration the physicspretransformsystem updates the ball's transformcomponent the rendering system draws the ball at its new position sequencediagram participant usercode participant compreactors as component reactors participant physicssystem participant physicspretfsys as physicspretransformsystem participant rapierengine as rapier (via physics ts) participant transformcomp as transformcomponent usercode >>compreactors add rigidbody/collider to entities compreactors >>rapierengine create rapier bodies & colliders note over physicssystem game loop tick physicssystem >>rapierengine simulate physics step note right of rapierengine apply gravity\<br/>detect collisions\<br/>resolve contacts rapierengine >>physicssystem updated physics poses physicssystem >>physicssystem update rigidbodycomponent data physicspretfsys >>physicspretfsys read physics poses physicspretfsys >>transformcomp update entity transforms note over transformcomp rendering system uses updated transforms advanced physics features the physics system supports several advanced features collision detection and events the system can detect and respond to collision events // add a collision component to receive events setcomponent(ballentity, collisioncomponent); // in a system or reactor const collision = getcomponent(ballentity, collisioncomponent); if (collision colliding) { // the ball is colliding with something for (const collidingentity of collision collidingentities) { // handle specific collision } } joints and constraints physical constraints can be created between entities // create a ball and socket joint between two entities setcomponent(jointentity, jointcomponent, { type jointtypes ballsocket, bodya entitya, bodyb entityb, anchor { x 0, y 1, z 0 } }); raycasting the physics system provides raycasting for detecting objects along a ray // cast a ray from origin in direction const hits = physics raycast( world, origin, direction, maxdistance, collisiongroups ); // process hits for (const hit of hits) { const hitentity = hit collider entity; const hitdistance = hit toi; // handle hit } next steps with an understanding of how entities can interact physically, the next chapter explores reference space management, which handles different coordinate systems within the virtual environment next reference space management docid\ jv 2db5evnyb4piiqn3yi