angular-three-rapier
Version:
Physics Rapier for Angular Three
410 lines (336 loc) • 10.3 kB
Markdown
# `angular-three-rapier`
This library provides Rapier physics integration for Angular Three. [Rapier](https://rapier.rs/) is a fast, cross-platform physics engine written in Rust with JavaScript bindings.
## Documentation
All public APIs are documented with JSDoc comments. Your IDE will provide inline documentation, parameter hints, and examples as you code.
## Installation
```bash
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 to already have `angular-three` installed
## Usage
```typescript
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 {}
```
## Physics Options
| 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'` |
## Rigid Body
Use `ngt-object3D` with the `rigidBody` attribute. The rigid body type is specified as the attribute value:
```html
<!-- 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>
```
### Rigid Body Types
- `''` 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
### Rigid Body Options
```html
<ngt-object3D
rigidBody
[options]="{
colliders: 'ball',
ccd: true,
gravityScale: 0.5,
linearVelocity: [0, 10, 0],
angularVelocity: [0, 1, 0]
}"
>
...
</ngt-object3D>
```
### Rigid Body Events
```html
<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
Colliders use `ngt-object3D` with specific collider attributes. By default, rigid bodies auto-generate colliders from child meshes.
### Explicit Colliders
```html
<!-- 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]" />
```
### Available Collider Directives
| 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 |
### Disabling Auto-Colliders
```html
<ngt-object3D rigidBody [options]="{ colliders: false }">
<!-- Manual colliders only -->
<ngt-object3D [ballCollider]="[0.5]" />
</ngt-object3D>
```
### Mesh Collider
Generate colliders from mesh geometry:
```html
<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>
```
## Joints
Create joints between rigid bodies using injectable functions:
```typescript
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,
},
},
);
}
```
## Instanced Rigid Bodies
For efficient physics with many identical objects:
```typescript
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],
}));
}
```
## Interaction Groups
Filter collisions between objects:
```typescript
import { interactionGroups } from 'angular-three-rapier';
// Member of group 0, collides with groups 0 and 1
const groups = interactionGroups([0], [0, 1]);
```
```html
<!-- Using directive -->
<ngt-object3D rigidBody [interactionGroups]="[[0], [0, 1]]">...</ngt-object3D>
```
## Physics Hooks
```typescript
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
});
}
}
```
## Debug Visualization
Enable debug rendering via physics options:
```html
<ngtr-physics [options]="{ debug: true }">
<ng-template>
<!-- your physics objects -->
</ng-template>
</ngtr-physics>
```
## Addons
### Attractor
Apply gravitational or magnetic forces:
```typescript
import { NgtrAttractor } from 'angular-three-rapier/addons';
```
```html
<!-- 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
}"
/>
```