1
1
import {
2
+ afterNextRender ,
3
+ booleanAttribute ,
2
4
ChangeDetectionStrategy ,
3
5
Component ,
4
- afterNextRender ,
6
+ ComponentRef ,
7
+ computed ,
5
8
createEnvironmentInjector ,
9
+ DestroyRef ,
10
+ ElementRef ,
11
+ EnvironmentInjector ,
12
+ inject ,
13
+ Injector ,
14
+ input ,
15
+ NgZone ,
16
+ output ,
6
17
signal ,
18
+ Type ,
7
19
untracked ,
20
+ viewChild ,
21
+ ViewContainerRef ,
8
22
} 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' ;
12
39
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' ;
14
45
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
+ }
15
113
16
114
@Component ( {
17
115
selector : 'ngt-canvas' ,
@@ -36,18 +134,73 @@ import { is } from './utils/is';
36
134
} ,
37
135
changeDetection : ChangeDetectionStrategy . OnPush ,
38
136
} )
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
+
40
184
// NOTE: this signal is updated outside of Zone
41
185
protected resizeResult = signal < ResizeResult > ( { } as ResizeResult , { equal : Object . is } ) ;
42
186
187
+ private configurator ?: NgtCanvasConfigurator ;
188
+ private glEnvironmentInjector ?: EnvironmentInjector ;
189
+ private glRef ?: ComponentRef < unknown > ;
190
+
43
191
constructor ( ) {
44
- super ( ) ;
45
192
afterNextRender ( ( ) => {
46
193
this . zone . runOutsideAngular ( ( ) => {
47
194
this . configurator = this . initRoot ( this . glCanvas ( ) . nativeElement ) ;
48
195
this . noZoneResizeEffect ( ) ;
49
196
} ) ;
50
197
} ) ;
198
+
199
+ inject ( DestroyRef ) . onDestroy ( ( ) => {
200
+ this . glRef ?. destroy ( ) ;
201
+ this . glEnvironmentInjector ?. destroy ( ) ;
202
+ this . configurator ?. destroy ( ) ;
203
+ } ) ;
51
204
}
52
205
53
206
private noZoneResizeEffect ( ) {
0 commit comments