Skip to content

Commit c8c3079

Browse files
committed
docs: finish current API
1 parent 380a739 commit c8c3079

File tree

11 files changed

+333
-4
lines changed

11 files changed

+333
-4
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
---
2+
id: additional-exports
3+
title: Additional Exports
4+
sidebar_label: Additional Exports
5+
---
6+
7+
## `injectBeforeRender()`
8+
9+
A shortcut to register a before render callback
10+
11+
```ts
12+
@Component({
13+
/*...*/
14+
})
15+
export class SceneGraph {
16+
readonly sub = injectBeforeRender(() => {}, 0);
17+
}
18+
```
19+
20+
:::info
21+
22+
`injectBeforeRender()` automatically cleans up the before render but it returns the reference to the clean up function.
23+
We can call this function anytime to clean up the before render, not just in `ngOnDestroy`
24+
25+
:::
26+
27+
## `is`
28+
29+
A collection of utility functions to assert object type
30+
31+
```ts
32+
is.obj(a); // checks if a value is an object; not an Array nor a Function
33+
is.ref(a); // checks if a value is ElementRef
34+
is.object3D(a); // checks if a value is an instance of THREE.Object3D
35+
// and more
36+
```

apps/documentation/docs/api/custom-renderer.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ See [TBD: ngt-value](#)
239239

240240
### `ref`
241241

242-
See [TBD: ref](#)
242+
See [Ref](./ref)
243243

244244
## Outputs
245245

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
---
2+
id: args
3+
title: NgtArgs
4+
sidebar_label: NgtArgs
5+
---
6+
7+
Some THREE.js entities like Geometries accept **Constructor Arguments** and it is **required** to reconstruct
8+
these entities when the arguments change.
9+
10+
```ts
11+
let geometry = new BoxGeometry(); // [1, 1, 1] box
12+
mesh.geometry = geometry;
13+
14+
// later when we want a bigger box
15+
mesh.geometry.dispose(); // dispose old box
16+
// construct new box
17+
geometry = new BoxGeometry(2, 2, 2); // [2, 2, 2] box
18+
mesh.geometry = geometry;
19+
```
20+
21+
To achieve this with the Custom Renderer, NGT provides a structural directive `NgtArgs`
22+
23+
```html
24+
<ngt-box-geometry *args="boxArgs" />
25+
```
26+
27+
When `boxArgs` changes, `*args` will destroy the current instance of `ngt-box-geometry` then re-create a new one with
28+
the new `boxArgs`
29+
30+
`*args` accepts an Array of **Constructor Arguments** that the underlying object accepts.
31+
32+
```html
33+
<ngt-box-geometry *args="[width, height, depth, widthSegments, heightSegments, depthSegments]" />
34+
35+
<ngt-instanced-mesh *args="[geometry, material, count]" />
36+
37+
<ngt-instanced-mesh *args="[undefined, undefined, count]">
38+
<ngt-box-geometry />
39+
<ngt-mesh-standard-material />
40+
</ngt-instanced-mesh>
41+
42+
<ngt-spot-light>
43+
<ngt-vector2 *args="[2048, 2048]" attach="shadow.mapSize" />
44+
</ngt-spot-light>
45+
```
46+
47+
Please consult [THREE.js Documentation](https://threejs.org) for details on the **Construtor Arguments**
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
id: repeat
3+
title: NgtRepeat
4+
sidebar_label: NgtRepeat
5+
---
6+
7+
`NgtRepeat` is a structural directive which subclasses `NgFor` to add `ngForRepeat` Input. This is done to provide an
8+
easy way of looping over a number of times to render a specific amount of objects.
9+
10+
```html
11+
<ngt-mesh>
12+
<ngt-box-geometry />
13+
<!-- a Box should have 6 faces, we want to assign an independent Material to each face -->
14+
<!-- without NgtRepeat -->
15+
<ngt-mesh-standard-material *ngFor="let i of [0, 1, 2, 3, 4, 5]" [attach]="['material', i]" />
16+
<!-- with NgtRepeat -->
17+
<ngt-mesh-standard-material *ngFor="let i; repeat 6" [attach]="['material', i]" />
18+
</ngt-mesh>
19+
```
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
id: push
3+
title: NgtPush
4+
sidebar_label: NgtPush
5+
---
6+
7+
`NgtPush` is similar to Angular's `AsyncPipe`. The difference is `NgtPush` works with a CD-detached environment like
8+
NGT.
9+
10+
```html
11+
<!-- might not work because Change Detection doesn't run -->
12+
<ngt-box-geometry *args="args$ | async" />
13+
<!-- ensures Change Detection triggers on changes -->
14+
<ngt-box-geometry *args="args$ | ngtPush" />
15+
```
16+
17+
### Default Value
18+
19+
`NgtPush` accepts a default value for the first emission.
20+
21+
```html
22+
<!-- make a box of [2, 2, 2] by default -->
23+
<ngt-box-geometry *args="args$ | ngtPush: [2, 2, 2]" />
24+
```
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
id: primitive
3+
title: Primitive
4+
sidebar_label: Primitive
5+
---
6+
7+
There are occasions that we need to put already-exist 3D objects on our SceneGraph (eg: an external 3D model). For this,
8+
we can use `<ngt-primitive>` Custom Element tag as a placeholder for our 3D objects
9+
10+
```html
11+
<ngt-primitive *args="[object]" [position]="[1, 1, 1]" />
12+
```
13+
14+
:::note
15+
16+
- `<ngt-primitive>` always requires `*args` with one item, the 3D object we want to put on the SceneGraph.
17+
- We can bind Inputs/Outputs to `<ngt-primitive>` and those will be forwarded to the underlying 3D object.
18+
- NGT Custom Renderer **does not** dispose `<ngt-primitive>` underlying 3D object on destroy, we have to do it manually.
19+
20+
:::
21+
22+
A more realistic use-case is to load a 3D model into our SceneGraph
23+
24+
```ts
25+
@Component({
26+
template: ` <ngt-primitive *args="[model$ | ngtPush]" [scale]="0.01" /> `,
27+
imports: [NgtArgs, NgtPush],
28+
})
29+
export class SceneGraph {
30+
readonly model$ = injectNgtLoader(GLTFLoader, 'assets/model.glb').pipe(map((model) => model.scene));
31+
}
32+
```
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
id: raw-value
3+
title: Raw Value
4+
sidebar_label: Raw Value
5+
---
6+
7+
There are occasions where we want to declaratively assign a single value to a property on a parent element, we can use
8+
`<ngt-value>` Custom Element tag.
9+
10+
```html
11+
<!-- this saves us the trouble of getting access to the PointLight instance and assign 0.0001 to light.shadow.bias -->
12+
<ngt-point-light>
13+
<!-- with this, we can declaratively assign a value to shadow.bias; under an ngIf or something similar -->
14+
<ngt-value [rawValue]="0.0001" attach="shadow.bias" />
15+
</ngt-point-light>
16+
```

apps/documentation/docs/api/ref.mdx

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,72 @@ id: ref
33
title: Ref
44
sidebar_label: Ref
55
---
6+
7+
Ref is a concept borrowed from [React](https://beta.reactjs.org/learn/referencing-values-with-refs), but with a little twist.
8+
In THREE.js, it is common to mutate properties on an object rather than staying in the immutable lane that we're used to.
9+
And there are many sources that can mutate properties of the same object: THREE.js, Cannon.js (Physics engine), Animations (GSAP) etc...
10+
11+
With that, NGT decides to implement the Ref concept.
12+
13+
## `ViewChild` / `ContentChild`
14+
15+
We can get a hold of an object on the template using the good ol' `ViewChild` or `ContentChild`
16+
17+
```ts
18+
@Component({
19+
template: `<ngt-mesh #mesh />`,
20+
})
21+
export class SceneGraph {
22+
@ViewChild('mesh', { static: true }) mesh!: ElementRef<Mesh>;
23+
}
24+
```
25+
26+
We can also use `ViewChildren` / `ContentChildren` if we want to interact with the [QueryList API](https://angular.io/api/core/QueryList)
27+
instead.
28+
29+
## `injectNgtRef()`
30+
31+
More than often, we want to define an **external** Ref that we can pass around. To do so, we can use `injectNgtRef()` function
32+
to create a Ref.
33+
34+
```ts
35+
@Component({
36+
template: `<ngt-mesh />`,
37+
})
38+
export class SceneGraph {
39+
// highlight-next-line
40+
readonly ref = injectNgtRef<Mesh>();
41+
}
42+
```
43+
44+
Then, we pass our `ref` into the `[ref]` Input on the element.
45+
46+
```ts
47+
@Component({
48+
template: `
49+
// highlight-next-line
50+
<ngt-mesh [ref]="ref" />
51+
`,
52+
})
53+
export class SceneGraph {
54+
readonly ref = injectNgtRef();
55+
}
56+
```
57+
58+
The NGT Custom Renderer will assign the `Mesh` object to the `ref.nativeElement` when it is available. `injectNgtRef()`
59+
returns an `NgtInjectedRef<ObjectType>`.
60+
61+
```ts
62+
type Subscribe<T> = (callback: (current: T, previous: T | null) => void) => Subscription;
63+
64+
export type NgtInjectedRef<T> = ElementRef<T> & {
65+
/* a Subscribe fn that emits current and previous value. Useful for debug */
66+
subscribe: Subscribe<T>;
67+
/* consumers should use this for listening to value of this ref. This filters out initial null value */
68+
$: Observable<T>;
69+
/* consumers should use this for listenting to children changes on this ref */
70+
children$: (type?: 'objects' | 'nonObjects' | 'both') => Observable<NgtInstanceNode[]>;
71+
/* notify this CD when ref value changes */
72+
useCDR: (cdr: ChangeDetectorRef) => void;
73+
};
74+
```title

apps/documentation/docs/api/store.mdx

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
---
2+
id: store
3+
title: Store
4+
sidebar_label: Store
5+
---
6+
7+
`NgtStore` contains all information about the current `NgtCanvas`. `NgtStore` extends [RxState](https://www.rx-angular.io/docs/state/api/rx-state) API
8+
9+
```ts
10+
@Component({
11+
/*...*/
12+
})
13+
export class SceneGraph {
14+
// inject the Store. We can also use Constructor DI
15+
readonly store = inject(NgtStore);
16+
}
17+
```
18+
19+
## Select states reactively
20+
21+
```ts
22+
@Component({
23+
/*...*/
24+
})
25+
export class SceneGraph {
26+
readonly store = inject(NgtStore);
27+
readonly camera$ = this.store.select('camera'); // Observable<NgtCamera>
28+
readonly glDom$ = this.store.select('gl', 'domElement'); // Observable<HTMLElement>
29+
}
30+
```
31+
32+
## Get states imperatively
33+
34+
```ts
35+
@Component({
36+
/*...*/
37+
})
38+
export class SceneGraph {
39+
readonly store = inject(NgtStore);
40+
readonly camera = this.store.get('camera'); // NgtCamera
41+
readonly glDom = this.store.get('gl', 'domElement'); // HTMLElement
42+
}
43+
```
44+
45+
## Register before render callbacks
46+
47+
Beside using `(beforeRender)` Output on Custom Element tags, we can also use `NgtStore` to register a before render callback
48+
49+
```ts
50+
@Component({
51+
/*...*/
52+
})
53+
export class SceneGraph {
54+
readonly store = inject(NgtStore);
55+
56+
private sub?: () => void;
57+
58+
ngOnInit() {
59+
// register and return the clean up function
60+
// signature: subscribe(callback, priority)
61+
this.sub = this.store.get('internal').subscribe(() => {}, 0);
62+
}
63+
64+
ngOnDestroy() {
65+
// call the clean up function to unregister the callback
66+
this.sub?.();
67+
}
68+
}
69+
```

apps/documentation/sidebars.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,25 @@ const sidebars = {
2222
{
2323
type: 'category',
2424
label: 'API',
25-
items: ['api/canvas', 'api/custom-renderer', 'api/ref'],
25+
items: [
26+
'api/canvas',
27+
'api/custom-renderer',
28+
{
29+
type: 'category',
30+
label: 'Directives',
31+
items: ['api/directives/args', 'api/directives/repeat'],
32+
},
33+
{
34+
type: 'category',
35+
label: 'Pipes',
36+
items: ['api/pipes/push'],
37+
},
38+
'api/ref',
39+
'api/primitive',
40+
'api/raw-value',
41+
'api/store',
42+
'api/additional-exports',
43+
],
2644
},
2745
],
2846
// By default, Docusaurus generates a sidebar from the docs folder structure

apps/documentation/src/pages/index.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import React from 'react';
21
import Link from '@docusaurus/Link';
32
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
43
import Layout from '@theme/Layout';
@@ -19,7 +18,7 @@ function HomepageHeader() {
1918
<small>"What happened to Angular 3.0? Well, it became Angular Three" - Mike Hartington</small>
2019
</em>
2120
<div className={styles.buttons}>
22-
<Link className="button button--secondary button--lg" to="/docs/getting-started/overview">
21+
<Link className="button button--secondary button--lg" to="/docs/getting-started/introduction">
2322
Get Started
2423
</Link>
2524
</div>

0 commit comments

Comments
 (0)