Skip to content

Commit 8864e0b

Browse files
committed
feat(core): first nativescript entry point release
1 parent af5e8c6 commit 8864e0b

File tree

6 files changed

+214
-2
lines changed

6 files changed

+214
-2
lines changed

libs/core/nativescript/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
# angular-three/nativescript
22

33
Secondary entry point of `angular-three`. It can be used by importing from `angular-three/nativescript`.
4+
5+
Depends on `@nativescript/canvas`, `@nativescript/canvas-three`, and `@nativescript/canvas-media`.

libs/core/nativescript/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './lib/canvas';
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import '@nativescript/canvas-three';
2+
3+
import { DOCUMENT } from '@angular/common';
4+
import {
5+
afterNextRender,
6+
booleanAttribute,
7+
ChangeDetectionStrategy,
8+
Component,
9+
ComponentRef,
10+
createEnvironmentInjector,
11+
DestroyRef,
12+
EnvironmentInjector,
13+
inject,
14+
Injector,
15+
input,
16+
NgZone,
17+
NO_ERRORS_SCHEMA,
18+
output,
19+
signal,
20+
Type,
21+
untracked,
22+
viewChild,
23+
ViewContainerRef,
24+
} from '@angular/core';
25+
import { registerElement } from '@nativescript/angular';
26+
import { Canvas } from '@nativescript/canvas';
27+
import {
28+
injectCanvasRootInitializer,
29+
injectStore,
30+
makeDpr,
31+
NgtCanvasConfigurator,
32+
NgtCanvasOptions,
33+
NgtDpr,
34+
NgtGLOptions,
35+
NgtPerformance,
36+
NgtRoutedScene,
37+
NgtSize,
38+
NgtState,
39+
provideNgtRenderer,
40+
provideStore,
41+
} from 'angular-three';
42+
import { injectAutoEffect } from 'ngxtension/auto-effect';
43+
import { Raycaster, Scene, Vector3, WebGLRenderer } from 'three';
44+
45+
registerElement('Canvas', () => Canvas);
46+
47+
@Component({
48+
selector: 'NgtCanvas',
49+
standalone: true,
50+
template: `
51+
<GridLayout>
52+
<Canvas #canvas style="width: 100%; height: auto" (ready)="canvasElement.set($event.object)"></Canvas>
53+
</GridLayout>
54+
`,
55+
providers: [{ provide: DOCUMENT, useValue: document }, provideStore()],
56+
schemas: [NO_ERRORS_SCHEMA],
57+
changeDetection: ChangeDetectionStrategy.OnPush,
58+
})
59+
export class NgtCanvasNative {
60+
sceneGraph = input.required<Type<any>, Type<any> | 'routed'>({
61+
transform: (value) => {
62+
if (value === 'routed') return NgtRoutedScene;
63+
return value;
64+
},
65+
});
66+
gl = input<NgtGLOptions>();
67+
size = input<NgtSize>();
68+
shadows = input(false, {
69+
transform: (value) => {
70+
if (value === '') return booleanAttribute(value);
71+
return value as NonNullable<NgtCanvasOptions['shadows']>;
72+
},
73+
});
74+
legacy = input(false, { transform: booleanAttribute });
75+
linear = input(false, { transform: booleanAttribute });
76+
flat = input(false, { transform: booleanAttribute });
77+
orthographic = input(false, { transform: booleanAttribute });
78+
frameloop = input<NonNullable<NgtCanvasOptions['frameloop']>>('always');
79+
performance = input<Partial<Omit<NgtPerformance, 'regress'>>>();
80+
dpr = input<NgtDpr>([1, 2]);
81+
raycaster = input<Partial<Raycaster>>();
82+
scene = input<Scene | Partial<Scene>>();
83+
camera = input<NonNullable<NgtCanvasOptions['camera']>>();
84+
lookAt = input<Vector3 | Parameters<Vector3['set']>>();
85+
created = output<NgtState>();
86+
87+
private store = injectStore();
88+
private initRoot = injectCanvasRootInitializer();
89+
private injector = inject(Injector);
90+
private environmentInjector = inject(EnvironmentInjector);
91+
private destroyRef = inject(DestroyRef);
92+
private zone = inject(NgZone);
93+
94+
private canvasViewContainerRef = viewChild.required('canvas', { read: ViewContainerRef });
95+
96+
private configurator?: NgtCanvasConfigurator;
97+
private glEnvironmentInjector?: EnvironmentInjector;
98+
private glRef?: ComponentRef<any>;
99+
100+
canvasElement = signal<Canvas | null>(null);
101+
102+
constructor() {
103+
const autoEffect = injectAutoEffect();
104+
105+
afterNextRender(() => {
106+
autoEffect(() => {
107+
const canvas = this.canvasElement();
108+
if (!canvas) return;
109+
110+
const dpr = makeDpr(untracked(this.dpr), window);
111+
const canvasWidth = canvas.clientWidth * dpr;
112+
const canvasHeight = canvas.clientHeight * dpr;
113+
Object.assign(canvas, { width: canvasWidth, height: canvasHeight });
114+
115+
const context = canvas.getContext('webgl2');
116+
const gl = new WebGLRenderer({
117+
context: context as unknown as WebGLRenderingContext,
118+
powerPreference: 'high-performance',
119+
antialias: true,
120+
alpha: true,
121+
...untracked(this.gl),
122+
});
123+
124+
this.zone.runOutsideAngular(() => {
125+
this.configurator = this.initRoot(canvas as unknown as HTMLCanvasElement);
126+
this.configurator.configure({
127+
gl,
128+
size: { width: canvasWidth, height: canvasHeight, top: 0, left: 0 },
129+
shadows: untracked(this.shadows),
130+
legacy: untracked(this.legacy),
131+
linear: untracked(this.linear),
132+
flat: untracked(this.flat),
133+
orthographic: untracked(this.orthographic),
134+
frameloop: untracked(this.frameloop),
135+
performance: untracked(this.performance),
136+
dpr: untracked(this.dpr),
137+
raycaster: untracked(this.raycaster),
138+
scene: untracked(this.scene),
139+
camera: untracked(this.camera),
140+
lookAt: untracked(this.lookAt),
141+
});
142+
untracked(this.noZoneRender.bind(this));
143+
});
144+
});
145+
});
146+
147+
this.destroyRef.onDestroy(() => {
148+
this.glRef?.destroy();
149+
this.glEnvironmentInjector?.destroy();
150+
this.configurator?.destroy();
151+
});
152+
}
153+
154+
private noZoneRender() {
155+
// NOTE: destroy previous instances if existed
156+
this.glEnvironmentInjector?.destroy();
157+
this.glRef?.destroy();
158+
159+
// NOTE: Flag the canvas active, rendering will now begin
160+
this.store.update((state) => ({ internal: { ...state.internal, active: true } }));
161+
162+
// emit created event if observed
163+
this.created.emit(this.store.snapshot);
164+
165+
this.glEnvironmentInjector = createEnvironmentInjector(
166+
[{ provide: DOCUMENT, useValue: document }, provideNgtRenderer(this.store)],
167+
this.environmentInjector,
168+
);
169+
this.glRef = untracked(this.canvasViewContainerRef).createComponent(untracked(this.sceneGraph), {
170+
environmentInjector: this.glEnvironmentInjector,
171+
injector: this.injector,
172+
});
173+
174+
this.glRef.changeDetectorRef.detectChanges();
175+
}
176+
}

libs/core/package.json

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,27 @@
2626
"@angular/core": ">=18.0.0 <19.0.0",
2727
"ngxtension": ">=3.0.0",
2828
"three": ">=0.148.0 <0.168.0",
29-
"node-three-gltf": ">=1.0.0 <2.0.0"
30-
},
29+
"node-three-gltf": ">=1.0.0 <2.0.0",
30+
"@nativescript/angular": ">=18.0.0 <19.0.0",
31+
"@nativescript/canvas": "2.0.0-webgpu.11",
32+
"@nativescript/canvas-three": "2.0.0-webgpu.11",
33+
"@nativescript/canvas-media": "2.0.0-webgpu.11"
34+
},
3135
"peerDependenciesMeta": {
3236
"node-three-gltf": {
3337
"optional": true
38+
},
39+
"@nativescript/angular": {
40+
"optional": true
41+
},
42+
"@nativescript/canvas": {
43+
"optional": true
44+
},
45+
"@nativescript/canvas-three": {
46+
"optional": true
47+
},
48+
"@nativescript/canvas-media": {
49+
"optional": true
3450
}
3551
},
3652
"dependencies": {

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
"@dimforge/rapier3d-compat": "^0.14.0",
121121
"@ionic/angular": "^7.0.0",
122122
"@monogrid/gainmap-js": "^3.0.5",
123+
"@nativescript/angular": "^18.1.1",
123124
"@nativescript/canvas": "2.0.0-webgpu.11",
124125
"@nativescript/canvas-media": "2.0.0-webgpu.11",
125126
"@nativescript/canvas-three": "2.0.0-webgpu.11",

pnpm-lock.yaml

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)