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