NgtcPhysics
angular-three-cannon
1import { NgtcPhysics } from 'angular-three-cannon';
1@Component({2 standalone: true,3 imports: [NgtcPhysics],4 template: `5 <ngtc-physics>6 <!-- objects that are subject to physics -->7 </ngtc-physics>8 `,9 schemas: [CUSTOM_ELEMENTS_SCHEMA],10})11export class SceneGraph {}
angular-three-cannon/body provides a variety of body shapes in form of Custom Inject Function that can be used to create physics bodies.
angular-three-cannon/body
1import { injectBox } from 'angular-three-cannon/body';2 3@Component({4 selector: 'app-box',5 standalone: true,6 template: `7 <ngt-mesh #mesh [receiveShadow]="true" [castShadow]="true">8 <ngt-box-geometry />9 </ngt-mesh>10 `,11 imports: [NgtArgs],12 schemas: [CUSTOM_ELEMENTS_SCHEMA],13 changeDetection: ChangeDetectionStrategy.OnPush,14})15export class Box {16 mesh = viewChild.required<ElementRef<Mesh>>('mesh');17 box = injectBox(() => ({ mass: 1 }), this.mesh);18}
injectBox accepts some physics properties as an argument and an () => ElementRef<Object3D> as the second argument to tie the body to the object.
injectBox
() => ElementRef<Object3D>
The returned value is a Signal<NgtcBodyPublicApi | null> that can be used to control the body, set position, rotation, and subscribe to changes.
Signal<NgtcBodyPublicApi | null>
1import { injectBox } from 'angular-three-cannon/body';2 3@Component({4 selector: 'app-box',5 standalone: true,6 template: `7 <ngt-mesh #mesh [receiveShadow]="true" [castShadow]="true">8 <ngt-box-geometry />9 </ngt-mesh>10 `,11 imports: [NgtArgs],12 schemas: [CUSTOM_ELEMENTS_SCHEMA],13 changeDetection: ChangeDetectionStrategy.OnPush,14})15export class Box {16 mesh = viewChild.required<ElementRef<Mesh>>('mesh');17 box = injectBox(() => ({ mass: 1 }), this.mesh);18 19 constructor() {20 injectBeforeRender(({ clock }) => {21 const api = this.box();22 if (!api) return;23 api.position.set(Math.sin(clock.getElapsedTime()) * 5, 0, 0);24 })25 }26}
1import {2 ChangeDetectionStrategy,3 Component,4 CUSTOM_ELEMENTS_SCHEMA,5 effect,6 type ElementRef,7 input,8 viewChild,9 viewChildren,10} from '@angular/core';11import type { Triplet } from '@pmndrs/cannon-worker-api';12import { extend, injectStore, NgtArgs, NgtCanvas, type NgtVector3 } from 'angular-three';13import { NgtcPhysics } from 'angular-three-cannon';14import { injectBox, injectPlane } from 'angular-three-cannon/body';15import type { Mesh } from 'three';16import * as THREE from 'three';17 18extend(THREE);19 20@Component({21 selector: 'app-plane',22 standalone: true,23 template: `24 <ngt-mesh #mesh [receiveShadow]="true">25 <ngt-plane-geometry *args="[1000, 1000]" />26 <ngt-shadow-material color="#171717" [transparent]="true" [opacity]="0.4" />27 </ngt-mesh>28 `,29 schemas: [CUSTOM_ELEMENTS_SCHEMA],30 changeDetection: ChangeDetectionStrategy.OnPush,31 imports: [NgtArgs],32})33export class Plane {34 meshRef = viewChild.required<ElementRef<Mesh>>('mesh');35 constructor() {36 injectPlane(() => ({ rotation: [-Math.PI / 2, 0, 0], position: [0, -2.5, 0] }), this.meshRef);37 }38}39 40@Component({41 selector: 'app-cube',42 standalone: true,43 template: `44 <ngt-mesh #mesh [receiveShadow]="true" [castShadow]="true">45 <ngt-box-geometry />46 <ngt-mesh-lambert-material color="hotpink" />47 </ngt-mesh>48 `,49 schemas: [CUSTOM_ELEMENTS_SCHEMA],50 changeDetection: ChangeDetectionStrategy.OnPush,51})52export class Cube {53 position = input<NgtVector3>([0, 5, 0]);54 55 meshRef = viewChild.required<ElementRef<Mesh>>('mesh');56 57 boxApi = injectBox(58 () => ({ mass: 1, position: this.position() as Triplet, rotation: [0.4, 0.2, 0.5], args: [1, 1, 1] }),59 this.meshRef,60 );61}62 63@Component({64 standalone: true,65 template: `66 <ngt-color attach="background" *args="['lightblue']" />67 <ngt-ambient-light />68 <ngt-directional-light [position]="10" [castShadow]="true">69 <ngt-vector2 *args="[2048, 2048]" attach="shadow.mapSize" />70 </ngt-directional-light>71 <ngtc-physics>72 <app-plane />73 @for (position of cubePositions; track $index) {74 <app-cube [position]="position" />75 }76 </ngtc-physics>77 `,78 imports: [Plane, Cube, NgtArgs, NgtcPhysics],79 schemas: [CUSTOM_ELEMENTS_SCHEMA],80 changeDetection: ChangeDetectionStrategy.OnPush,81})82export class SceneGraph {83 cubePositions: Triplet[] = [84 [0.1, 5, 0],85 [0, 10, -1],86 [0, 20, -2],87 ];88 89 cubes = viewChildren(Cube);90 91 constructor() {92 const store = injectStore();93 94 effect((onCleanup) => {95 const cubes = this.cubes();96 if (!cubes.length) return;97 98 const sub = store.snapshot.pointerMissed$.subscribe(() => {99 cubes.forEach((cube, index) => {100 cube.boxApi()?.position.set(...this.cubePositions[index]);101 cube.boxApi()?.rotation.set(0.4, 0.2, 0.5);102 });103 });104 onCleanup(() => sub.unsubscribe());105 });106 }107}108 109@Component({110 standalone: true,111 template: `112 <ngt-canvas113 [sceneGraph]="sceneGraph"114 [camera]="{ position: [-1, 5, 5], fov: 45 }"115 [shadows]="true"116 [dpr]="[1, 2]"117 [gl]="{ alpha: false }"118 />119 `,120 changeDetection: ChangeDetectionStrategy.OnPush,121 imports: [NgtCanvas],122 host: { class: 'cannon-sample' },123})124export default class CannonSample {125 sceneGraph = SceneGraph;126}