APM

>Agent Skill

@facebook/iwsdk-physics

skilldesign

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.

typescriptdocumentation
apm::install
$apm install @facebook/iwsdk-physics
apm::skill.md
---
name: iwsdk-physics
description: 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.
---

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

```typescript
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`.

```typescript
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:**

| Property         | Type           | Default                          | Description                                           |
| ---------------- | -------------- | -------------------------------- | ----------------------------------------------------- |
| `state`          | `PhysicsState` | `Dynamic`                        | Motion type (see below)                               |
| `linearDamping`  | `Float32`      | `0.0`                            | Air resistance for translation (0 = none, 1 = heavy)  |
| `angularDamping` | `Float32`      | `0.0`                            | Air resistance for rotation                           |
| `gravityFactor`  | `Float32`      | `1.0`                            | Gravity multiplier (0 = floating, 2 = double gravity) |
| `centerOfMass`   | `Vec3`         | `[Infinity, Infinity, Infinity]` | Override center of mass; `Infinity` = auto-compute    |

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

| Property           | Type   | Description              |
| ------------------ | ------ | ------------------------ |
| `_linearVelocity`  | `Vec3` | Current linear velocity  |
| `_angularVelocity` | `Vec3` | Current angular velocity |

### PhysicsState Enum

```typescript
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.

```typescript
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:**

| Property      | Type               | Default     | Description                                                                           |
| ------------- | ------------------ | ----------- | ------------------------------------------------------------------------------------- |
| `shape`       | `PhysicsShapeType` | `Auto`      | Collision shape type                                                                  |
| `dimensions`  | `Vec3`             | `[0, 0, 0]` | Shape-specific dimensions array. Not applicable when `PhysicsShapeType.Auto` is used. |
| `density`     | `Float32`          | `1.0`       | Mass density (kg/m^3). Higher = heavier.                                              |
| `restitution` | `Float32`          | `0.0`       | Bounciness (0 = no bounce, 1 = perfect bounce)                                        |
| `friction`    | `Float32`          | `0.5`       | Surface friction (0 = ice, 1 = rubber)                                                |

### PhysicsShapeType Enum

```typescript
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 Type   | `dimensions[0]` | `dimensions[1]` | `dimensions[2]` | Example                         |
| ------------ | --------------- | --------------- | --------------- | ------------------------------- |
| `Sphere`     | radius          | _(unused)_      | _(unused)_      | `[0.5, 0, 0]` -- sphere r=0.5   |
| `Box`        | width           | height          | depth           | `[1, 2, 0.5]` -- 1×2×0.5 box    |
| `Cylinder`   | radius          | height          | _(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 Geometry               | Detected Shape | Dimensions Source                               |
| ------------------------------- | -------------- | ----------------------------------------------- |
| `SphereGeometry`                | Sphere         | `radius` from geometry parameters               |
| `BoxGeometry`                   | Box            | `width, height, depth` from parameters          |
| `PlaneGeometry`                 | Box            | `width, height, 0.01` (thin box)                |
| `CylinderGeometry`              | Cylinder       | Average of `radiusTop`/`radiusBottom`, `height` |
| `BufferGeometry` (generic/GLTF) | ConvexHull     | From mesh vertices                              |
| Unknown                         | Box (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.

```typescript
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:**

| Property          | Type   | Default     | Description                             |
| ----------------- | ------ | ----------- | --------------------------------------- |
| `force`           | `Vec3` | `[0, 0, 0]` | Impulse force applied at center of mass |
| `linearVelocity`  | `Vec3` | `[0, 0, 0]` | Sets absolute linear velocity           |
| `angularVelocity` | `Vec3` | `[0, 0, 0]` | Sets absolute angular velocity          |

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

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

## Common Workflows

### Creating a Dynamic Physics Object

```typescript
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:

```typescript
// 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:

```typescript
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:

```typescript
// 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:

```typescript
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

```typescript
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:

```typescript
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:

```typescript
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:

| Material    | Density | Restitution | Friction |
| ----------- | ------- | ----------- | -------- |
| Wood        | 0.6     | 0.3         | 0.5      |
| Metal/Steel | 7.8     | 0.2         | 0.4      |
| Rubber      | 1.1     | 0.8         | 0.9      |
| Ice         | 0.9     | 0.1         | 0.05     |
| Concrete    | 2.4     | 0.1         | 0.7      |
| Foam/Light  | 0.05    | 0.1         | 0.6      |
| Bouncy ball | 1.0     | 0.95        | 0.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:

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

## PhysicsSystem Configuration

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

```typescript
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):

```json
{
  "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

```typescript
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] });
});
```