1
- import {
2
- ChangeDetectionStrategy ,
3
- Component ,
4
- Signal ,
5
- afterNextRender ,
6
- effect ,
7
- input ,
8
- signal ,
9
- untracked ,
10
- } from '@angular/core' ;
1
+ import { Directive , Injector , afterNextRender , effect , inject , input , signal , untracked } from '@angular/core' ;
11
2
import {
12
3
CannonWorkerAPI ,
13
4
CannonWorkerProps ,
@@ -84,15 +75,6 @@ export interface NgtcPhysicsOptions extends CannonWorkerProps {
84
75
stepSize ?: number ;
85
76
}
86
77
87
- export interface NgtcPhysicsApi {
88
- bodies : { [ uuid : string ] : number } ;
89
- events : NgtcCannonEvents ;
90
- refs : Refs ;
91
- scaleOverrides : ScaleOverrides ;
92
- subscriptions : Subscriptions ;
93
- worker : Signal < CannonWorkerAPI > ;
94
- }
95
-
96
78
const defaultOptions : NgtcPhysicsOptions = {
97
79
allowSleep : false ,
98
80
axisIndex : 0 ,
@@ -117,14 +99,7 @@ type NgtsPhysicsUpdatableOptions = Extract<
117
99
'gravity' | 'iterations' | 'tolerance' | 'broadphase' | 'axisIndex'
118
100
> ;
119
101
120
- @Component ( {
121
- selector : 'ngtc-physics' ,
122
- standalone : true ,
123
- template : `
124
- <ng-content />
125
- ` ,
126
- changeDetection : ChangeDetectionStrategy . OnPush ,
127
- } )
102
+ @Directive ( { selector : 'ngtc-physics' , standalone : true } )
128
103
export class NgtcPhysics {
129
104
private store = injectStore ( ) ;
130
105
@@ -138,38 +113,48 @@ export class NgtcPhysics {
138
113
139
114
private invalidate = this . store . select ( 'invalidate' ) ;
140
115
// @ts -expect-error - worker is not nullable, and we don't want to use ! operator.
141
- private worker = signal < CannonWorkerAPI > ( null ) ;
116
+ private cannonWorker = signal < CannonWorkerAPI > ( null ) ;
142
117
143
- api : NgtcPhysicsApi = {
144
- bodies : { } ,
145
- events : { } ,
146
- refs : { } ,
147
- scaleOverrides : { } ,
148
- subscriptions : { } ,
149
- worker : this . worker . asReadonly ( ) ,
150
- } ;
118
+ bodies : { [ uuid : string ] : number } = { } ;
119
+ events : NgtcCannonEvents = { } ;
120
+ refs : Refs = { } ;
121
+ scaleOverrides : ScaleOverrides = { } ;
122
+ subscriptions : Subscriptions = { } ;
123
+ worker = this . cannonWorker . asReadonly ( ) ;
151
124
152
125
constructor ( ) {
126
+ const injector = inject ( Injector ) ;
127
+
128
+ // NOTE: set new cannonworker in afterNextRender
129
+ // - so inputs are resolved
130
+ // - so the worker is instantiated only once
131
+ // - effects are started after worker is instantiated
153
132
afterNextRender ( ( ) => {
154
- this . worker . set ( new CannonWorkerAPI ( this . options ( ) ) ) ;
155
- } ) ;
133
+ this . cannonWorker . set ( new CannonWorkerAPI ( this . options ( ) ) ) ;
156
134
157
- effect ( ( onCleanup ) => {
158
- const cleanup = this . connectWorkerEffect ( ) ;
159
- onCleanup ( ( ) => cleanup ?.( ) ) ;
160
- } ) ;
135
+ effect (
136
+ ( onCleanup ) => {
137
+ const cleanup = this . connectWorkerEffect ( ) ;
138
+ onCleanup ( ( ) => cleanup ?.( ) ) ;
139
+ } ,
140
+ { injector } ,
141
+ ) ;
161
142
162
- effect ( ( ) => {
163
- this . updateWorkerStateEffect ( 'axisIndex' , this . axisIndex ) ;
164
- this . updateWorkerStateEffect ( 'broadphase' , this . broadphase ) ;
165
- this . updateWorkerStateEffect ( 'gravity' , this . gravity ) ;
166
- this . updateWorkerStateEffect ( 'iterations' , this . iterations ) ;
167
- this . updateWorkerStateEffect ( 'tolerance' , this . tolerance ) ;
143
+ effect (
144
+ ( ) => {
145
+ this . updateWorkerStateEffect ( 'axisIndex' , this . axisIndex ) ;
146
+ this . updateWorkerStateEffect ( 'broadphase' , this . broadphase ) ;
147
+ this . updateWorkerStateEffect ( 'gravity' , this . gravity ) ;
148
+ this . updateWorkerStateEffect ( 'iterations' , this . iterations ) ;
149
+ this . updateWorkerStateEffect ( 'tolerance' , this . tolerance ) ;
150
+ } ,
151
+ { injector } ,
152
+ ) ;
168
153
} ) ;
169
154
170
155
let timeSinceLastCalled = 0 ;
171
156
injectBeforeRender ( ( { delta } ) => {
172
- const [ { isPaused, maxSubSteps, stepSize } , worker ] = [ this . options ( ) , this . worker ( ) ] ;
157
+ const [ { isPaused, maxSubSteps, stepSize } , worker ] = [ this . options ( ) , this . cannonWorker ( ) ] ;
173
158
if ( isPaused || ! worker || stepSize == null ) return ;
174
159
timeSinceLastCalled += delta ;
175
160
worker . step ( { maxSubSteps, stepSize, timeSinceLastCalled } ) ;
@@ -178,7 +163,7 @@ export class NgtcPhysics {
178
163
}
179
164
180
165
private connectWorkerEffect ( ) {
181
- const worker = this . worker ( ) as NgtcCannonWorker ;
166
+ const worker = this . cannonWorker ( ) as NgtcCannonWorker ;
182
167
if ( ! worker ) return ;
183
168
184
169
worker . connect ( ) ;
@@ -200,29 +185,29 @@ export class NgtcPhysics {
200
185
key : TUpdatableKey ,
201
186
option : ( ) => NgtcPhysicsOptions [ TUpdatableKey ] ,
202
187
) {
203
- const worker = this . worker ( ) ;
188
+ const worker = this . cannonWorker ( ) ;
204
189
if ( ! worker ) return ;
205
190
Object . assign ( worker , { [ key ] : option ( ) } ) ;
206
191
}
207
192
208
193
private collideHandler ( { body, contact : { bi, bj, ...contactRest } , target, ...rest } : WorkerCollideEvent [ 'data' ] ) {
209
- const { events, refs } = this . api ;
194
+ const { events, refs } = this ;
210
195
const cb = events [ target ] ?. collide ;
211
196
if ( cb ) {
212
197
cb ( { body : refs [ body ] , contact : { bi : refs [ bi ] , bj : refs [ bj ] , ...contactRest } , target : refs [ target ] , ...rest } ) ;
213
198
}
214
199
}
215
200
216
201
private collideBeginHandler ( { bodyA, bodyB } : WorkerCollideBeginEvent [ 'data' ] ) {
217
- const { events, refs } = this . api ;
202
+ const { events, refs } = this ;
218
203
const cbA = events [ bodyA ] ?. collideBegin ;
219
204
if ( cbA ) cbA ( { body : refs [ bodyB ] , op : 'event' , target : refs [ bodyA ] , type : 'collideBegin' } ) ;
220
205
const cbB = events [ bodyB ] ?. collideBegin ;
221
206
if ( cbB ) cbB ( { body : refs [ bodyA ] , op : 'event' , target : refs [ bodyB ] , type : 'collideBegin' } ) ;
222
207
}
223
208
224
209
private collideEndHandler ( { bodyA, bodyB } : WorkerCollideEndEvent [ 'data' ] ) {
225
- const { events, refs } = this . api ;
210
+ const { events, refs } = this ;
226
211
const cbA = events [ bodyA ] ?. collideEnd ;
227
212
if ( cbA ) cbA ( { body : refs [ bodyB ] , op : 'event' , target : refs [ bodyA ] , type : 'collideEnd' } ) ;
228
213
const cbB = events [ bodyB ] ?. collideEnd ;
@@ -238,7 +223,7 @@ export class NgtcPhysics {
238
223
} : WorkerFrameMessage [ 'data' ] ) {
239
224
const [ { shouldInvalidate } , { bodies, subscriptions, refs, scaleOverrides } , invalidate ] = [
240
225
untracked ( this . options ) ,
241
- this . api ,
226
+ this ,
242
227
this . invalidate ( ) ,
243
228
] ;
244
229
for ( let i = 0 ; i < uuids . length ; i ++ ) {
@@ -270,7 +255,7 @@ export class NgtcPhysics {
270
255
}
271
256
272
257
private rayhitHandler ( { body, ray : { uuid, ...rayRest } , ...rest } : WorkerRayhitEvent [ 'data' ] ) {
273
- const { events, refs } = this . api ;
258
+ const { events, refs } = this ;
274
259
const cb = events [ uuid ] ?. rayhit ;
275
260
if ( cb ) cb ( { body : body ? refs [ body ] : null , ray : { uuid, ...rayRest } , ...rest } ) ;
276
261
}
0 commit comments