Skip to content

Commit 6a5fac8

Browse files
committed
break canvas into canvas handler
1 parent 0854e64 commit 6a5fac8

File tree

5 files changed

+173
-159
lines changed

5 files changed

+173
-159
lines changed

libs/core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export * from './lib/canvas';
2+
export * from './lib/canvas-handler';
23
export * from './lib/directives/args';
34
export { NgtCamera, NgtComputeFunction, NgtDomEvent, NgtThreeEvent } from './lib/events';
45
export * from './lib/html';

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

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
import {
2+
booleanAttribute,
3+
ComponentRef,
4+
computed,
5+
DestroyRef,
6+
Directive,
7+
ElementRef,
8+
EnvironmentInjector,
9+
inject,
10+
Injector,
11+
input,
12+
NgZone,
13+
output,
14+
Type,
15+
viewChild,
16+
ViewContainerRef,
17+
} from '@angular/core';
18+
import { outputFromObservable } from '@angular/core/rxjs-interop';
19+
import { injectAutoEffect } from 'ngxtension/auto-effect';
20+
import {
21+
Camera,
22+
OrthographicCamera,
23+
PerspectiveCamera,
24+
Raycaster,
25+
Scene,
26+
Vector3,
27+
WebGLRenderer,
28+
WebGLRendererParameters,
29+
WebGLShadowMap,
30+
} from 'three';
31+
import { createPointerEvents } from './dom/events';
32+
import { NgtCamera, NgtEventManager } from './events';
33+
import { injectCanvasRootInitializer, NgtCanvasConfigurator, NgtCanvasElement } from './roots';
34+
import { NgtRoutedScene } from './routed-scene';
35+
import { injectStore, NgtDpr, NgtPerformance, NgtRendererLike, NgtSize, NgtState } from './store';
36+
import { NgtObject3DNode } from './three-types';
37+
import { NgtProperties } from './types';
38+
import { NgtSignalStore } from './utils/signal-store';
39+
40+
export type NgtGLOptions =
41+
| NgtRendererLike
42+
| ((canvas: NgtCanvasElement) => NgtRendererLike)
43+
| Partial<NgtProperties<WebGLRenderer> | WebGLRendererParameters>
44+
| undefined;
45+
46+
export interface NgtCanvasOptions {
47+
/** A threejs renderer instance or props that go into the default renderer */
48+
gl?: NgtGLOptions;
49+
/** Dimensions to fit the renderer to. Will measure canvas dimensions if omitted */
50+
size?: NgtSize;
51+
/**
52+
* Enables shadows (by default PCFsoft). Can accept `gl.shadowMap` options for fine-tuning,
53+
* but also strings: 'basic' | 'percentage' | 'soft' | 'variance'.
54+
* @see https://threejs.org/docs/#api/en/renderers/WebGLRenderer.shadowMap
55+
*/
56+
shadows?: boolean | 'basic' | 'percentage' | 'soft' | 'variance' | Partial<WebGLShadowMap>;
57+
/**
58+
* Disables three r139 color management.
59+
* @see https://threejs.org/docs/#manual/en/introduction/Color-management
60+
*/
61+
legacy?: boolean;
62+
/** Switch off automatic sRGB color space and gamma correction */
63+
linear?: boolean;
64+
/** Use `THREE.NoToneMapping` instead of `THREE.ACESFilmicToneMapping` */
65+
flat?: boolean;
66+
/** Creates an orthographic camera */
67+
orthographic?: boolean;
68+
/**
69+
* R3F's render mode. Set to `demand` to only render on state change or `never` to take control.
70+
* @see https://docs.pmnd.rs/react-three-fiber/advanced/scaling-performance#on-demand-rendering
71+
*/
72+
frameloop?: 'always' | 'demand' | 'never';
73+
/**
74+
* R3F performance options for adaptive performance.
75+
* @see https://docs.pmnd.rs/react-three-fiber/advanced/scaling-performance#movement-regression
76+
*/
77+
performance?: Partial<Omit<NgtPerformance, 'regress'>>;
78+
/** Target pixel ratio. Can clamp between a range: `[min, max]` */
79+
dpr?: NgtDpr;
80+
/** Props that go into the default raycaster */
81+
raycaster?: Partial<Raycaster>;
82+
/** A `Scene` instance or props that go into the default scene */
83+
scene?: Scene | Partial<Scene>;
84+
/** A `Camera` instance or props that go into the default camera */
85+
camera?: (
86+
| NgtCamera
87+
| Partial<
88+
NgtObject3DNode<Camera, typeof Camera> &
89+
NgtObject3DNode<PerspectiveCamera, typeof PerspectiveCamera> &
90+
NgtObject3DNode<OrthographicCamera, typeof OrthographicCamera>
91+
>
92+
) & {
93+
/** Flags the camera as manual, putting projection into your own hands */
94+
manual?: boolean;
95+
};
96+
/** An R3F event manager to manage elements' pointer events */
97+
events?: (store: NgtSignalStore<NgtState>) => NgtEventManager<HTMLElement>;
98+
/** The target where events are being subscribed to, default: the div that wraps canvas */
99+
eventSource?: HTMLElement | ElementRef<HTMLElement>;
100+
/** The event prefix that is cast into canvas pointer x/y events, default: "offset" */
101+
eventPrefix?: 'offset' | 'client' | 'page' | 'layer' | 'screen';
102+
/** Default coordinate for the camera to look at */
103+
lookAt?: Vector3 | Parameters<Vector3['set']>;
104+
}
105+
106+
@Directive()
107+
export abstract class NgtCanvasHandler {
108+
protected store = injectStore();
109+
protected initRoot = injectCanvasRootInitializer();
110+
protected autoEffect = injectAutoEffect();
111+
112+
protected host = inject<ElementRef<HTMLElement>>(ElementRef);
113+
protected viewContainerRef = inject(ViewContainerRef);
114+
protected zone = inject(NgZone);
115+
protected environmentInjector = inject(EnvironmentInjector);
116+
protected injector = inject(Injector);
117+
118+
sceneGraph = input.required<Type<any>, Type<any> | 'routed'>({
119+
transform: (value) => {
120+
if (value === 'routed') return NgtRoutedScene;
121+
return value;
122+
},
123+
});
124+
gl = input<NgtGLOptions>();
125+
size = input<NgtSize>();
126+
shadows = input(false, {
127+
transform: (value) => {
128+
if (value === '') return booleanAttribute(value);
129+
return value as NonNullable<NgtCanvasOptions['shadows']>;
130+
},
131+
});
132+
legacy = input(false, { transform: booleanAttribute });
133+
linear = input(false, { transform: booleanAttribute });
134+
flat = input(false, { transform: booleanAttribute });
135+
orthographic = input(false, { transform: booleanAttribute });
136+
frameloop = input<NonNullable<NgtCanvasOptions['frameloop']>>('always');
137+
performance = input<Partial<Omit<NgtPerformance, 'regress'>>>();
138+
dpr = input<NgtDpr>([1, 2]);
139+
raycaster = input<Partial<Raycaster>>();
140+
scene = input<Scene | Partial<Scene>>();
141+
camera = input<NonNullable<NgtCanvasOptions['camera']>>();
142+
events = input(createPointerEvents);
143+
eventSource = input<HTMLElement | ElementRef<HTMLElement>>();
144+
eventPrefix = input<NonNullable<NgtCanvasOptions['eventPrefix']>>('offset');
145+
lookAt = input<Vector3 | Parameters<Vector3['set']>>();
146+
created = output<NgtState>();
147+
pointerMissed = outputFromObservable(this.store.get('pointerMissed$'));
148+
149+
glCanvas = viewChild.required<ElementRef<HTMLCanvasElement>>('glCanvas');
150+
glCanvasViewContainerRef = viewChild.required('glCanvas', { read: ViewContainerRef });
151+
152+
protected hbPointerEvents = computed(() => (this.eventSource() ? 'none' : 'auto'));
153+
154+
protected configurator?: NgtCanvasConfigurator;
155+
protected glEnvironmentInjector?: EnvironmentInjector;
156+
protected glRef?: ComponentRef<unknown>;
157+
158+
protected constructor() {
159+
inject(DestroyRef).onDestroy(() => {
160+
this.glRef?.destroy();
161+
this.glEnvironmentInjector?.destroy();
162+
this.configurator?.destroy();
163+
});
164+
}
165+
}

libs/core/src/lib/canvas.ts

Lines changed: 5 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -1,115 +1,17 @@
11
import {
22
ChangeDetectionStrategy,
33
Component,
4-
ComponentRef,
5-
DestroyRef,
6-
ElementRef,
7-
EnvironmentInjector,
8-
Injector,
9-
NgZone,
10-
Type,
11-
ViewContainerRef,
124
afterNextRender,
13-
booleanAttribute,
14-
computed,
155
createEnvironmentInjector,
16-
inject,
17-
input,
18-
output,
196
signal,
207
untracked,
21-
viewChild,
228
} from '@angular/core';
23-
import { outputFromObservable } from '@angular/core/rxjs-interop';
24-
import { injectAutoEffect } from 'ngxtension/auto-effect';
259
import { NgxResize, ResizeOptions, ResizeResult, provideResizeOptions } 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';
10+
import { NgtCanvasHandler } from './canvas-handler';
11+
import { NgtDomEvent } from './events';
3912
import { provideNgtRenderer } from './renderer';
40-
import { NgtCanvasConfigurator, NgtCanvasElement, injectCanvasRootInitializer } from './roots';
41-
import { NgtRoutedScene } from './routed-scene';
42-
import { NgtDpr, NgtPerformance, NgtRendererLike, NgtSize, NgtState, injectStore, provideStore } from './store';
43-
import { NgtObject3DNode } from './three-types';
44-
import { NgtProperties } from './types';
13+
import { provideStore } from './store';
4514
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-
}
11315

11416
@Component({
11517
selector: 'ngt-canvas',
@@ -134,72 +36,18 @@ export interface NgtCanvasOptions {
13436
},
13537
changeDetection: ChangeDetectionStrategy.OnPush,
13638
})
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-
39+
export class NgtCanvas extends NgtCanvasHandler {
18240
// NOTE: this signal is updated outside of Zone
18341
protected resizeResult = signal<ResizeResult>({} as ResizeResult, { equal: Object.is });
184-
protected hbPointerEvents = computed(() => (this.eventSource() ? 'none' : 'auto'));
185-
186-
private configurator?: NgtCanvasConfigurator;
187-
private glEnvironmentInjector?: EnvironmentInjector;
188-
private glRef?: ComponentRef<unknown>;
18942

19043
constructor() {
44+
super();
19145
afterNextRender(() => {
19246
this.zone.runOutsideAngular(() => {
19347
this.configurator = this.initRoot(this.glCanvas().nativeElement);
19448
this.noZoneResizeEffect();
19549
});
19650
});
197-
198-
inject(DestroyRef).onDestroy(() => {
199-
this.glRef?.destroy();
200-
this.glEnvironmentInjector?.destroy();
201-
this.configurator?.destroy();
202-
});
20351
}
20452

20553
private noZoneResizeEffect() {

libs/core/src/lib/roots.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
VSMShadowMap,
1313
Vector3,
1414
} from 'three';
15-
import { NgtCanvasOptions } from './canvas';
15+
import { NgtCanvasOptions } from './canvas-handler';
1616
import { prepare } from './instance';
1717
import { injectLoop } from './loop';
1818
import { NgtSize, NgtState, injectStore } from './store';

libs/core/src/lib/utils/make.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Material, MathUtils, Mesh, Object3D, OrthographicCamera, PerspectiveCamera, WebGLRenderer } from 'three';
2-
import { NgtGLOptions } from '../canvas';
2+
import { NgtGLOptions } from '../canvas-handler';
33
import { NgtIntersection } from '../events';
44
import { NgtCanvasElement } from '../roots';
55
import { NgtDpr, NgtSize } from '../store';

0 commit comments

Comments
 (0)