Skip to content

Commit f12cc50

Browse files
committed
add first iteration of NgtTestBed
1 parent 6a5fac8 commit f12cc50

16 files changed

+1193
-180
lines changed

libs/core/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,5 +56,8 @@
5656
"angular-three-rapier"
5757
]
5858
},
59-
"web-types": "./web-types.json"
59+
"web-types": [
60+
"./web-types.json",
61+
"../../node_modules/angular-three/web-types.json"
62+
]
6063
}

libs/core/project.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
"executor": "@nx/jest:jest",
6565
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
6666
"options": {
67-
"jestConfig": "libs/core/jest.config.mts"
67+
"jestConfig": "libs/core/jest.config.ts"
6868
}
6969
},
7070
"lint": {

libs/core/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
export * from './lib/canvas';
2-
export * from './lib/canvas-handler';
32
export * from './lib/directives/args';
4-
export { NgtCamera, NgtComputeFunction, NgtDomEvent, NgtThreeEvent } from './lib/events';
3+
export { NgtCamera, NgtComputeFunction, NgtDomEvent, NgtEventHandlers, NgtThreeEvent } from './lib/events';
54
export * from './lib/html';
65
export * from './lib/instance';
76
export * from './lib/loader';
87
export { addAfterEffect, addEffect, addTail } from './lib/loop';
98
export { NgtPortal, NgtPortalContent } from './lib/portal';
109
export * from './lib/renderer';
10+
export { injectCanvasRootInitializer } from './lib/roots';
1111
export * from './lib/routed-scene';
1212
export * from './lib/store';
1313
export * from './lib/utils/apply-props';

libs/core/src/lib/canvas-handler.ts

Lines changed: 0 additions & 165 deletions
This file was deleted.

libs/core/src/lib/canvas.ts

Lines changed: 160 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,115 @@
11
import {
2+
afterNextRender,
3+
booleanAttribute,
24
ChangeDetectionStrategy,
35
Component,
4-
afterNextRender,
6+
ComponentRef,
7+
computed,
58
createEnvironmentInjector,
9+
DestroyRef,
10+
ElementRef,
11+
EnvironmentInjector,
12+
inject,
13+
Injector,
14+
input,
15+
NgZone,
16+
output,
617
signal,
18+
Type,
719
untracked,
20+
viewChild,
21+
ViewContainerRef,
822
} from '@angular/core';
9-
import { NgxResize, ResizeOptions, ResizeResult, provideResizeOptions } from 'ngxtension/resize';
10-
import { NgtCanvasHandler } from './canvas-handler';
11-
import { NgtDomEvent } from './events';
23+
import { outputFromObservable } from '@angular/core/rxjs-interop';
24+
import { injectAutoEffect } from 'ngxtension/auto-effect';
25+
import { NgxResize, provideResizeOptions, ResizeOptions, ResizeResult } from 'ngxtension/resize';
26+
import {
27+
Camera,
28+
OrthographicCamera,
29+
PerspectiveCamera,
30+
Raycaster,
31+
Scene,
32+
Vector3,
33+
WebGLRenderer,
34+
WebGLRendererParameters,
35+
WebGLShadowMap,
36+
} from 'three';
37+
import { createPointerEvents } from './dom/events';
38+
import { NgtCamera, NgtDomEvent, NgtEventManager } from './events';
1239
import { provideNgtRenderer } from './renderer';
13-
import { provideStore } from './store';
40+
import { injectCanvasRootInitializer, NgtCanvasConfigurator, NgtCanvasElement } from './roots';
41+
import { NgtRoutedScene } from './routed-scene';
42+
import { injectStore, NgtDpr, NgtPerformance, NgtRendererLike, NgtSize, NgtState, provideStore } from './store';
43+
import { NgtObject3DNode } from './three-types';
44+
import { NgtProperties } from './types';
1445
import { is } from './utils/is';
46+
import { NgtSignalStore } from './utils/signal-store';
47+
48+
export type NgtGLOptions =
49+
| NgtRendererLike
50+
| ((canvas: NgtCanvasElement) => NgtRendererLike)
51+
| Partial<NgtProperties<WebGLRenderer> | WebGLRendererParameters>
52+
| undefined;
53+
54+
export interface NgtCanvasOptions {
55+
/** A threejs renderer instance or props that go into the default renderer */
56+
gl?: NgtGLOptions;
57+
/** Dimensions to fit the renderer to. Will measure canvas dimensions if omitted */
58+
size?: NgtSize;
59+
/**
60+
* Enables shadows (by default PCFsoft). Can accept `gl.shadowMap` options for fine-tuning,
61+
* but also strings: 'basic' | 'percentage' | 'soft' | 'variance'.
62+
* @see https://threejs.org/docs/#api/en/renderers/WebGLRenderer.shadowMap
63+
*/
64+
shadows?: boolean | 'basic' | 'percentage' | 'soft' | 'variance' | Partial<WebGLShadowMap>;
65+
/**
66+
* Disables three r139 color management.
67+
* @see https://threejs.org/docs/#manual/en/introduction/Color-management
68+
*/
69+
legacy?: boolean;
70+
/** Switch off automatic sRGB color space and gamma correction */
71+
linear?: boolean;
72+
/** Use `THREE.NoToneMapping` instead of `THREE.ACESFilmicToneMapping` */
73+
flat?: boolean;
74+
/** Creates an orthographic camera */
75+
orthographic?: boolean;
76+
/**
77+
* R3F's render mode. Set to `demand` to only render on state change or `never` to take control.
78+
* @see https://docs.pmnd.rs/react-three-fiber/advanced/scaling-performance#on-demand-rendering
79+
*/
80+
frameloop?: 'always' | 'demand' | 'never';
81+
/**
82+
* R3F performance options for adaptive performance.
83+
* @see https://docs.pmnd.rs/react-three-fiber/advanced/scaling-performance#movement-regression
84+
*/
85+
performance?: Partial<Omit<NgtPerformance, 'regress'>>;
86+
/** Target pixel ratio. Can clamp between a range: `[min, max]` */
87+
dpr?: NgtDpr;
88+
/** Props that go into the default raycaster */
89+
raycaster?: Partial<Raycaster>;
90+
/** A `Scene` instance or props that go into the default scene */
91+
scene?: Scene | Partial<Scene>;
92+
/** A `Camera` instance or props that go into the default camera */
93+
camera?: (
94+
| NgtCamera
95+
| Partial<
96+
NgtObject3DNode<Camera, typeof Camera> &
97+
NgtObject3DNode<PerspectiveCamera, typeof PerspectiveCamera> &
98+
NgtObject3DNode<OrthographicCamera, typeof OrthographicCamera>
99+
>
100+
) & {
101+
/** Flags the camera as manual, putting projection into your own hands */
102+
manual?: boolean;
103+
};
104+
/** An R3F event manager to manage elements' pointer events */
105+
events?: (store: NgtSignalStore<NgtState>) => NgtEventManager<HTMLElement>;
106+
/** The target where events are being subscribed to, default: the div that wraps canvas */
107+
eventSource?: HTMLElement | ElementRef<HTMLElement>;
108+
/** The event prefix that is cast into canvas pointer x/y events, default: "offset" */
109+
eventPrefix?: 'offset' | 'client' | 'page' | 'layer' | 'screen';
110+
/** Default coordinate for the camera to look at */
111+
lookAt?: Vector3 | Parameters<Vector3['set']>;
112+
}
15113

16114
@Component({
17115
selector: 'ngt-canvas',
@@ -36,18 +134,73 @@ import { is } from './utils/is';
36134
},
37135
changeDetection: ChangeDetectionStrategy.OnPush,
38136
})
39-
export class NgtCanvas extends NgtCanvasHandler {
137+
export class NgtCanvas {
138+
private store = injectStore();
139+
private initRoot = injectCanvasRootInitializer();
140+
private autoEffect = injectAutoEffect();
141+
142+
private host = inject<ElementRef<HTMLElement>>(ElementRef);
143+
private viewContainerRef = inject(ViewContainerRef);
144+
private zone = inject(NgZone);
145+
private environmentInjector = inject(EnvironmentInjector);
146+
private injector = inject(Injector);
147+
148+
sceneGraph = input.required<Type<any>, Type<any> | 'routed'>({
149+
transform: (value) => {
150+
if (value === 'routed') return NgtRoutedScene;
151+
return value;
152+
},
153+
});
154+
gl = input<NgtGLOptions>();
155+
size = input<NgtSize>();
156+
shadows = input(false, {
157+
transform: (value) => {
158+
if (value === '') return booleanAttribute(value);
159+
return value as NonNullable<NgtCanvasOptions['shadows']>;
160+
},
161+
});
162+
legacy = input(false, { transform: booleanAttribute });
163+
linear = input(false, { transform: booleanAttribute });
164+
flat = input(false, { transform: booleanAttribute });
165+
orthographic = input(false, { transform: booleanAttribute });
166+
frameloop = input<NonNullable<NgtCanvasOptions['frameloop']>>('always');
167+
performance = input<Partial<Omit<NgtPerformance, 'regress'>>>();
168+
dpr = input<NgtDpr>([1, 2]);
169+
raycaster = input<Partial<Raycaster>>();
170+
scene = input<Scene | Partial<Scene>>();
171+
camera = input<NonNullable<NgtCanvasOptions['camera']>>();
172+
events = input(createPointerEvents);
173+
eventSource = input<HTMLElement | ElementRef<HTMLElement>>();
174+
eventPrefix = input<NonNullable<NgtCanvasOptions['eventPrefix']>>('offset');
175+
lookAt = input<Vector3 | Parameters<Vector3['set']>>();
176+
created = output<NgtState>();
177+
pointerMissed = outputFromObservable(this.store.get('pointerMissed$'));
178+
179+
glCanvas = viewChild.required<ElementRef<HTMLCanvasElement>>('glCanvas');
180+
glCanvasViewContainerRef = viewChild.required('glCanvas', { read: ViewContainerRef });
181+
182+
protected hbPointerEvents = computed(() => (this.eventSource() ? 'none' : 'auto'));
183+
40184
// NOTE: this signal is updated outside of Zone
41185
protected resizeResult = signal<ResizeResult>({} as ResizeResult, { equal: Object.is });
42186

187+
private configurator?: NgtCanvasConfigurator;
188+
private glEnvironmentInjector?: EnvironmentInjector;
189+
private glRef?: ComponentRef<unknown>;
190+
43191
constructor() {
44-
super();
45192
afterNextRender(() => {
46193
this.zone.runOutsideAngular(() => {
47194
this.configurator = this.initRoot(this.glCanvas().nativeElement);
48195
this.noZoneResizeEffect();
49196
});
50197
});
198+
199+
inject(DestroyRef).onDestroy(() => {
200+
this.glRef?.destroy();
201+
this.glEnvironmentInjector?.destroy();
202+
this.configurator?.destroy();
203+
});
51204
}
52205

53206
private noZoneResizeEffect() {

0 commit comments

Comments
 (0)