Skip to content

Custom Renderer

Catalogue

Angular Three Custom Renderer maintains a single catalogue of entities to render. By default, the catalogue is empty.

extend

In order to populate the catalogue, call the extend function and pass in a Record of entities. Angular Three then maps the catalogue to Custom Elements tags with the following naming convention:

extend({
Mesh, // makes ngt-mesh available
BoxGeometry, // makes ngt-box-geometry available
/* ... */,
MyMesh: Mesh, // makes ngt-my-mesh available
})

CUSTOM_ELEMENTS_SCHEMA

The CUSTOM_ELEMENTS_SCHEMA is required to use Angular Three elements as Angular does not support custom schemas at the moment.

@Component({
standalone: true,
template: `<!-- contains custom ngt elements -->`,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class Experience {}

Property Bindings

We can use property bindings to pass data to any of the NGT elements’ properties.

<ngt-mesh
[position]="[0, 1, 0]"
[rotation]="[Math.PI / 2, 0, 0]"
[scale]="1.5"
>
<ngt-box-geometry />
<ngt-mesh-standard-material
color="hotpink"
/>
</ngt-mesh>

Shortcuts

Some THREE.js entities have different methods to update their values due to performance and how WebGL works. For example, THREE.Vector3 has a set method that accepts a tuple of [x, y, z] instead. On the other hand, if we pass in a THREE.Vector3 instance, the current THREE.Vector3 will call copy and pass in the new instance instead.

This characteristic is baked into the Angular Three Custom Renderer so it is more intuitive to pass values to these properties.

NgtVector*

NgtVector2, NgtVector3, and NgtVector4 are shortcuts types for THREE.Vector2, THREE.Vector3, and THREE.Vector4 respectively.

<ngt-mesh
[position]="[0, 1, 0]"
[scale]="1.5"
[rotation]="myRotation"
></ngt-mesh>

NgtEuler and NgtQuaternion

Similar to NgtVector*, these types are shortcuts for THREE.Euler and THREE.Quaternion respectively.

ColorRepresentation

Similar to NgtVector*, any elements that accept a color property can accept a ColorRepresentation type. This is a THREE.js type and Angular Three Custom Renderer will apply the value properly.

<!-- different ways to pass in color -->
<ngt-mesh-basic-material color="hotpink" />
<ngt-mesh-basic-material color="#ff00ff" />
<ngt-mesh-basic-material color="rgb(255, 0, 255)" />
<ngt-mesh-basic-material [color]="myColor" />
<ngt-mesh-basic-material [color]="myHexadecimalColor" />

NGT Properties

Aside from the elements’ own properties, there are a few properties that are specific to the Angular Three Custom Renderer.

parameters

All custom elements accept a parameters property that accepts an object of properties to pass to the underlying entity.

<ngt-mesh-basic-material
[parameters]="{ color: 'hotpink', side: BackSide, transparent: true }"
/>

attach

This property is used to specify a property on the parent that this element should be attached to. Attaching takes into account the life-cycle of the elements and will automatically detach when the elements are destroyed.

Static Value

If the property on the parent is a static value, use Attribute Binding to bind a static string to the attach property.

<ngt-mesh>
<ngt-mesh-basic-material attach="material" />
</ngt-mesh>

This is equivalent to:

const mesh = new THREE.Mesh();
const material = new THREE.MeshBasicMaterial();
mesh.material = material;
Nested Path

We can attach to a nested property on the parent by using a dot-separated path.

<ngt-spot-light [castShadow]="true">
<ngt-vector2 attach="shadow.mapSize" />
</ngt-spot-light>

This is equivalent to:

const spotLight = new THREE.SpotLight();
spotLight.castShadow = true;
const vector2 = new THREE.Vector2();
// shortcut is still applied automatically
spotLight.shadow.mapSize.copy(vector2);
Dynamic Value

We can pass a dynamic value to attach property by using Property Binding syntax [attach]. When this is the case, attach accepts Array<string | number> as well as string

@Component({
template: `
<ngt-mesh>
<ngt-box-geometry />
@for (color of colors; track $index) {
<ngt-mesh-basic-material
[attach]="['material', $index]"
[color]="color"
/>
}
</ngt-mesh>
`
})
export class MyCube {
colors = [
'red',
'green',
'blue',
'yellow',
'orange',
'purple',
]; // cube has 6 faces
}

This is equivalent to:

const mesh = new THREE.Mesh();
const geometry = new THREE.BoxGeometry();
mesh.geometry = geometry;
mesh.material = [];
const colors = ['red', 'green', 'blue', 'yellow', 'orange', 'purple'];
for (let i = 0; i < colors.length; i++) {
const material = new THREE.MeshLambertMaterial();
material.color.set(colors[i]);
mesh.material[i] = material;
}
NgtAttachFunction

Optionally, we can pass a NgtAttachFunction to attach property. We are responsible for attaching and detaching the elements.

import { createAttachFunction } from 'angular-three';
@Component({
template: `
<ngt-mesh>
<ngt-mesh-basic-material [attach]="attachFn" />
</ngt-mesh>
`
})
export class Experience {
attachFn = createAttachFunction<MeshBasicMaterial, Mesh>(({ parent, child }) => {
const oldMaterial = parent.material;
parent.material = child;
// return a clean-up function that will be called when `ngt-mesh-basic-material` is destroyed
return () => {
parent.material = oldMaterial;
}
})
}

priority

See Render Priority for more information.

Event Bindings

Pointer Events

Event NameDescription
clickIf observed, emits when the object is clicked.
contextmenuIf observed, emits when the object is right-clicked.
dblclickIf observed, emits when the object is double clicked.
pointerupIf observed, emits when the pointer moves up while on the object.
pointerdownIf observed, emits when the pointer moves down while on the object.
pointeroverIf observed, emits when the pointer is over the object.
pointeroutIf observed, emits when the pointer gets on then out of the object.
pointerenterIf observed, emits when the pointer gets on the object.
pointerleaveIf observed, emits when the pointer gets on then out of the object.
pointermoveIf observed, emits when the pointer moves while on the object.
pointermissedIf observed, emits when the pointer misses the object.
pointercancelIf observed, emits when the current pointer event gets cancelled.
wheelIf observed, emits when the wheel is acted on when on the object.

beforeRender

To register a callback in the animation loop, listen to the beforeRender event on a NGT element.

@Component({
template: `
<ngt-mesh
(beforeRender)="onBeforeRender($any($event))"
></ngt-mesh>
`
})
export class Experience {
onBeforeRender(event: NgtBeforeRenderEvent<Mesh>) {
const { object, state } = event;
// runs on every frame
}
}

When the element is destroyed, the callback will be removed automatically.

Render Priority

By default, NGT renders the scene on every frame. If we need to control this process, we can pass priority as Attribute Binding with number-string values to any object whose (beforeRender) is being listened to. When a priority is set, we are responsible to render our scene.

@Component({
template: `
<ngt-mesh
priority="1"
(beforeRender)="onBeforeRender($any($event))"
/>
<ngt-mesh
priority="2"
(beforeRender)="onOtherBeforeRender($any($event))"
/>
`,
})
export class SceneGraph {
onBeforeRender(event: NgtBeforeRenderEvent<Mesh>) {
const { gl, scene, camera } = event.state;
// do something
gl.render(scene, camera);
// do something else
}
onOtherBeforeRender(event: NgtBeforeRenderEvent<Mesh>) {
// this runs after the above beforeRender
}
}

attached

This event is emitted when the element is attached or added to the parent.

@Component({
template: `
<ngt-mesh>
<ngt-mesh-basic-material (attached)="onAttached($any($event))" />
</ngt-mesh>
`
})
export class Experience {
onAttached(event: NgtAfterAttach<Mesh, MeshBasicMaterial>) {
const { parent, node } = event;
// ^? Mesh ^? MeshBasicMaterial
}
}

updated

This event is emitted when the element is updated.

@Component({
template: `
<ngt-mesh
[position]="[1, 1, 1]"
(updated)="onUpdated($any($event)"
></ngt-mesh>
`
})
export class Experience {
onUpdated(event: Mesh) { }
}