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#

PropertyTypeDefaultDescription
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
massf321.0Mass in kilograms (0=infinite for static)
frictionf320.5Friction coefficient (0=frictionless, 1=high friction)
restitutionf320.0Bounciness (0=no bounce, 1=perfect bounce)
typeui800=Dynamic, 1=Fixed, 2=KinematicPosition, 3=KinematicVelocity
ccdui80Continuous 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; // Dynamic
RigidBody.mass[ball] = 1.0; // 1 kg
RigidBody.friction[ball] = 0.3;
RigidBody.restitution[ball] = 0.8; // Bouncy
Collider.type[ball] = 1; // Sphere
Collider.size[ball][0] = 0.5; // radius
// Create static floor
const floor = world.addEntity();
addComponent(world, Transform, floor);
addComponent(world, RigidBody, floor);
addComponent(world, Collider, floor);
RigidBody.type[floor] = 1; // Fixed/Static
Collider.type[floor] = 0; // Box
Collider.size[floor][0] = 50; // half-width
Collider.size[floor][1] = 0.5; // half-height
Collider.size[floor][2] = 50; // half-depth

CCD 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#

PropertyTypeDefaultDescription
typeui800=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]
isSensorui80Sensor/trigger (0=solid, 1=trigger)
collisionGroupui160xFFFFCollision group membership bitmask
collisionMaskui160xFFFFCollision filter mask (which groups to collide with)
solverGroupui160xFFFFSolver group membership
solverMaskui160xFFFFSolver filter mask

Collider Shapes#

ShapeTypesize[0]size[1]size[2]Use Case
Box0half-widthhalf-heighthalf-depthCrates, walls, floors
Sphere1radiusBalls, explosions
Capsule2radiusheightCharacters, pills
Heightfield3Terrain (use ColliderDataMap)
Trimesh4Complex meshes (use ColliderDataMap)
ConvexHull5Simplified convex shapes

Usage#

import { addComponent, Collider } from '@web-engine/core';
// Box collider
const box = world.addEntity();
addComponent(world, Collider, box);
Collider.type[box] = 0; // Box
Collider.size[box][0] = 1.0; // half-width (total width = 2.0)
Collider.size[box][1] = 1.0; // half-height
Collider.size[box][2] = 1.0; // half-depth
// Sphere collider
const sphere = world.addEntity();
addComponent(world, Collider, sphere);
Collider.type[sphere] = 1; // Sphere
Collider.size[sphere][0] = 0.5; // radius
// Capsule collider (character)
const character = world.addEntity();
addComponent(world, Collider, character);
Collider.type[character] = 2; // Capsule
Collider.size[character][0] = 0.3; // radius
Collider.size[character][1] = 1.8; // height
Collider.offset[character][1] = 0.9; // Center at half height
// Sensor (trigger) collider
const trigger = world.addEntity();
addComponent(world, Collider, trigger);
Collider.type[trigger] = 0; // Box
Collider.size[trigger] = [2, 2, 2];
Collider.isSensor[trigger] = 1; // Trigger, no collision

Collision 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 0
const GROUP_ENEMY = 1 << 1; // Bit 1
const GROUP_PROJECTILE = 1 << 2; // Bit 2
const GROUP_GROUND = 1 << 3; // Bit 3
// Player collides with enemies, projectiles, and ground
Collider.collisionGroup[player] = GROUP_PLAYER;
Collider.collisionMask[player] = GROUP_ENEMY | GROUP_PROJECTILE | GROUP_GROUND;
// Enemy collides with player, projectiles, and ground
Collider.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 everything
Collider.collisionGroup[ground] = GROUP_GROUND;
Collider.collisionMask[ground] = 0xFFFF; // All groups

Velocity#

The Velocity component stores linear and angular velocity for kinematic movement or as input/output for physics simulation.

Properties#

PropertyTypeDefaultDescription
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#

PropertyTypeDefaultDescription
speedf325.0Movement speed in m/s
jumpHeightf322.0Jump height in meters
verticalVelocityf320.0Current vertical velocity (gravity)
groundedui80Is character on ground (0=no, 1=yes)
heightf321.8Character capsule height
radiusf320.3Character capsule radius
stepHeightf320.3Maximum stair step height
maxSlopef3245.0Maximum walkable slope in degrees
snapToGroundf320.5Distance 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 controller
const player = world.addEntity();
addComponent(world, Transform, player);
addComponent(world, CharacterController, player);
// Configure movement
CharacterController.speed[player] = 6.0; // 6 m/s
CharacterController.jumpHeight[player] = 2.5;
CharacterController.height[player] = 1.8; // 1.8m tall
CharacterController.radius[player] = 0.4;
CharacterController.stepHeight[player] = 0.4; // Can climb 40cm stairs
CharacterController.maxSlope[player] = 50.0; // 50 degree slopes
// Apply movement (in update loop)
CharacterController.movement[player][0] = inputX; // horizontal
CharacterController.movement[player][2] = inputZ; // forward/back
// Jump
if (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#

PropertyTypeDefaultDescription
typeui800=Fixed, 1=Spherical, 2=Revolute, 3=Prismatic
connectedEntityui320Entity 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)
minf32-InfinityMinimum limit (angle in rad, distance in m)
maxf32InfinityMaximum limit (angle in rad, distance in m)
stiffnessf320.0Motor/spring stiffness
dampingf320.0Motor/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 door
Joint.type[door] = 2; // Revolute/Hinge
Joint.connectedEntity[door] = doorFrame;
Joint.anchor1[door][0] = -0.5; // Left edge of door
Joint.axis1[door][1] = 1; // Rotate around Y axis
Joint.min[door] = 0; // 0 degrees
Joint.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; // Spherical
Joint.connectedEntity[upperArm] = shoulder;
Joint.anchor1[upperArm][1] = 0.3; // Top of upper arm
Joint.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; // Prismatic
Joint.connectedEntity[piston] = cylinder;
Joint.axis1[piston][2] = 1; // Slide along Z axis
Joint.min[piston] = 0;
Joint.max[piston] = 2; // 2 meters travel

TriggerVolume#

The TriggerVolume component detects when entities enter or exit a volume. Useful for level triggers, damage zones, and pickups.

Properties#

PropertyTypeDefaultDescription
shapeui800=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
collisionGroupui160xFFFFWhich entities trigger this volume
isActiveui81Is trigger active (0=no, 1=yes)

Usage#

import { addComponent, TriggerVolume, TriggerStateMap } from '@web-engine/core';
// Create trigger zone
const trigger = world.addEntity();
addComponent(world, Transform, trigger);
addComponent(world, TriggerVolume, trigger);
TriggerVolume.shape[trigger] = 0; // Box
TriggerVolume.size[trigger] = [5, 2, 5]; // 10x4x10 meter box
TriggerVolume.isActive[trigger] = 1;
// Check which entities are inside trigger
const 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)
Components | Web Engine Docs | Web Engine Docs