Physics Components
RigidBody, Collider, CharacterController, and Joint components powered by Rapier3D physics engine.
Physics components enable realistic physical simulation including gravity, collisions, forces, and constraints. Web Engine uses Rapier3D for high-performance physics simulation with support for dynamic, kinematic, and static bodies.
RigidBody#
The RigidBody component adds physics simulation to an entity. Supports dynamic (fully simulated), kinematic (script-controlled), and fixed (static) body types.
Properties#
| Property | Type | Default | Description |
|---|---|---|---|
| velocity | [f32, 3] | [0, 0, 0] | Linear velocity [x, y, z] in m/s |
| angularVelocity | [f32, 3] | [0, 0, 0] | Angular velocity [x, y, z] in rad/s |
| mass | f32 | 1.0 | Mass in kilograms (0=infinite for static) |
| friction | f32 | 0.5 | Friction coefficient (0=frictionless, 1=high friction) |
| restitution | f32 | 0.0 | Bounciness (0=no bounce, 1=perfect bounce) |
| type | ui8 | 0 | 0=Dynamic, 1=Fixed, 2=KinematicPosition, 3=KinematicVelocity |
| ccd | ui8 | 0 | Continuous collision detection (0=off, 1=on) |
| lockRotation | [ui8, 3] | [0, 0, 0] | Lock rotation axes [x, y, z] (1=locked) |
Body Types#
- Dynamic (0): Fully simulated by physics engine, affected by forces, gravity, and collisions
- Fixed (1): Static, never moves, infinite mass (walls, floors, terrain)
- KinematicPosition (2): Controlled by script position, affects other bodies, not affected by forces
- KinematicVelocity (3): Controlled by script velocity, affects other bodies, not affected by forces
Usage#
import { addComponent, RigidBody, Collider } from '@web-engine/core'; // Create dynamic physics object (ball)const ball = world.addEntity();addComponent(world, Transform, ball);addComponent(world, RigidBody, ball);addComponent(world, Collider, ball); RigidBody.type[ball] = 0; // DynamicRigidBody.mass[ball] = 1.0; // 1 kgRigidBody.friction[ball] = 0.3;RigidBody.restitution[ball] = 0.8; // Bouncy Collider.type[ball] = 1; // SphereCollider.size[ball][0] = 0.5; // radius // Create static floorconst floor = world.addEntity();addComponent(world, Transform, floor);addComponent(world, RigidBody, floor);addComponent(world, Collider, floor); RigidBody.type[floor] = 1; // Fixed/StaticCollider.type[floor] = 0; // BoxCollider.size[floor][0] = 50; // half-widthCollider.size[floor][1] = 0.5; // half-heightCollider.size[floor][2] = 50; // half-depthCCD for Fast Objects
Enable Continuous Collision Detection (CCD) for fast-moving objects like bullets to prevent tunneling through thin walls. CCD is more expensive, use sparingly.
Collider#
The Collider component defines the collision shape for physics bodies. Supports box, sphere, capsule, heightfield, trimesh, and convex hull shapes.
Properties#
| Property | Type | Default | Description |
|---|---|---|---|
| type | ui8 | 0 | 0=Box, 1=Sphere, 2=Capsule, 3=Heightfield, 4=Trimesh, 5=ConvexHull |
| size | [f32, 3] | [0.5, 0.5, 0.5] | Shape dimensions (varies by type) |
| offset | [f32, 3] | [0, 0, 0] | Local offset from entity center [x, y, z] |
| isSensor | ui8 | 0 | Sensor/trigger (0=solid, 1=trigger) |
| collisionGroup | ui16 | 0xFFFF | Collision group membership bitmask |
| collisionMask | ui16 | 0xFFFF | Collision filter mask (which groups to collide with) |
| solverGroup | ui16 | 0xFFFF | Solver group membership |
| solverMask | ui16 | 0xFFFF | Solver filter mask |
Collider Shapes#
| Shape | Type | size[0] | size[1] | size[2] | Use Case |
|---|---|---|---|---|---|
| Box | 0 | half-width | half-height | half-depth | Crates, walls, floors |
| Sphere | 1 | radius | — | — | Balls, explosions |
| Capsule | 2 | radius | height | — | Characters, pills |
| Heightfield | 3 | — | — | — | Terrain (use ColliderDataMap) |
| Trimesh | 4 | — | — | — | Complex meshes (use ColliderDataMap) |
| ConvexHull | 5 | — | — | — | Simplified convex shapes |
Usage#
import { addComponent, Collider } from '@web-engine/core'; // Box colliderconst box = world.addEntity();addComponent(world, Collider, box);Collider.type[box] = 0; // BoxCollider.size[box][0] = 1.0; // half-width (total width = 2.0)Collider.size[box][1] = 1.0; // half-heightCollider.size[box][2] = 1.0; // half-depth // Sphere colliderconst sphere = world.addEntity();addComponent(world, Collider, sphere);Collider.type[sphere] = 1; // SphereCollider.size[sphere][0] = 0.5; // radius // Capsule collider (character)const character = world.addEntity();addComponent(world, Collider, character);Collider.type[character] = 2; // CapsuleCollider.size[character][0] = 0.3; // radiusCollider.size[character][1] = 1.8; // heightCollider.offset[character][1] = 0.9; // Center at half height // Sensor (trigger) colliderconst trigger = world.addEntity();addComponent(world, Collider, trigger);Collider.type[trigger] = 0; // BoxCollider.size[trigger] = [2, 2, 2];Collider.isSensor[trigger] = 1; // Trigger, no collisionCollision Filtering
Use collisionGroup and collisionMask for advanced filtering. A collision occurs when (A.collisionGroup & B.collisionMask) !== 0 AND (B.collisionGroup & A.collisionMask) !== 0.
Collision Filtering Example#
// Define groups (bitmasks)const GROUP_PLAYER = 1 << 0; // Bit 0const GROUP_ENEMY = 1 << 1; // Bit 1const GROUP_PROJECTILE = 1 << 2; // Bit 2const GROUP_GROUND = 1 << 3; // Bit 3 // Player collides with enemies, projectiles, and groundCollider.collisionGroup[player] = GROUP_PLAYER;Collider.collisionMask[player] = GROUP_ENEMY | GROUP_PROJECTILE | GROUP_GROUND; // Enemy collides with player, projectiles, and groundCollider.collisionGroup[enemy] = GROUP_ENEMY;Collider.collisionMask[enemy] = GROUP_PLAYER | GROUP_PROJECTILE | GROUP_GROUND; // Projectile collides with player, enemy, and ground (but not other projectiles)Collider.collisionGroup[projectile] = GROUP_PROJECTILE;Collider.collisionMask[projectile] = GROUP_PLAYER | GROUP_ENEMY | GROUP_GROUND; // Ground collides with everythingCollider.collisionGroup[ground] = GROUP_GROUND;Collider.collisionMask[ground] = 0xFFFF; // All groupsVelocity#
The Velocity component stores linear and angular velocity for kinematic movement or as input/output for physics simulation.
Properties#
| Property | Type | Default | Description |
|---|---|---|---|
| linear | [f32, 3] | [0, 0, 0] | Linear velocity [x, y, z] in m/s |
| angular | [f32, 3] | [0, 0, 0] | Angular velocity [x, y, z] in rad/s |
CharacterController#
The CharacterController component provides kinematic character movement with ground detection, slope handling, and stair climbing.
Properties#
| Property | Type | Default | Description |
|---|---|---|---|
| speed | f32 | 5.0 | Movement speed in m/s |
| jumpHeight | f32 | 2.0 | Jump height in meters |
| verticalVelocity | f32 | 0.0 | Current vertical velocity (gravity) |
| grounded | ui8 | 0 | Is character on ground (0=no, 1=yes) |
| height | f32 | 1.8 | Character capsule height |
| radius | f32 | 0.3 | Character capsule radius |
| stepHeight | f32 | 0.3 | Maximum stair step height |
| maxSlope | f32 | 45.0 | Maximum walkable slope in degrees |
| snapToGround | f32 | 0.5 | Distance to snap down to ground |
| movement | [f32, 3] | [0, 0, 0] | Desired movement vector for current frame |
Usage#
import { addComponent, CharacterController } from '@web-engine/core'; // Create character controllerconst player = world.addEntity();addComponent(world, Transform, player);addComponent(world, CharacterController, player); // Configure movementCharacterController.speed[player] = 6.0; // 6 m/sCharacterController.jumpHeight[player] = 2.5;CharacterController.height[player] = 1.8; // 1.8m tallCharacterController.radius[player] = 0.4;CharacterController.stepHeight[player] = 0.4; // Can climb 40cm stairsCharacterController.maxSlope[player] = 50.0; // 50 degree slopes // Apply movement (in update loop)CharacterController.movement[player][0] = inputX; // horizontalCharacterController.movement[player][2] = inputZ; // forward/back // Jumpif (CharacterController.grounded[player] && jumpPressed) { CharacterController.verticalVelocity[player] = Math.sqrt(2 * 9.81 * CharacterController.jumpHeight[player]);}Character Movement
CharacterController handles collision, ground detection, and slope traversal automatically. Just set the movement vector and vertical velocity, the system handles the rest.
Joint#
The Joint component connects two physics bodies with constraints. Supports fixed, spherical (ball), revolute (hinge), and prismatic (slider) joints.
Properties#
| Property | Type | Default | Description |
|---|---|---|---|
| type | ui8 | 0 | 0=Fixed, 1=Spherical, 2=Revolute, 3=Prismatic |
| connectedEntity | ui32 | 0 | Entity ID of connected body (0=world) |
| anchor1 | [f32, 3] | [0, 0, 0] | Anchor point on this entity (local) |
| anchor2 | [f32, 3] | [0, 0, 0] | Anchor point on connected entity (local) |
| axis1 | [f32, 3] | [0, 1, 0] | Axis on this entity (Revolute/Prismatic) |
| axis2 | [f32, 3] | [0, 1, 0] | Axis on connected entity (Revolute/Prismatic) |
| min | f32 | -Infinity | Minimum limit (angle in rad, distance in m) |
| max | f32 | Infinity | Maximum limit (angle in rad, distance in m) |
| stiffness | f32 | 0.0 | Motor/spring stiffness |
| damping | f32 | 0.0 | Motor/spring damping |
Joint Types#
- Fixed (0): Rigidly attaches two bodies together (no relative motion)
- Spherical (1): Ball joint allowing rotation around all axes
- Revolute (2): Hinge joint allowing rotation around one axis
- Prismatic (3): Slider joint allowing linear movement along one axis
Usage#
import { addComponent, Joint } from '@web-engine/core'; // Create hinge (door)const door = world.addEntity();const doorFrame = world.addEntity(); addComponent(world, RigidBody, door);addComponent(world, Collider, door);addComponent(world, Joint, door); // Revolute joint at edge of doorJoint.type[door] = 2; // Revolute/HingeJoint.connectedEntity[door] = doorFrame;Joint.anchor1[door][0] = -0.5; // Left edge of doorJoint.axis1[door][1] = 1; // Rotate around Y axisJoint.min[door] = 0; // 0 degreesJoint.max[door] = Math.PI / 2; // 90 degrees // Create ragdoll arm joint (spherical)const upperArm = world.addEntity();const shoulder = world.addEntity(); addComponent(world, RigidBody, upperArm);addComponent(world, Joint, upperArm); Joint.type[upperArm] = 1; // SphericalJoint.connectedEntity[upperArm] = shoulder;Joint.anchor1[upperArm][1] = 0.3; // Top of upper armJoint.anchor2[upperArm][1] = -0.1; // Bottom of shoulder // Create slider (piston)const piston = world.addEntity();const cylinder = world.addEntity(); addComponent(world, RigidBody, piston);addComponent(world, Joint, piston); Joint.type[piston] = 3; // PrismaticJoint.connectedEntity[piston] = cylinder;Joint.axis1[piston][2] = 1; // Slide along Z axisJoint.min[piston] = 0;Joint.max[piston] = 2; // 2 meters travelTriggerVolume#
The TriggerVolume component detects when entities enter or exit a volume. Useful for level triggers, damage zones, and pickups.
Properties#
| Property | Type | Default | Description |
|---|---|---|---|
| shape | ui8 | 0 | 0=Box, 1=Sphere |
| size | [f32, 3] | [1, 1, 1] | Box: [halfX, halfY, halfZ], Sphere: [radius, 0, 0] |
| offset | [f32, 3] | [0, 0, 0] | Offset from entity position |
| collisionGroup | ui16 | 0xFFFF | Which entities trigger this volume |
| isActive | ui8 | 1 | Is trigger active (0=no, 1=yes) |
Usage#
import { addComponent, TriggerVolume, TriggerStateMap } from '@web-engine/core'; // Create trigger zoneconst trigger = world.addEntity();addComponent(world, Transform, trigger);addComponent(world, TriggerVolume, trigger); TriggerVolume.shape[trigger] = 0; // BoxTriggerVolume.size[trigger] = [5, 2, 5]; // 10x4x10 meter boxTriggerVolume.isActive[trigger] = 1; // Check which entities are inside triggerconst entitiesInside = TriggerStateMap.get(trigger) ?? new Set();for (const eid of entitiesInside) { console.log(`Entity ${eid} is in trigger`);}Best Practices#
- Use Fixed bodies for static geometry (walls, floors, terrain)
- Use CharacterController for player characters instead of dynamic RigidBody
- Enable CCD only for fast projectiles to avoid performance cost
- Use collision filtering to avoid unnecessary collision checks
- Keep collider shapes simple (box/sphere/capsule) for best performance
- Use TriggerVolume with isSensor for non-physical triggers
- Limit joint count for complex ragdolls (8-12 joints max)
- Set appropriate mass values (1kg for small objects, 50kg for characters, 1000kg for vehicles)