Skip to content

Commit 332229c

Browse files
committed
fix: clean up renderer
1 parent b7bede3 commit 332229c

File tree

6 files changed

+88
-50
lines changed

6 files changed

+88
-50
lines changed

apps/demo/src/app/cubes/cubes.component.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { Component, CUSTOM_ELEMENTS_SCHEMA, inject, Input } from '@angular/core';
2-
import { extend, NgtArgs, NgtBeforeRenderEvent, NgtCanvas, NgtStore } from 'angular-three';
1+
import { Component, CUSTOM_ELEMENTS_SCHEMA, ElementRef, inject, Input, ViewChild } from '@angular/core';
2+
import { extend, injectBeforeRender, NgtArgs, NgtCanvas, NgtStore } from 'angular-three';
33
import * as THREE from 'three';
44
import { OrbitControls } from 'three-stdlib';
55

@@ -10,11 +10,11 @@ extend({ OrbitControls });
1010
standalone: true,
1111
template: `
1212
<ngt-mesh
13+
#mesh
1314
(click)="active = !active"
1415
(pointerover)="hover = true"
1516
(pointerout)="hover = false"
1617
[scale]="active ? 1.5 : 1"
17-
(beforeRender)="onBeforeRender($any($event))"
1818
[position]="position"
1919
>
2020
<ngt-box-geometry />
@@ -29,21 +29,27 @@ export class Cube {
2929
active = false;
3030
hover = false;
3131

32-
onBeforeRender({ object }: NgtBeforeRenderEvent<THREE.Mesh>) {
33-
object.rotation.x += 0.01;
34-
object.rotation.y += 0.01;
32+
@ViewChild('mesh', { static: true }) mesh!: ElementRef<THREE.Mesh>;
33+
34+
constructor() {
35+
injectBeforeRender(() => {
36+
const object = this.mesh.nativeElement;
37+
if (object) {
38+
object.rotation.x += 0.01;
39+
object.rotation.y += 0.01;
40+
object.rotation.z += 0.01;
41+
}
42+
});
3543
}
3644
}
3745

3846
@Component({
3947
selector: 'demo-cubes-scene',
4048
standalone: true,
4149
template: `
42-
<ngt-color *args="['#BFD1E5']" attach="background" />
50+
<ngt-color *args="['skyblue']" attach="background" />
4351
44-
<ngt-ambient-light>
45-
<ngt-value rawValue="0.5" attach="intensity" />
46-
</ngt-ambient-light>
52+
<ngt-ambient-light [intensity]="0.5" />
4753
<ngt-spot-light [intensity]="0.5" [position]="10" [angle]="0.15" [penumbra]="1" />
4854
<ngt-point-light [intensity]="0.5" [position]="-10" />
4955

apps/demo/src/app/view-cube/view-cube.component.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { Component, CUSTOM_ELEMENTS_SCHEMA, ElementRef, inject, ViewChild } from '@angular/core';
22
import {
33
extend,
4+
injectBeforeRender,
45
NgtArgs,
56
NgtCanvas,
67
NgtPortal,
78
NgtPortalContent,
89
NgtPush,
9-
NgtRenderState,
1010
NgtRepeat,
1111
NgtRxStore,
1212
NgtStore,
@@ -21,7 +21,7 @@ extend({ OrbitControls });
2121
selector: 'view-cube',
2222
standalone: true,
2323
template: `
24-
<ngt-portal (beforeRender)="onBeforeRender($event.root)" [state]="{camera}">
24+
<ngt-portal [state]="{camera}">
2525
<ng-template ngtPortalContent>
2626
<ngt-mesh
2727
#cube
@@ -64,11 +64,14 @@ export class ViewCube extends NgtRxStore {
6464

6565
hovered = -1;
6666

67-
onBeforeRender({ camera }: NgtRenderState) {
68-
if (this.cube?.nativeElement) {
69-
this.matrix.copy(camera.matrix).invert();
70-
this.cube.nativeElement.quaternion.setFromRotationMatrix(this.matrix);
71-
}
67+
constructor() {
68+
super();
69+
injectBeforeRender(({ camera }) => {
70+
if (this.cube?.nativeElement) {
71+
this.matrix.copy(camera.matrix).invert();
72+
this.cube.nativeElement.quaternion.setFromRotationMatrix(this.matrix);
73+
}
74+
});
7275
}
7376
}
7477

@@ -78,10 +81,14 @@ export class ViewCube extends NgtRxStore {
7881
template: `
7982
<ngt-ambient-light [intensity]="0.5" />
8083
<ngt-mesh [scale]="2">
81-
<ngt-torus-geometry *args="[1, 0.25, 32, 100]" />
84+
<ngt-torus-geometry *args="[1, 0.5, 32, 100]" />
8285
<ngt-mesh-normal-material />
8386
</ngt-mesh>
84-
<ngt-orbit-controls *args="[camera, glDom]" [enableDamping]="true" />
87+
<ngt-orbit-controls
88+
*args="[camera, glDom]"
89+
[enableDamping]="true"
90+
(beforeRender)="$any($event).object.update()"
91+
/>
8592
<view-cube />
8693
`,
8794
imports: [ViewCube, NgtArgs],

libs/angular-three/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ Leverage your [Angular](https://angular.io) to build 3D applications with [THREE
44

55
## Installation
66

7+
```shell
8+
npx ng add angular-three
9+
```
10+
11+
or
12+
713
```shell
814
npm i angular-three three
915
```

libs/angular-three/src/lib/renderer/renderer.ts

Lines changed: 39 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,36 @@ export class NgtRendererFactory implements RendererFactory2 {
1717
private readonly catalogue = inject(NGT_CATALOGUE);
1818
private readonly compoundPrefixes = inject(NGT_COMPOUND_PREFIXES);
1919

20-
private readonly rendererStore = new NgtRendererStore({
21-
store: this.store,
22-
cdr: this.cdr,
23-
compoundPrefixes: this.compoundPrefixes,
24-
});
25-
26-
private renderer?: NgtRenderer;
20+
private rendererMap = new Map<string, Renderer2>();
2721

2822
createRenderer(hostElement: any, type: RendererType2 | null): Renderer2 {
29-
// TODO we might need to check on "type" to return DomRenderer for that particular type to support HTML
30-
if (!this.renderer) {
31-
const domRenderer = this.domRendererFactory.createRenderer(hostElement, type);
32-
this.renderer = new NgtRenderer(domRenderer, this.rendererStore, this.catalogue);
23+
const domRenderer = this.domRendererFactory.createRenderer(hostElement, type);
24+
if (!type) return domRenderer;
25+
26+
let renderer = this.rendererMap.get(type.id);
27+
if (renderer) return renderer;
28+
29+
if (!hostElement) {
30+
const store = new NgtRendererStore({
31+
store: this.store,
32+
cdr: this.cdr,
33+
compoundPrefixes: this.compoundPrefixes,
34+
});
35+
renderer = new NgtRenderer(domRenderer, store, this.catalogue, true);
36+
this.rendererMap.set(type.id, renderer);
37+
}
38+
39+
if (!renderer) {
40+
const store = new NgtRendererStore({
41+
store: this.store,
42+
cdr: this.cdr,
43+
compoundPrefixes: this.compoundPrefixes,
44+
});
45+
renderer = new NgtRenderer(domRenderer, store, this.catalogue);
46+
this.rendererMap.set(type.id, renderer);
3347
}
34-
return this.renderer;
48+
49+
return renderer;
3550
}
3651
}
3752

@@ -41,14 +56,15 @@ export class NgtRenderer implements Renderer2 {
4156
constructor(
4257
private readonly domRenderer: Renderer2,
4358
private readonly store: NgtRendererStore,
44-
private readonly catalogue: Record<string, new (...args: any[]) => any>
59+
private readonly catalogue: Record<string, new (...args: any[]) => any>,
60+
private readonly root = false
4561
) {}
4662

4763
createElement(name: string, namespace?: string | null | undefined) {
4864
const element = this.domRenderer.createElement(name, namespace);
4965

5066
// on first pass, we return the Root Scene as the root node
51-
if (!this.first) {
67+
if (this.root && !this.first) {
5268
this.first = true;
5369
return this.store.createNode('three', this.store.rootScene);
5470
}
@@ -191,19 +207,15 @@ export class NgtRenderer implements Renderer2 {
191207
}
192208
}
193209

194-
if (newChild.__ngt_renderer__[NgtRendererClassId.type] === 'three' && !getLocalState(newChild).parent) {
195-
// we'll try to get the grandparent instance here so that we can run appendChild with both instances
196-
const closestGrandparentInstance = this.store.getClosestParentWithInstance(parent);
197-
if (closestGrandparentInstance) {
198-
this.appendChild(closestGrandparentInstance, newChild);
199-
}
200-
return;
201-
}
210+
const shouldFindGrandparentInstance =
211+
// if child is three but haven't been attached to a parent yet
212+
(newChild.__ngt_renderer__[NgtRendererClassId.type] === 'three' && !getLocalState(newChild).parent) ||
213+
// or both parent and child are DOM elements
214+
(parent.__ngt_renderer__[NgtRendererClassId.type] === 'dom' &&
215+
newChild.__ngt_renderer__[NgtRendererClassId.type] === 'dom');
202216

203-
if (
204-
parent.__ngt_renderer__[NgtRendererClassId.type] === 'dom' &&
205-
newChild.__ngt_renderer__[NgtRendererClassId.type] === 'dom'
206-
) {
217+
if (shouldFindGrandparentInstance) {
218+
// we'll try to get the grandparent instance here so that we can run appendChild with both instances
207219
const closestGrandparentInstance = this.store.getClosestParentWithInstance(parent);
208220
if (closestGrandparentInstance) {
209221
this.appendChild(closestGrandparentInstance, newChild);
@@ -215,7 +227,7 @@ export class NgtRenderer implements Renderer2 {
215227
parent: NgtRendererNode,
216228
newChild: NgtRendererNode
217229
// TODO we might need these?
218-
// refChild: NgtRendererNode,
230+
// refChild: NgtRendererNode
219231
// isMove?: boolean | undefined
220232
): void {
221233
if (!parent.__ngt_renderer__) return;

libs/angular-three/src/lib/renderer/state.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -415,13 +415,19 @@ export class NgtRendererStore {
415415
return directive;
416416
}
417417

418-
tryGetPortalStore() {
418+
private tryGetPortalStore() {
419419
let store: NgtStore | undefined;
420420
// we only care about the portal states because NgtStore only differs per Portal
421421
let i = this.portals.length - 1;
422422
while (i >= 0) {
423423
// loop through the portal state backwards to find the closest NgtStore
424-
const injector = this.portals[i].__ngt_renderer__[NgtRendererClassId.injectorFactory]();
424+
const portal = this.portals[i];
425+
if (portal.__ngt_renderer__[NgtRendererClassId.destroyed]) {
426+
i--;
427+
continue;
428+
}
429+
430+
const injector = portal.__ngt_renderer__[NgtRendererClassId.injectorFactory]();
425431
if (!injector) {
426432
i--;
427433
continue;
@@ -434,6 +440,6 @@ export class NgtRendererStore {
434440
}
435441
i--;
436442
}
437-
return store || this.root.store;
443+
return store || this.root!.store;
438444
}
439445
}

libs/angular-three/src/lib/renderer/utils.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ export function attachThreeChild(parent: NgtInstanceNode, child: NgtInstanceNode
3939
let added = false;
4040

4141
// assign store on child if not already exist
42-
if (!cLS.store) {
42+
// or child store is the parent of parent store
43+
if (!cLS.store || cLS.store === pLS.store.get('previousStore')) {
4344
cLS.store = pLS.store;
4445
}
4546

0 commit comments

Comments
 (0)