Skip to content

Commit d2c329b

Browse files
committed
docs: almost finish custom renderer docs
1 parent 882f7d9 commit d2c329b

File tree

4 files changed

+356
-1
lines changed

4 files changed

+356
-1
lines changed

apps/documentation/docs/api/canvas.mdx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,6 @@ and the following properties:
9292
- A `THREE.Scene`
9393
- A `THREE.Raycaster`
9494
- A `window:resize` listener that will update the `THREE.Renderer` and `THREE.Camera`` when the container is resized.
95+
96+
From THREE.js 0.139+, `THREE.ColorManagement.legacyMode` is set to `false` to enable automatic conversion of colors
97+
based on the Renderer's configured color space. For more on this topic, check [THREE.js Color Management](https://threejs.org/docs/#manual/en/introduction/Color-management)
Lines changed: 347 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,347 @@
1+
---
2+
id: custom-renderer
3+
title: Custom Renderer
4+
sidebar_label: Custom Renderer
5+
---
6+
7+
:::note
8+
Throughout this documentation, when we talk about **Custom Renderer**, we mean the Angular Custom Renderer implementation, not THREE.js Renderer
9+
:::
10+
11+
## Custom Element tags
12+
13+
Since NGT is an [Angular Custom Renderer](https://angular.io/api/core/Renderer2), we can control the _tags_ on the template.
14+
Thanks to that, we can take a set of Custom Element tags and create corresponding THREE.js entities from those tags.
15+
16+
The convention of these Custom Element tags is: `ngt-three-js-class-in-kebab-case`
17+
18+
- `ngt-mesh` -> `THREE.Mesh`
19+
- `ngt-box-geometry` -> `THREE.BoxGeometry`
20+
- `ngt-lOD` -> `THREE.LOD`
21+
22+
### `CUSTOM_ELEMENTS_SCHEMA`
23+
24+
At the moment, Angular **does not** support userland `schemas`. Hence, we need to rely on `CUSTOM_ELEMENTS_SCHEMA` to compile our
25+
application when using Custom Elements tag
26+
27+
```ts
28+
@Component({
29+
template: `...`,
30+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
31+
})
32+
export class SceneGraph {}
33+
```
34+
35+
:::tip
36+
37+
Only the Components that use Custom Element tags in their template needs `CUSTOM_ELEMENTS_SCHEMA`. The Angular Compiler will
38+
throw compilation errors if we violate.
39+
40+
:::
41+
42+
## Catalogue
43+
44+
In order to map Custom Element tags to THREE.js entities, NGT creates an internal `catalogue` to keep a dictionary of THREE.js entities.
45+
This is done so the consumers **do not** have to include the whole THREE.js namespace in their application all the time.
46+
47+
To add THREE.js entities to this `catalogue`, consumers use `extend()` function; ideally at the beginning of the `SceneGraph` component
48+
49+
```ts title="scene-graph.component.ts"
50+
import { extend } from 'angular-three';
51+
52+
// call extend here
53+
extend({ Mesh, BoxGeometry });
54+
55+
@Component({
56+
/*...*/
57+
})
58+
export class SceneGraph {} // SceneGraph can be named anything
59+
```
60+
61+
```ts
62+
@Component({
63+
template: '<ngt-canvas [sceneGraph]="SceneGraph" />',
64+
imports: [NgtCanvas],
65+
})
66+
export class SomeFeatureComponent {
67+
readonly SceneGraph = SceneGraph; // import SceneGraph component above
68+
}
69+
```
70+
71+
:::note
72+
73+
- It is ok to call `extend()` multiple times with duplications
74+
- It is ok to call `extend(THREE)` once to include the entire THREE.js namespace. However, the bundle will also include the entire THREE.js namespace
75+
76+
:::
77+
78+
## THREE.js Inputs
79+
80+
We can pass in any THREE.js entities' properties as Inputs to the Custom Element tags.
81+
82+
```html
83+
<ngt-mesh [position]="[1, 1, 1]" [scale]="1.5" [castShadow]="true">
84+
<ngt-mesh-basic-material color="red" [wireframe]="true" />
85+
</ngt-mesh>
86+
<ngt-ambient-light [intensity]="0.5" />
87+
```
88+
89+
Due to limitations of Angular schemas, we do not have intellisense support here. The best documentation for these Inputs is [THREE.js Documentatation](https://threejs.org)
90+
91+
### Short-cuts
92+
93+
#### `set()`
94+
95+
All Inputs whose underlying object has a `.set()` can accept the same arguments as `set()`. For example, `THREE.Color#set` can accept
96+
a CSS-like color string. Hence, we can pass `color="red"` instead of `[color]="color"` (where `color = new THREE.Color('red')` in our Component code)
97+
98+
```html
99+
<ngt-mesh-basic-material color="red" />
100+
```
101+
102+
#### `setScalar()`
103+
104+
All Inputs whose underlying object has a `.setScalar()` can accept the same arguments as `setScalar()`
105+
106+
```html
107+
<!-- equivalent to [position]="[10, 10, 10]" -->
108+
<ngt-mesh [position]="10" />
109+
```
110+
111+
There are other shortcuts like `copy()`, `fromArray()` etc... The concept is the same but those aren't used as nearly common as `set()` and `setScalar()`
112+
113+
## NGT Inputs
114+
115+
In addition to Inputs that are THREE.js entities' properties, there are several Inputs that are unique to NGT Custom Renderer
116+
117+
### `attach`
118+
119+
This Input is used to specify a property on the parent that this object should be **attached** to. Objects with `attach`
120+
will be taken off their parent when they're not on the template (eg: under an `*ngIf` or some other Structural Directive)
121+
122+
#### Static value
123+
124+
If the property on the parent is known and static, use `attach` as **Attribute Binding** with `string` values.
125+
126+
```html
127+
<ngt-mesh>
128+
<ngt-mesh-basic-material attach="material" />
129+
</ngt-mesh>
130+
```
131+
132+
This is equivalent to
133+
134+
```ts
135+
const mesh = new THREE.Mesh();
136+
const material = new THREE.MeshBasicMaterial();
137+
138+
mesh.material = material;
139+
```
140+
141+
:::info
142+
143+
- All Geometries have `attach="geometry"` by default
144+
- All Materials have `attach="material"` by default
145+
146+
```html
147+
<ngt-mesh>
148+
<!-- implicit attach="geometry" -->
149+
<ngt-box-geometry />
150+
<!-- implicit attach="material" -->
151+
<ngt-mesh-basic-material />
152+
</ngt-mesh>
153+
```
154+
155+
:::
156+
157+
We can also pass a `dotted.path` to `attach` if the property is nested
158+
159+
```html
160+
<ngt-spot-light [castShadow]="true">
161+
<ngt-vector2 attach="shadow.mapSize" />
162+
</ngt-spot-light>
163+
```
164+
165+
This is equivalent to
166+
167+
```ts
168+
const spotLight = new THREE.SpotLight();
169+
spotLight.castShadow = true;
170+
171+
const vector2 = new THREE.Vector2();
172+
// shortcut is still applied automatically
173+
spotLight.shadow.mapSize.copy(vector2);
174+
```
175+
176+
#### Dynamic value
177+
178+
If we need to pass a dynamic value to `attach`, use `attach` as **Property Binding** with `Array<string | number>` values
179+
180+
```html
181+
<ngt-mesh>
182+
<ngt-box-geometry />
183+
<!-- ngForRepeat is an NGT directive. We'll learn about it in a different section -->
184+
<ngt-mesh-lambert-material *ngFor="let i; repeat 6" [attach]="['material', i]" />
185+
</ngt-mesh>
186+
```
187+
188+
This is equivalent to:
189+
190+
```ts
191+
const mesh = new THREE.Mesh();
192+
const geometry = new THREE.BoxGeometry();
193+
194+
mesh.geometry = geometry;
195+
mesh.material = [];
196+
197+
for (let i = 0; i < 6; i++) {
198+
const material = new THREE.MeshLambertMaterial();
199+
mesh.material[i] = material;
200+
}
201+
```
202+
203+
#### `NgtAttachFunction`
204+
205+
Optionally, we can also pass an `NgtAttachFunction` to `[attach]`. When this is the case, we are responsible for **attaching** the child onto the parent
206+
as well as **de-attaching** (clean-up phase)
207+
208+
```ts
209+
// we can import this utility from 'angular-three'
210+
import { createAttachFunction } from 'angular-three':
211+
212+
@Component({
213+
template: `
214+
<ngt-mesh>
215+
<ngt-mesh-basic-material [attach]="attachFn" />
216+
</ngt-mesh>
217+
`,
218+
})
219+
export class SceneGraph {
220+
// "store" is the NgtStore, which has all information about the NgtCanvas
221+
readonly attachFn = createAttachFunction<Mesh, MeshBasicMaterial>(({ parent, child /*, store */ }) => {
222+
const oldMaterial = parent.material;
223+
parent.material = child;
224+
// return a clean-up function that will be called when `ngt-mesh-basic-material` is destroyed
225+
return () => {
226+
parent.material = oldMaterial;
227+
};
228+
});
229+
}
230+
```
231+
232+
### `priority`
233+
234+
See [Render Priority](#render-priority)
235+
236+
### `rawValue`
237+
238+
See [TBD: ngt-value](#)
239+
240+
### `ref`
241+
242+
See [TBD: ref](#)
243+
244+
## Outputs
245+
246+
### Object3D Events
247+
248+
All of the following Outputs will trigger Change Detection upon invoked. This is intentional as we usually update Component's state with these Events.
249+
250+
| name | description |
251+
| ------------- | ------------------------------------------------------------------ |
252+
| click | If observed, emits when the object is clicked |
253+
| contextmenu | If observed, emits when the object is right-clicked |
254+
| dblclick | If observed, emits when the object is double clicked |
255+
| pointerup | If observed, emits when the pointer moves up while on the object |
256+
| pointerdown | If observed, emits when the pointer moves down while on the object |
257+
| pointerover | If observed, emits when the pointer is over the object |
258+
| pointerout | If observed, emits when the pointer gets on then out of the object |
259+
| pointerenter | If observed, emits when the pointer gets on the object |
260+
| pointerleave | If observed, emits when the pointer gets on then out of the object |
261+
| pointermove | If observed, emits when the pointer moves while on the object |
262+
| pointermissed | If observed, emits when the pointer misses the object |
263+
| pointercancel | If observed, emits when the current pointer event gets cancelled |
264+
| wheel | If observed, emits when the wheel is acted on when on the object |
265+
266+
:::info
267+
268+
The events system in NGT is completely ported from R3F. For more information, please check [React Three Fiber Events](https://docs.pmnd.rs/react-three-fiber/api/events)
269+
270+
:::
271+
272+
### `beforeRender`
273+
274+
To register a callback in the animation loop, we can listen for `(beforeRender)`
275+
276+
```ts
277+
@Component({
278+
template: `<ngt-mesh (beforeRender)="onBeforeRender($any($event))" />`,
279+
})
280+
export class SceneGraph {
281+
onBeforeRender(event: NgtBeforeRenderEvent<Mesh>) {
282+
// call per frame, will not trigger Change Detection
283+
}
284+
}
285+
```
286+
287+
When the element is destroyed, `(beforeRender)` is unregistered automatically.
288+
289+
:::note
290+
We use `$any($event)` because of Angular limitations on `CUSTOM_ELEMENTS_SCHEMA`
291+
:::
292+
293+
#### Render Priority
294+
295+
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
296+
to any object whose `(beforeRender)` is being listened to. When a `priority` is set, we are responsible to render our scene now.
297+
298+
```ts
299+
@Component({
300+
template: `
301+
<ngt-mesh priority="1" (beforeRender)="onBeforeRender($any($event))" />
302+
<ngt-mesh priority="2" (beforeRender)="onOtherBeforeRender($any($event))" />
303+
`,
304+
})
305+
export class SceneGraph {
306+
onBeforeRender(event: NgtBeforeRenderEvent<Mesh>) {
307+
const { gl, scene, camera } = event.state;
308+
// do something
309+
gl.render(scene, camera);
310+
// do something else
311+
}
312+
313+
onOtherBeforeRender(event: NgtBeforeRenderEvent<Mesh>) {
314+
// this runs after the above beforeRender
315+
}
316+
}
317+
```
318+
319+
### `afterAttach`
320+
321+
This event emits after the child **has been attached** to the parent
322+
323+
```ts
324+
@Component({
325+
template: `
326+
<ngt-mesh>
327+
<ngt-mesh-basic-material attach="material" (afterAttach)="onAfterAttach($any($event))" />
328+
</ngt-mesh>
329+
`,
330+
})
331+
export class SceneGraph {
332+
onAfterAttach(event: NgtAfterAttach<Mesh, MeshBasicMaterial>) {}
333+
}
334+
```
335+
336+
### `afterUpdate`
337+
338+
This event emits after an object is updated
339+
340+
```ts
341+
@Component({
342+
template: ` <ngt-mesh (afterUpdate)="onAfterUpdate($any($event))" [position]="[0, 1, 2]" /> `,
343+
})
344+
export class SceneGraph {
345+
onAfterUpdate(event: Mesh) {}
346+
}
347+
```

apps/documentation/docs/api/ref.mdx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
id: ref
3+
title: Ref
4+
sidebar_label: Ref
5+
---

apps/documentation/sidebars.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ const sidebars = {
2222
{
2323
type: 'category',
2424
label: 'API',
25-
items: ['api/canvas'],
25+
items: ['api/canvas', 'api/custom-renderer', 'api/ref'],
2626
},
2727
],
2828
// By default, Docusaurus generates a sidebar from the docs folder structure

0 commit comments

Comments
 (0)