CtrlK
BlogDocsLog inGet started
Tessl Logo

iwsdk-physics

Guide for implementing physics in IWSDK projects. Use when adding physics simulation, configuring rigid bodies, collision shapes, applying forces, creating grabbable physics objects, or troubleshooting physics behavior.

84

1.02x
Quality

77%

Does it follow best practices?

Impact

99%

1.02x

Average score across 3 eval scenarios

SecuritybySnyk

Passed

No known issues

Optimize this skill with Tessl

npx tessl skill review --optimize ./packages/starter-assets/claude-injections/skills/iwsdk-physics/SKILL.md
SKILL.md
Quality
Evals
Security

IWSDK Physics System Guide

This skill provides the complete reference and workflow for implementing Havok-powered physics simulation in IWSDK applications. Physics is built on three ECS components (PhysicsBody, PhysicsShape, PhysicsManipulation) orchestrated by the PhysicsSystem.

Enabling Physics

Enable physics in World.create with the physics feature flag:

import { World, SessionMode } from '@iwsdk/core';

const world = await World.create(container, {
  xr: { sessionMode: SessionMode.ImmersiveVR },
  features: {
    physics: true,
    grabbing: true, // Required if physics objects should be grabbable
    locomotion: true, // Requires collision geometry in the scene
  },
  level: './glxf/Composition.glxf',
});

Setting physics: true automatically registers PhysicsBody, PhysicsShape, PhysicsManipulation components and the PhysicsSystem at priority -2.

Only enable physics when needed. If no objects require dynamic simulation, omit it to avoid overhead.

PhysicsBody Component Reference

Defines the motion behavior of a physics entity. Import from @iwsdk/core.

import { PhysicsBody, PhysicsState } from '@iwsdk/core';

entity.addComponent(PhysicsBody, {
  state: PhysicsState.Dynamic,
  linearDamping: 0.0,
  angularDamping: 0.0,
  gravityFactor: 1.0,
  centerOfMass: [Infinity, Infinity, Infinity], // Infinity = auto-compute from shape
});

Properties:

PropertyTypeDefaultDescription
statePhysicsStateDynamicMotion type (see below)
linearDampingFloat320.0Air resistance for translation (0 = none, 1 = heavy)
angularDampingFloat320.0Air resistance for rotation
gravityFactorFloat321.0Gravity multiplier (0 = floating, 2 = double gravity)
centerOfMassVec3[Infinity, Infinity, Infinity]Override center of mass; Infinity = auto-compute

Read-only properties (updated each frame by PhysicsSystem):

PropertyTypeDescription
_linearVelocityVec3Current linear velocity
_angularVelocityVec3Current angular velocity

PhysicsState Enum

PhysicsState.Static; // Immovable (walls, floors). Zero simulation cost.
PhysicsState.Dynamic; // Fully simulated. Responds to forces, gravity, collisions.
PhysicsState.Kinematic; // Programmatically moved. Pushes dynamic bodies but is not affected by them.

When to use each:

  • Static -- Environment geometry (walls, floors, tables). Objects that never move but block dynamic bodies.
  • Dynamic -- Objects that respond to physics (balls, crates, interactive props). Default for most gameplay objects.
  • Kinematic -- Moving platforms that won't be pushed by other physics bodies.

PhysicsShape Component Reference

Defines the collision geometry and material properties. Both PhysicsShape and PhysicsBody are required for physics simulation.

import { PhysicsShape, PhysicsShapeType } from '@iwsdk/core';

entity.addComponent(PhysicsShape, {
  shape: PhysicsShapeType.Auto,
  dimensions: [0, 0, 0],
  density: 1.0,
  restitution: 0.0,
  friction: 0.5,
});

Properties:

PropertyTypeDefaultDescription
shapePhysicsShapeTypeAutoCollision shape type
dimensionsVec3[0, 0, 0]Shape-specific dimensions array. Not applicable when PhysicsShapeType.Auto is used.
densityFloat321.0Mass density (kg/m^3). Higher = heavier.
restitutionFloat320.0Bounciness (0 = no bounce, 1 = perfect bounce)
frictionFloat320.5Surface friction (0 = ice, 1 = rubber)

PhysicsShapeType Enum

PhysicsShapeType.Sphere; // dimensions[0] = radius
PhysicsShapeType.Box; // dimensions = [width, height, depth]
PhysicsShapeType.Cylinder; // dimensions[0] = radius, dimensions[1] = height
PhysicsShapeType.ConvexHull; // Convex wrapper around mesh vertices (dimensions ignored)
PhysicsShapeType.TriMesh; // Exact mesh triangles (dimensions ignored). Expensive; use for static only.
PhysicsShapeType.Auto; // Auto-detect from Three.js geometry type

Dimensions by Shape Type

The dimensions property is a Vec3 ([x, y, z]) whose meaning changes depending on the selected shape:

Shape Typedimensions[0]dimensions[1]dimensions[2]Example
Sphereradius(unused)(unused)[0.5, 0, 0] -- sphere r=0.5
Boxwidthheightdepth[1, 2, 0.5] -- 1×2×0.5 box
Cylinderradiusheight(unused)[0.3, 1.5, 0] -- r=0.3, h=1.5
ConvexHull(ignored)(ignored)(ignored)Computed from mesh vertices
TriMesh(ignored)(ignored)(ignored)Computed from mesh triangles
Auto(ignored)(ignored)(ignored)Auto-detected from geometry

For ConvexHull, TriMesh, and Auto, the dimensions array is not used -- the shape is derived directly from the entity's Three.js geometry.

Auto-detection mapping:

Three.js GeometryDetected ShapeDimensions Source
SphereGeometrySphereradius from geometry parameters
BoxGeometryBoxwidth, height, depth from parameters
PlaneGeometryBoxwidth, height, 0.01 (thin box)
CylinderGeometryCylinderAverage of radiusTop/radiusBottom, height
BufferGeometry (generic/GLTF)ConvexHullFrom mesh vertices
UnknownBox (fallback)From bounding box

Performance guidance:

  • Sphere/Box/Cylinder: Fastest collision detection. Prefer these when possible.
  • ConvexHull: Good balance for complex meshes. Default for GLTF models via Auto.
  • TriMesh: Exact geometry collision. Use only for static objects (walls, floors, terrain).

PhysicsManipulation Component Reference

A one-shot component for applying forces and velocities. Automatically removed after one frame.

import { PhysicsManipulation } from '@iwsdk/core';

// Apply an impulse (removed automatically after 1 frame)
entity.addComponent(PhysicsManipulation, {
  force: [0, 10, 0], // Impulse force vector
  linearVelocity: [0, 0, 0], // Override linear velocity (0 = no change)
  angularVelocity: [0, 0, 0], // Override angular velocity (0 = no change)
});

Properties:

PropertyTypeDefaultDescription
forceVec3[0, 0, 0]Impulse force applied at center of mass
linearVelocityVec3[0, 0, 0]Sets absolute linear velocity
angularVelocityVec3[0, 0, 0]Sets absolute angular velocity

The component is auto-removed by PhysicsSystem after applying values. For sustained forces, re-add each frame:

update() {
  if (!entity.hasComponent(PhysicsManipulation)) {
    entity.addComponent(PhysicsManipulation, { force: [0, 5, 0] });
  }
}

Common Workflows

Creating a Dynamic Physics Object

import {
  Mesh,
  SphereGeometry,
  MeshStandardMaterial,
  Color,
  FrontSide,
} from 'three';
import {
  PhysicsShape,
  PhysicsShapeType,
  PhysicsBody,
  PhysicsState,
  PhysicsManipulation,
} from '@iwsdk/core';

// 1. Create Three.js mesh
const ball = new Mesh(
  new SphereGeometry(0.2),
  new MeshStandardMaterial({ color: new Color(0xff4444), side: FrontSide }),
);
ball.position.set(0, 2, -1);
scene.add(ball);

// 2. Wrap as ECS entity
const entity = world.createTransformEntity(ball);

// 3. Add physics components
entity.addComponent(PhysicsShape, {
  shape: PhysicsShapeType.Sphere,
  dimensions: [0.2],
  restitution: 0.6, // Bouncy
});
entity.addComponent(PhysicsBody, { state: PhysicsState.Dynamic });

// 4. Optional: apply initial impulse
entity.addComponent(PhysicsManipulation, { force: [5, 2, 0] });

Creating a Static Environment Collider

For walls, floors, and fixed scenery that block dynamic objects but never move:

// Ground plane
const ground = new Mesh(
  new BoxGeometry(10, 0.1, 10),
  new MeshStandardMaterial({ color: 0x888888 }),
);
ground.position.set(0, -0.05, 0);
scene.add(ground);

const groundEntity = world.createTransformEntity(ground);
groundEntity.addComponent(PhysicsShape, {
  shape: PhysicsShapeType.Box,
  dimensions: [10, 0.1, 10],
  friction: 0.8,
});
groundEntity.addComponent(PhysicsBody, { state: PhysicsState.Static });

For complex static geometry (GLTF environments), use TriMesh for exact collision:

envEntity.addComponent(PhysicsShape, { shape: PhysicsShapeType.TriMesh });
envEntity.addComponent(PhysicsBody, { state: PhysicsState.Static });

Creating a Kinematic Moving Platform

Kinematic bodies are moved by code and push dynamic objects:

// Setup
const platform = new Mesh(
  new BoxGeometry(3, 0.2, 3),
  new MeshStandardMaterial({ color: 0x4488ff }),
);
scene.add(platform);

const platformEntity = world.createTransformEntity(platform);
platformEntity.addComponent(PhysicsShape, {
  shape: PhysicsShapeType.Box,
  dimensions: [3, 0.2, 3],
});
platformEntity.addComponent(PhysicsBody, { state: PhysicsState.Kinematic });

// In a system's update loop, move it:
update(delta, time) {
  for (const entity of this.queries.platforms.entities) {
    entity.object3D.position.y = 1 + Math.sin(time) * 2;
  }
}

Making an Object Grabbable with Physics

Combine grab components with physics for throwable objects:

import { Interactable, OneHandGrabbable, DistanceGrabbable } from '@iwsdk/core';

// Physics components
entity.addComponent(PhysicsShape, { shape: PhysicsShapeType.Auto });
entity.addComponent(PhysicsBody, { state: PhysicsState.Dynamic });

// Grab components
entity.addComponent(Interactable);
entity.addComponent(OneHandGrabbable);

// Optional: allow grabbing from a distance
entity.addComponent(DistanceGrabbable, {
  rotate: true,
  translate: true,
});

When grabbed, the PhysicsSystem automatically detects the Pressed component and overrides physics with HP_Body_SetTargetQTransform, making the object follow the hand. On release, the object resumes dynamic simulation with natural velocity for realistic throwing.

Reading Velocity for Game Logic

const velocity = entity.getVectorView(PhysicsBody, '_linearVelocity');
const speed = Math.sqrt(velocity[0] ** 2 + velocity[1] ** 2 + velocity[2] ** 2);

if (speed > 5.0) {
  // High-speed impact logic
}

Explosion Pattern (Radial Force)

Apply outward force to all nearby physics objects:

const explosionPos = bomb.object3D.position;
const radius = 5.0;
const force = 50.0;

for (const target of this.queries.physicsObjects.entities) {
  const dist = target.object3D.position.distanceTo(explosionPos);
  if (dist < radius && dist > 0) {
    const direction = target.object3D.position
      .clone()
      .sub(explosionPos)
      .normalize();
    const strength = force * (1 - dist / radius);
    target.addComponent(PhysicsManipulation, {
      force: direction.multiplyScalar(strength).toArray(),
    });
  }
}

Custom Physics System Pattern

Create domain-specific components that interact with the physics system:

import {
  createComponent,
  createSystem,
  Types,
  PhysicsBody,
  PhysicsManipulation,
} from '@iwsdk/core';

// 1. Define custom component
export const Buoyancy = createComponent('Buoyancy', {
  waterLevel: { type: Types.Float32, default: 0.0 },
  buoyancyForce: { type: Types.Float32, default: 15.0 },
});

// 2. Create system that applies physics forces
export class BuoyancySystem extends createSystem({
  floaters: { required: [Buoyancy, PhysicsBody] },
}) {
  update(delta) {
    for (const entity of this.queries.floaters.entities) {
      const waterLevel = entity.getValue(Buoyancy, 'waterLevel');
      const force = entity.getValue(Buoyancy, 'buoyancyForce');
      const y = entity.object3D.position.y;

      if (y < waterLevel) {
        const submersion = Math.min(1, (waterLevel - y) / 0.5);
        if (!entity.hasComponent(PhysicsManipulation)) {
          entity.addComponent(PhysicsManipulation, {
            force: [0, force * submersion * delta, 0],
          });
        }
      }
    }
  }
}

// 3. Register with world
world.registerComponent(Buoyancy);
world.registerSystem(BuoyancySystem, { priority: 5 });

Material Tuning Guide

Adjust density, restitution, and friction on PhysicsShape to simulate different materials:

MaterialDensityRestitutionFriction
Wood0.60.30.5
Metal/Steel7.80.20.4
Rubber1.10.80.9
Ice0.90.10.05
Concrete2.40.10.7
Foam/Light0.050.10.6
Bouncy ball1.00.950.5

System Priority Order

Physics runs in a carefully orchestrated sequence:

Priority -5: LocomotionSystem  (Player movement)
Priority -4: InputSystem       (Controller/hand input)
Priority -3: GrabSystem        (Grab interactions)
Priority -2: PhysicsSystem     (Physics simulation)
Priority -1: SceneUnderstanding (AR plane/mesh updates)

Register custom physics-related systems after the built-in PhysicsSystem (priority > -2) to read updated transforms:

world.registerSystem(MyPhysicsLogicSystem, { priority: 5 });

PhysicsSystem Configuration

The system accepts a gravity config (defaults to Earth gravity):

import { PhysicsSystem } from '@iwsdk/core';

const physicsSystem = world.getSystem(PhysicsSystem);
physicsSystem.config.gravity.value = [0, -9.81, 0]; // Earth gravity (default)
physicsSystem.config.gravity.value = [0, -1.62, 0]; // Moon gravity
physicsSystem.config.gravity.value = [0, 0, 0]; // Zero gravity

GLXF / Editor Configuration

Physics components can be configured declaratively in GLXF scene files (exported by Meta Spatial Editor):

{
  "com.iwsdk.components.PhysicsShape": {
    "shape": { "alias": "Auto", "value": 6 },
    "dimensions": { "value": [0, 0, 0] },
    "density": { "value": 1.0 },
    "friction": { "value": 0.5 },
    "restitution": { "value": 0.0 }
  },
  "com.iwsdk.components.PhysicsBody": {
    "state": { "alias": "DYNAMIC", "value": 1 },
    "gravityFactor": { "value": 1.0 },
    "linearDamping": { "value": 0.0 },
    "angularDamping": { "value": 0.0 }
  }
}

State enum values in GLXF:

  • 0 = STATIC
  • 1 = DYNAMIC
  • 2 = KINEMATIC

Shape enum values in GLXF:

  • 0 = Sphere
  • 1 = Box
  • 2 = Cylinder
  • 3 = Capsules
  • 4 = ConvexHull
  • 5 = TriMesh
  • 6 = Auto

Troubleshooting

Objects fall through the floor:

  • Ensure the floor entity has both PhysicsShape and PhysicsBody with state: PhysicsState.Static
  • Verify the shape type and dimensions match the visual geometry
  • If the Auto or ConvexHull is selected for the PhysicsShape of static objects, try to change into TriMesh
  • Check that physics: true is set in World.create features

Objects don't move:

  • Confirm state is PhysicsState.Dynamic (not Static or Kinematic)
  • Check gravityFactor is > 0
  • Verify both PhysicsShape and PhysicsBody are added (both are required)

Objects are too bouncy or slide too much:

  • Lower restitution to reduce bouncing (0 = no bounce)
  • Increase friction to reduce sliding (0.8+ for grippy surfaces)

Objects move too slowly or feel sluggish:

  • Reduce linearDamping (0 = no air resistance)
  • Check density is not too high (high density = heavy = resists force)

Poor frame rate with many physics objects:

  • Use simpler shape types (Sphere/Box instead of ConvexHull/TriMesh)
  • Use TriMesh only for static objects
  • Explicitly set shape types instead of Auto to avoid detection overhead
  • Reduce the number of dynamic bodies; make non-essential objects static

Grabbed object doesn't follow hand:

  • Ensure grabbing: true in features
  • Verify the entity has Interactable and a grabbable component (OneHandGrabbable, TwoHandsGrabbable, or DistanceGrabbable)

PhysicsManipulation has no effect:

  • The entity must have a PhysicsBody with an active engine body (_engineBody != 0)
  • The component is auto-removed after one frame; re-add it for sustained effects
  • Force values may need to be larger; they are scaled by frame delta time

Performance Tips

  1. Use primitive shapes (Sphere, Box, Cylinder) over ConvexHull/TriMesh whenever acceptable
  2. Use PhysicsState.Static for all non-moving objects; static bodies have zero simulation cost
  3. Explicitly set shape types in production; avoid Auto detection overhead
  4. Minimize dynamic body count -- each dynamic body requires per-frame transform sync
  5. Use damping to settle objects faster and reduce ongoing simulation work
  6. TriMesh is for static only -- it is computationally expensive and should never be used on dynamic bodies

Complete Example: Physics Playground

import {
  World,
  SessionMode,
  PhysicsShape,
  PhysicsShapeType,
  PhysicsBody,
  PhysicsState,
  PhysicsManipulation,
  Interactable,
  OneHandGrabbable,
} from '@iwsdk/core';
import {
  Mesh,
  BoxGeometry,
  SphereGeometry,
  MeshStandardMaterial,
  Color,
  FrontSide,
} from 'three';

World.create(document.getElementById('scene-container'), {
  xr: { sessionMode: SessionMode.ImmersiveVR },
  features: { physics: true, grabbing: true },
}).then((world) => {
  const { scene } = world;

  // Static floor
  const floor = new Mesh(
    new BoxGeometry(10, 0.1, 10),
    new MeshStandardMaterial({ color: 0x555555 }),
  );
  floor.position.set(0, -0.05, 0);
  scene.add(floor);
  const floorEntity = world.createTransformEntity(floor);
  floorEntity.addComponent(PhysicsShape, {
    shape: PhysicsShapeType.Box,
    dimensions: [10, 0.1, 10],
    friction: 0.8,
  });
  floorEntity.addComponent(PhysicsBody, { state: PhysicsState.Static });

  // Dynamic bouncy ball (grabbable)
  const ball = new Mesh(
    new SphereGeometry(0.15),
    new MeshStandardMaterial({ color: new Color(0xff4444), side: FrontSide }),
  );
  ball.position.set(0, 1.5, -1);
  scene.add(ball);
  const ballEntity = world.createTransformEntity(ball);
  ballEntity.addComponent(PhysicsShape, {
    shape: PhysicsShapeType.Sphere,
    dimensions: [0.15],
    restitution: 0.8,
    friction: 0.5,
  });
  ballEntity.addComponent(PhysicsBody, { state: PhysicsState.Dynamic });
  ballEntity.addComponent(Interactable);
  ballEntity.addComponent(OneHandGrabbable);

  // Dynamic box with initial impulse
  const box = new Mesh(
    new BoxGeometry(0.3, 0.3, 0.3),
    new MeshStandardMaterial({ color: new Color(0x4488ff), side: FrontSide }),
  );
  box.position.set(0.5, 2, -1);
  scene.add(box);
  const boxEntity = world.createTransformEntity(box);
  boxEntity.addComponent(PhysicsShape, {
    shape: PhysicsShapeType.Box,
    dimensions: [0.3, 0.3, 0.3],
    restitution: 0.3,
  });
  boxEntity.addComponent(PhysicsBody, {
    state: PhysicsState.Dynamic,
    linearDamping: 0.1,
  });
  boxEntity.addComponent(PhysicsManipulation, { force: [-3, 5, 0] });
});
Repository
facebook/immersive-web-sdk
Last updated
Created

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.