Skip to content

Commit

Permalink
feat(core): first nativescript entry point release
Browse files Browse the repository at this point in the history
  • Loading branch information
nartc committed Aug 17, 2024
1 parent af5e8c6 commit 8864e0b
Show file tree
Hide file tree
Showing 6 changed files with 214 additions and 2 deletions.
2 changes: 2 additions & 0 deletions libs/core/nativescript/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# angular-three/nativescript

Secondary entry point of `angular-three`. It can be used by importing from `angular-three/nativescript`.

Depends on `@nativescript/canvas`, `@nativescript/canvas-three`, and `@nativescript/canvas-media`.
1 change: 1 addition & 0 deletions libs/core/nativescript/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './lib/canvas';
176 changes: 176 additions & 0 deletions libs/core/nativescript/src/lib/canvas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import '@nativescript/canvas-three';

import { DOCUMENT } from '@angular/common';
import {
afterNextRender,
booleanAttribute,
ChangeDetectionStrategy,
Component,
ComponentRef,
createEnvironmentInjector,
DestroyRef,
EnvironmentInjector,
inject,
Injector,
input,
NgZone,
NO_ERRORS_SCHEMA,
output,
signal,
Type,
untracked,
viewChild,
ViewContainerRef,
} from '@angular/core';
import { registerElement } from '@nativescript/angular';
import { Canvas } from '@nativescript/canvas';
import {
injectCanvasRootInitializer,
injectStore,
makeDpr,
NgtCanvasConfigurator,
NgtCanvasOptions,
NgtDpr,
NgtGLOptions,
NgtPerformance,
NgtRoutedScene,
NgtSize,
NgtState,
provideNgtRenderer,
provideStore,
} from 'angular-three';
import { injectAutoEffect } from 'ngxtension/auto-effect';
import { Raycaster, Scene, Vector3, WebGLRenderer } from 'three';

registerElement('Canvas', () => Canvas);

@Component({
selector: 'NgtCanvas',
standalone: true,
template: `
<GridLayout>
<Canvas #canvas style="width: 100%; height: auto" (ready)="canvasElement.set($event.object)"></Canvas>
</GridLayout>
`,
providers: [{ provide: DOCUMENT, useValue: document }, provideStore()],
schemas: [NO_ERRORS_SCHEMA],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class NgtCanvasNative {
sceneGraph = input.required<Type<any>, Type<any> | 'routed'>({
transform: (value) => {
if (value === 'routed') return NgtRoutedScene;
return value;
},
});
gl = input<NgtGLOptions>();
size = input<NgtSize>();
shadows = input(false, {
transform: (value) => {
if (value === '') return booleanAttribute(value);
return value as NonNullable<NgtCanvasOptions['shadows']>;
},
});
legacy = input(false, { transform: booleanAttribute });
linear = input(false, { transform: booleanAttribute });
flat = input(false, { transform: booleanAttribute });
orthographic = input(false, { transform: booleanAttribute });
frameloop = input<NonNullable<NgtCanvasOptions['frameloop']>>('always');
performance = input<Partial<Omit<NgtPerformance, 'regress'>>>();
dpr = input<NgtDpr>([1, 2]);
raycaster = input<Partial<Raycaster>>();
scene = input<Scene | Partial<Scene>>();
camera = input<NonNullable<NgtCanvasOptions['camera']>>();
lookAt = input<Vector3 | Parameters<Vector3['set']>>();
created = output<NgtState>();

private store = injectStore();
private initRoot = injectCanvasRootInitializer();
private injector = inject(Injector);
private environmentInjector = inject(EnvironmentInjector);
private destroyRef = inject(DestroyRef);
private zone = inject(NgZone);

private canvasViewContainerRef = viewChild.required('canvas', { read: ViewContainerRef });

private configurator?: NgtCanvasConfigurator;
private glEnvironmentInjector?: EnvironmentInjector;
private glRef?: ComponentRef<any>;

canvasElement = signal<Canvas | null>(null);

constructor() {
const autoEffect = injectAutoEffect();

afterNextRender(() => {
autoEffect(() => {
const canvas = this.canvasElement();
if (!canvas) return;

const dpr = makeDpr(untracked(this.dpr), window);
const canvasWidth = canvas.clientWidth * dpr;
const canvasHeight = canvas.clientHeight * dpr;
Object.assign(canvas, { width: canvasWidth, height: canvasHeight });

const context = canvas.getContext('webgl2');
const gl = new WebGLRenderer({
context: context as unknown as WebGLRenderingContext,
powerPreference: 'high-performance',
antialias: true,
alpha: true,
...untracked(this.gl),
});

this.zone.runOutsideAngular(() => {
this.configurator = this.initRoot(canvas as unknown as HTMLCanvasElement);
this.configurator.configure({
gl,
size: { width: canvasWidth, height: canvasHeight, top: 0, left: 0 },
shadows: untracked(this.shadows),
legacy: untracked(this.legacy),
linear: untracked(this.linear),
flat: untracked(this.flat),
orthographic: untracked(this.orthographic),
frameloop: untracked(this.frameloop),
performance: untracked(this.performance),
dpr: untracked(this.dpr),
raycaster: untracked(this.raycaster),
scene: untracked(this.scene),
camera: untracked(this.camera),
lookAt: untracked(this.lookAt),
});
untracked(this.noZoneRender.bind(this));
});
});
});

this.destroyRef.onDestroy(() => {
this.glRef?.destroy();
this.glEnvironmentInjector?.destroy();
this.configurator?.destroy();
});
}

private noZoneRender() {
// NOTE: destroy previous instances if existed
this.glEnvironmentInjector?.destroy();
this.glRef?.destroy();

// NOTE: Flag the canvas active, rendering will now begin
this.store.update((state) => ({ internal: { ...state.internal, active: true } }));

// emit created event if observed
this.created.emit(this.store.snapshot);

this.glEnvironmentInjector = createEnvironmentInjector(
[{ provide: DOCUMENT, useValue: document }, provideNgtRenderer(this.store)],
this.environmentInjector,
);
this.glRef = untracked(this.canvasViewContainerRef).createComponent(untracked(this.sceneGraph), {
environmentInjector: this.glEnvironmentInjector,
injector: this.injector,
});

this.glRef.changeDetectorRef.detectChanges();
}
}
20 changes: 18 additions & 2 deletions libs/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,27 @@
"@angular/core": ">=18.0.0 <19.0.0",
"ngxtension": ">=3.0.0",
"three": ">=0.148.0 <0.168.0",
"node-three-gltf": ">=1.0.0 <2.0.0"
},
"node-three-gltf": ">=1.0.0 <2.0.0",
"@nativescript/angular": ">=18.0.0 <19.0.0",
"@nativescript/canvas": "2.0.0-webgpu.11",
"@nativescript/canvas-three": "2.0.0-webgpu.11",
"@nativescript/canvas-media": "2.0.0-webgpu.11"
},
"peerDependenciesMeta": {
"node-three-gltf": {
"optional": true
},
"@nativescript/angular": {
"optional": true
},
"@nativescript/canvas": {
"optional": true
},
"@nativescript/canvas-three": {
"optional": true
},
"@nativescript/canvas-media": {
"optional": true
}
},
"dependencies": {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@
"@dimforge/rapier3d-compat": "^0.14.0",
"@ionic/angular": "^7.0.0",
"@monogrid/gainmap-js": "^3.0.5",
"@nativescript/angular": "^18.1.1",
"@nativescript/canvas": "2.0.0-webgpu.11",
"@nativescript/canvas-media": "2.0.0-webgpu.11",
"@nativescript/canvas-three": "2.0.0-webgpu.11",
Expand Down
16 changes: 16 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 8864e0b

Please sign in to comment.