Skip to content
🎉 Angular Three v4 is here! Read the announcement

Introduction

angular-three-rapier provides Rapier physics integration for Angular Three. Rapier is a fast, cross-platform physics engine written in Rust with JavaScript bindings.

No additional peer dependencies required beyond the core dependencies.

Terminal window
npm install angular-three-rapier @dimforge/rapier3d-compat
# yarn add angular-three-rapier @dimforge/rapier3d-compat
# pnpm add angular-three-rapier @dimforge/rapier3d-compat

Make sure you already have angular-three installed

Wrap your physics-enabled 3D scene with NgtrPhysics:

import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { extend, NgtArgs } from 'angular-three';
import { NgtrPhysics, NgtrRigidBody } from 'angular-three-rapier';
import * as THREE from 'three';
extend(THREE);
@Component({
selector: 'app-box',
template: `
<ngt-object3D rigidBody [position]="[0, 5, 0]">
<ngt-mesh>
<ngt-box-geometry />
<ngt-mesh-standard-material color="hotpink" />
</ngt-mesh>
</ngt-object3D>
`,
imports: [NgtrRigidBody],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class Box {}
@Component({
selector: 'app-ground',
template: `
<ngt-object3D rigidBody="fixed" [position]="[0, -1, 0]">
<ngt-mesh>
<ngt-box-geometry *args="[20, 1, 20]" />
<ngt-mesh-standard-material color="gray" />
</ngt-mesh>
</ngt-object3D>
`,
imports: [NgtrRigidBody, NgtArgs],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class Ground {}
@Component({
template: `
<ngtr-physics [options]="{ gravity: [0, -9.81, 0] }">
<ng-template>
<app-box />
<app-ground />
</ng-template>
</ngtr-physics>
`,
imports: [NgtrPhysics, Box, Ground],
})
export class SceneGraph {}

The main component that creates and manages a Rapier physics world. All physics-enabled objects must be children of this component.

<ngtr-physics [options]="{ gravity: [0, -9.81, 0], debug: true }">
<ng-template>
<!-- Your physics objects here -->
</ng-template>
</ngtr-physics>
OptionTypeDefaultDescription
gravity[x, y, z][0, -9.81, 0]Gravity vector for the physics world
colliders'ball' | 'cuboid' | 'hull' | 'trimesh' | false'cuboid'Default collider type for rigid bodies
pausedbooleanfalsePause the physics simulation
timeStepnumber | 'vary'1/60Fixed timestep for simulation
debugbooleanfalseEnable debug visualization
interpolatebooleantrueEnable transform interpolation
updateLoop'follow' | 'independent''follow'Update loop strategy
updatePrioritynumberundefinedPriority for the update loop
allowedLinearErrornumber0.001Penetration the engine won’t correct
numSolverIterationsnumber4Solver iterations for calculating forces
numInternalPgsIterationsnumber1Internal PGS iterations per solver iteration
predictionDistancenumber0.002Max distance for predictive contacts
minIslandSizenumber128Minimum dynamic bodies per active island
maxCcdSubstepsnumber1Maximum CCD substeps
contactNaturalFrequencynumber30Error reduction parameter frequency
lengthUnitnumber1Approximate size of dynamic objects

Handle Rapier loading failures with a fallback template:

<ngtr-physics>
<ng-template>
<!-- Physics scene content -->
</ng-template>
<ng-template rapierFallback let-error="error">
<p>Failed to load physics: {{ error }}</p>
</ng-template>
</ngtr-physics>

Enable debug rendering to see collider shapes:

<ngtr-physics [options]="{ debug: true }">
<ng-template>
<!-- Your physics objects -->
</ng-template>
</ngtr-physics>

Physics updates are tied to the render loop via beforeRender. Use updatePriority to control scheduling.

<ngtr-physics [options]="{ updateLoop: 'follow', updatePriority: -1 }">...</ngtr-physics>

Physics runs in a separate loop, useful for on-demand rendering scenarios.

<ngtr-physics [options]="{ updateLoop: 'independent' }">...</ngtr-physics>

Ensures consistent physics behavior regardless of frame rate:

<ngtr-physics [options]="{ timeStep: 1/60 }">...</ngtr-physics>

Synchronize physics with actual frame delta times:

<ngtr-physics [options]="{ timeStep: 'vary' }">...</ngtr-physics>

NgtrPhysics exposes several useful properties:

PropertyTypeDescription
rapierSignal<RAPIER | null>The loaded Rapier module
worldSingletonSignal<World | null>The Rapier physics world
pausedSignal<boolean>Whether simulation is paused
collidersSignal<NgtrRigidBodyAutoCollider>Default collider type
rigidBodyStatesMapMap of rigid body states
colliderStatesMapMap of collider states
beforeStepCallbacksSetCallbacks run before physics step
afterStepCallbacksSetCallbacks run after physics step

You can manually step the physics simulation:

@Component({...})
export class MyComponent {
physics = inject(NgtrPhysics);
manualStep() {
this.physics.step(1 / 60); // Step by 1/60 seconds
}
}