This library provides Rapier physics integration for Angular Three. Rapier is a fast, cross-platform physics engine written in Rust with JavaScript bindings.
All public APIs are documented with JSDoc comments. Your IDE will provide inline documentation, parameter hints, and examples as you code.
npm install angular-three-rapier @dimforge/rapier3d-compat
# yarn add angular-three-rapier @dimforge/rapier3d-compat
# pnpm add angular-three-rapier @dimforge/rapier3d-compatMake sure to already have
angular-threeinstalled
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 {}| Property | Description | Default |
|---|---|---|
gravity |
Gravity vector [x, y, z] |
[0, -9.81, 0] |
colliders |
Default collider type for rigid bodies | 'cuboid' |
paused |
Whether physics simulation is paused | false |
timeStep |
Fixed timestep for physics simulation | 1/60 |
debug |
Enable debug visualization | false |
interpolate |
Enable transform interpolation | true |
updateLoop |
Update loop type: 'follow' or 'independent' |
'follow' |
Use ngt-object3D with the rigidBody attribute. The rigid body type is specified as the attribute value:
<!-- Dynamic (default when empty) -->
<ngt-object3D rigidBody [position]="[0, 5, 0]">
<ngt-mesh>...</ngt-mesh>
</ngt-object3D>
<!-- Fixed (static) -->
<ngt-object3D rigidBody="fixed" [position]="[0, -1, 0]">
<ngt-mesh>...</ngt-mesh>
</ngt-object3D>
<!-- Kinematic -->
<ngt-object3D rigidBody="kinematicPosition">
<ngt-mesh>...</ngt-mesh>
</ngt-object3D>''or'dynamic'- Affected by forces and collisions'fixed'- Static, immovable body'kinematicPosition'- Controlled by position, affects dynamic bodies'kinematicVelocity'- Controlled by velocity, affects dynamic bodies
<ngt-object3D
rigidBody
[options]="{
colliders: 'ball',
ccd: true,
gravityScale: 0.5,
linearVelocity: [0, 10, 0],
angularVelocity: [0, 1, 0]
}"
>
...
</ngt-object3D><ngt-object3D
rigidBody
(collisionEnter)="onCollisionEnter($event)"
(collisionExit)="onCollisionExit($event)"
(intersectionEnter)="onIntersectionEnter($event)"
(intersectionExit)="onIntersectionExit($event)"
(contactForce)="onContactForce($event)"
(sleep)="onSleep()"
(wake)="onWake()"
>
...
</ngt-object3D>Colliders use ngt-object3D with specific collider attributes. By default, rigid bodies auto-generate colliders from child meshes.
<!-- Ball collider -->
<ngt-object3D [ballCollider]="[0.5]" [position]="[0, 2, 0]" />
<!-- Cuboid collider (half-extents) -->
<ngt-object3D [cuboidCollider]="[1, 0.5, 2]" [position]="[0, 0, 0]" />
<!-- Capsule collider (half-height, radius) -->
<ngt-object3D [capsuleCollider]="[0.5, 0.25]" [position]="[0, 1, 0]" />
<!-- Cylinder collider (half-height, radius) -->
<ngt-object3D [cylinderCollider]="[1, 0.5]" />
<!-- Cone collider (half-height, radius) -->
<ngt-object3D [coneCollider]="[1, 0.5]" />| Directive | Args | Description |
|---|---|---|
NgtrBallCollider |
[radius] |
Sphere shape |
NgtrCuboidCollider |
[halfW, halfH, halfD] |
Box shape |
NgtrCapsuleCollider |
[halfHeight, radius] |
Capsule shape |
NgtrCylinderCollider |
[halfHeight, radius] |
Cylinder shape |
NgtrConeCollider |
[halfHeight, radius] |
Cone shape |
NgtrConvexHullCollider |
[vertices] |
Convex hull |
NgtrTrimeshCollider |
[vertices, indices] |
Triangle mesh |
NgtrHeightfieldCollider |
[width, height, heights, scale] |
Terrain heightfield |
NgtrRoundCuboidCollider |
[halfW, halfH, halfD, radius] |
Rounded box |
NgtrRoundCylinderCollider |
[halfH, radius, borderRadius] |
Rounded cylinder |
NgtrRoundConeCollider |
[halfH, radius, borderRadius] |
Rounded cone |
<ngt-object3D rigidBody [options]="{ colliders: false }">
<!-- Manual colliders only -->
<ngt-object3D [ballCollider]="[0.5]" />
</ngt-object3D>Generate colliders from mesh geometry:
<ngt-object3D rigidBody [options]="{ colliders: false }">
<ngt-object3D [meshCollider]="'trimesh'">
<ngt-mesh>
<ngt-torus-geometry />
<ngt-mesh-standard-material />
</ngt-mesh>
</ngt-object3D>
</ngt-object3D>Create joints between rigid bodies using injectable functions:
import { Component, viewChild } from '@angular/core';
import {
NgtrRigidBody,
sphericalJoint,
revoluteJoint,
prismaticJoint,
fixedJoint,
ropeJoint,
springJoint,
} from 'angular-three-rapier';
@Component({
template: `
<ngt-object3D rigidBody="fixed" #bodyA="rigidBody">...</ngt-object3D>
<ngt-object3D rigidBody #bodyB="rigidBody">...</ngt-object3D>
`,
})
export class JointExample {
bodyA = viewChild.required<NgtrRigidBody>('bodyA');
bodyB = viewChild.required<NgtrRigidBody>('bodyB');
// Spherical joint (ball-and-socket)
joint = sphericalJoint(
() => this.bodyA().rigidBody(),
() => this.bodyB().rigidBody(),
{
data: {
body1Anchor: [0, -0.5, 0],
body2Anchor: [0, 0.5, 0],
},
},
);
// Revolute joint (hinge) with limits
hingeJoint = revoluteJoint(
() => this.bodyA().rigidBody(),
() => this.bodyB().rigidBody(),
{
data: {
body1Anchor: [0, 0, 0],
body2Anchor: [0, 1, 0],
axis: [0, 1, 0],
limits: [-Math.PI / 2, Math.PI / 2],
},
},
);
// Prismatic joint (slider)
sliderJoint = prismaticJoint(
() => this.bodyA().rigidBody(),
() => this.bodyB().rigidBody(),
{
data: {
body1Anchor: [0, 0, 0],
body2Anchor: [2, 0, 0],
axis: [1, 0, 0],
limits: [-1, 1],
},
},
);
// Rope joint (max distance constraint)
rope = ropeJoint(
() => this.bodyA().rigidBody(),
() => this.bodyB().rigidBody(),
{
data: {
body1Anchor: [0, 0, 0],
body2Anchor: [0, 0, 0],
length: 5,
},
},
);
// Spring joint
spring = springJoint(
() => this.bodyA().rigidBody(),
() => this.bodyB().rigidBody(),
{
data: {
body1Anchor: [0, 0, 0],
body2Anchor: [0, 0, 0],
restLength: 2,
stiffness: 100,
damping: 10,
},
},
);
}For efficient physics with many identical objects:
import { NgtArgs } from 'angular-three';
import { NgtrInstancedRigidBodies } from 'angular-three-rapier';
@Component({
template: `
<ngt-object3D [instancedRigidBodies]="instances">
<ngt-instanced-mesh [count]="instances.length" castShadow>
<ngt-sphere-geometry *args="[0.5]" />
<ngt-mesh-standard-material color="orange" />
</ngt-instanced-mesh>
</ngt-object3D>
`,
imports: [NgtrInstancedRigidBodies, NgtArgs],
})
export class Spheres {
instances = Array.from({ length: 100 }, (_, i) => ({
key: i,
position: [Math.random() * 10, Math.random() * 10, 0] as [number, number, number],
}));
}Filter collisions between objects:
import { interactionGroups } from 'angular-three-rapier';
// Member of group 0, collides with groups 0 and 1
const groups = interactionGroups([0], [0, 1]);<!-- Using directive -->
<ngt-object3D rigidBody [interactionGroups]="[[0], [0, 1]]">...</ngt-object3D>import { beforePhysicsStep, afterPhysicsStep } from 'angular-three-rapier';
@Component({...})
export class MyComponent {
constructor() {
beforePhysicsStep((world) => {
// Run before each physics step
});
afterPhysicsStep((world) => {
// Run after each physics step
});
}
}Enable debug rendering via physics options:
<ngtr-physics [options]="{ debug: true }">
<ng-template>
<!-- your physics objects -->
</ng-template>
</ngtr-physics>Apply gravitational or magnetic forces:
import { NgtrAttractor } from 'angular-three-rapier/addons';<!-- Attractor with default options -->
<ngt-object3D attractor />
<!-- Attractor with custom options -->
<ngt-object3D [attractor]="{ strength: 10, range: 20, type: 'linear' }" />
<!-- Repeller (negative strength) -->
<ngt-object3D [attractor]="{ strength: -10, range: 15 }" [position]="[5, 0, 0]" />
<!-- Newtonian gravity -->
<ngt-object3D
[attractor]="{
strength: 1000,
range: 50,
type: 'newtonian',
gravitationalConstant: 0.01
}"
/>