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
77%
Does it follow best practices?
Impact
99%
1.02xAverage score across 3 eval scenarios
Passed
No known issues
Optimize this skill with Tessl
npx tessl skill review --optimize ./packages/starter-assets/claude-injections/skills/iwsdk-physics/SKILL.mdThis 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.
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.
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:
| 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.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:
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:
| 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.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 typeThe 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:
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:
| 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:
update() {
if (!entity.hasComponent(PhysicsManipulation)) {
entity.addComponent(PhysicsManipulation, { force: [0, 5, 0] });
}
}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] });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 });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;
}
}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.
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
}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(),
});
}
}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 });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 |
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 });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 gravityPhysics 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 = STATIC1 = DYNAMIC2 = KINEMATICShape enum values in GLXF:
0 = Sphere1 = Box2 = Cylinder3 = Capsules4 = ConvexHull5 = TriMesh6 = AutoObjects fall through the floor:
PhysicsShape and PhysicsBody with state: PhysicsState.StaticAuto or ConvexHull is selected for the PhysicsShape of static objects, try to change into TriMeshphysics: true is set in World.create featuresObjects don't move:
state is PhysicsState.Dynamic (not Static or Kinematic)gravityFactor is > 0PhysicsShape and PhysicsBody are added (both are required)Objects are too bouncy or slide too much:
restitution to reduce bouncing (0 = no bounce)friction to reduce sliding (0.8+ for grippy surfaces)Objects move too slowly or feel sluggish:
linearDamping (0 = no air resistance)density is not too high (high density = heavy = resists force)Poor frame rate with many physics objects:
TriMesh only for static objectsAuto to avoid detection overheadGrabbed object doesn't follow hand:
grabbing: true in featuresInteractable and a grabbable component (OneHandGrabbable, TwoHandsGrabbable, or DistanceGrabbable)PhysicsManipulation has no effect:
PhysicsBody with an active engine body (_engineBody != 0)PhysicsState.Static for all non-moving objects; static bodies have zero simulation costAuto detection overheadimport {
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] });
});b3d1162
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.