Browse Source

Refine the PointerTracker

customisations
alemart 10 months ago
parent
commit
144f4b06a1

+ 1
- 1
docs/api/trackable-pointer.md View File

26
 * `"stationary"`: the user did not move the pointer in this frame
26
 * `"stationary"`: the user did not move the pointer in this frame
27
 * `"moved"`: the user moved the pointer in this frame
27
 * `"moved"`: the user moved the pointer in this frame
28
 * `"ended"`: the tracking ended in this frame (e.g., a finger has just been lifted from the screen)
28
 * `"ended"`: the tracking ended in this frame (e.g., a finger has just been lifted from the screen)
29
-* `"canceled"`: the tracking was canceled in this frame (e.g., the screen orientation of the device has just been changed)
29
+* `"canceled"`: the tracking was canceled in this frame (e.g., the page has just lost focus)
30
 
30
 
31
 ### position
31
 ### position
32
 
32
 

+ 14
- 0
src/sources/pointer-source.ts View File

48
         this._queue = [];
48
         this._queue = [];
49
         this._viewport = null;
49
         this._viewport = null;
50
         this._onPointerEvent = this._onPointerEvent.bind(this);
50
         this._onPointerEvent = this._onPointerEvent.bind(this);
51
+        this._cancelEvent = this._cancelEvent.bind(this);
51
     }
52
     }
52
 
53
 
53
     /**
54
     /**
143
     private _onPointerEvent(event: PointerEvent): void
144
     private _onPointerEvent(event: PointerEvent): void
144
     {
145
     {
145
         this._queue.push(event);
146
         this._queue.push(event);
147
+        event.preventDefault();
148
+    }
149
+
150
+    /**
151
+     * Cancel event
152
+     * @param event
153
+     */
154
+    private _cancelEvent(event: Event): void
155
+    {
156
+        if(event.cancelable)
157
+            event.preventDefault();
146
     }
158
     }
147
 
159
 
148
     /**
160
     /**
157
         canvas.addEventListener('pointercancel', this._onPointerEvent);
169
         canvas.addEventListener('pointercancel', this._onPointerEvent);
158
         canvas.addEventListener('pointerleave', this._onPointerEvent);
170
         canvas.addEventListener('pointerleave', this._onPointerEvent);
159
         canvas.addEventListener('pointerenter', this._onPointerEvent);
171
         canvas.addEventListener('pointerenter', this._onPointerEvent);
172
+        canvas.addEventListener('touchstart', this._cancelEvent, { passive: false });
160
     }
173
     }
161
 
174
 
162
     /**
175
     /**
165
      */
178
      */
166
     private _removeEventListeners(canvas: HTMLCanvasElement): void
179
     private _removeEventListeners(canvas: HTMLCanvasElement): void
167
     {
180
     {
181
+        canvas.removeEventListener('touchstart', this._cancelEvent);
168
         canvas.removeEventListener('pointerenter', this._onPointerEvent);
182
         canvas.removeEventListener('pointerenter', this._onPointerEvent);
169
         canvas.removeEventListener('pointerleave', this._onPointerEvent);
183
         canvas.removeEventListener('pointerleave', this._onPointerEvent);
170
         canvas.removeEventListener('pointercancel', this._onPointerEvent);
184
         canvas.removeEventListener('pointercancel', this._onPointerEvent);

+ 100
- 6
src/trackers/pointer-tracker/pointer-tracker.ts View File

88
     /** time of the previous update */
88
     /** time of the previous update */
89
     private _previousUpdateTime: DOMHighResTimeStamp;
89
     private _previousUpdateTime: DOMHighResTimeStamp;
90
 
90
 
91
+    /** helper flag */
92
+    private _wantToReset: boolean;
93
+
91
 
94
 
92
 
95
 
93
     /**
96
     /**
101
         this._newPointers = new Map();
104
         this._newPointers = new Map();
102
         this._previousOutput = this._generateOutput();
105
         this._previousOutput = this._generateOutput();
103
         this._previousUpdateTime = Number.POSITIVE_INFINITY;
106
         this._previousUpdateTime = Number.POSITIVE_INFINITY;
107
+        this._wantToReset = false;
108
+        this._resetInTheNextUpdate = this._resetInTheNextUpdate.bind(this);
104
     }
109
     }
105
 
110
 
106
     /**
111
     /**
138
         // link the pointer source to the viewport
143
         // link the pointer source to the viewport
139
         this._source._setViewport(this._viewport);
144
         this._source._setViewport(this._viewport);
140
 
145
 
146
+        // reset trackables
147
+        document.addEventListener('visibilitychange', this._resetInTheNextUpdate);
148
+
141
         // done!
149
         // done!
142
         return Speedy.Promise.resolve();
150
         return Speedy.Promise.resolve();
143
     }
151
     }
154
         this._activePointers.clear();
162
         this._activePointers.clear();
155
         this._newPointers.clear();
163
         this._newPointers.clear();
156
 
164
 
165
+        document.removeEventListener('visibilitychange', this._resetInTheNextUpdate);
166
+
157
         return Speedy.Promise.resolve();
167
         return Speedy.Promise.resolve();
158
     }
168
     }
159
 
169
 
172
         const inverseDeltaTime = (deltaTime > 1e-5) ? 1 / deltaTime : 60; // 1/dt = 1 / (1/60) with 60 fps
182
         const inverseDeltaTime = (deltaTime > 1e-5) ? 1 / deltaTime : 60; // 1/dt = 1 / (1/60) with 60 fps
173
 
183
 
174
         // remove inactive trackables from the previous frame (update cycle)
184
         // remove inactive trackables from the previous frame (update cycle)
175
-        const inactiveTrackables = this._findUnwantedTrackables();
185
+        const inactiveTrackables = this._findInactiveTrackables();
176
         for(let i = inactiveTrackables.length - 1; i >= 0; i--)
186
         for(let i = inactiveTrackables.length - 1; i >= 0; i--)
177
             this._activePointers.delete(inactiveTrackables[i].id);
187
             this._activePointers.delete(inactiveTrackables[i].id);
178
 
188
 
179
         // make all active trackables stationary
189
         // make all active trackables stationary
180
-        this._activePointers.forEach((trackable, id) => {
181
-            this._activePointers.set(id, Object.assign({}, trackable, {
182
-                phase: 'stationary'
183
-            }));
190
+        this._updateAllTrackables({
191
+            phase: 'stationary',
192
+            velocity: Vector2.Zero(),
193
+            deltaPosition: Vector2.Zero()
184
         });
194
         });
185
 
195
 
196
+        // want to reset?
197
+        if(this._wantToReset) {
198
+            this._reset();
199
+            this._wantToReset = false;
200
+        }
201
+
186
         // consume events
202
         // consume events
187
         let event: Nullable<PointerEvent>;
203
         let event: Nullable<PointerEvent>;
188
         while((event = this._source!._consume()) !== null) {
204
         while((event = this._source!._consume()) !== null) {
238
                 }
254
                 }
239
             }
255
             }
240
 
256
 
257
+            // discard previously canceled pointers (e.g., with a visibilitychange event)
258
+            if(previous?.phase == 'canceled')
259
+                continue;
260
+
241
             // more special rules
261
             // more special rules
242
             switch(event.type) {
262
             switch(event.type) {
243
                 case 'pointermove':
263
                 case 'pointermove':
249
                     if(event.buttons == 0 || previous?.phase == 'began' || current?.phase == 'began')
269
                     if(event.buttons == 0 || previous?.phase == 'began' || current?.phase == 'began')
250
                         continue;
270
                         continue;
251
                     break;
271
                     break;
272
+
273
+                case 'pointercancel': // purge everything
274
+                    this._reset();
275
+                    this._newPointers.clear();
276
+                    continue;
252
             }
277
             }
253
 
278
 
254
             // determine the current position
279
             // determine the current position
287
         // update trackables
312
         // update trackables
288
         this._newPointers.forEach((trackable, id) => this._activePointers.set(id, trackable));
313
         this._newPointers.forEach((trackable, id) => this._activePointers.set(id, trackable));
289
         this._newPointers.clear();
314
         this._newPointers.clear();
315
+        this._advanceAllStationaryTrackables(deltaTime);
290
 
316
 
291
         // generate output
317
         // generate output
292
         this._previousOutput = this._generateOutput();
318
         this._previousOutput = this._generateOutput();
337
     }
363
     }
338
 
364
 
339
     /**
365
     /**
366
+     * Update all active pointers
367
+     * @param fields
368
+     */
369
+    private _updateAllTrackables(fields: Partial<TrackablePointer>): void
370
+    {
371
+        this._activePointers.forEach((trackable, id) => {
372
+            this._activePointers.set(id, Object.assign({}, trackable, fields));
373
+        });
374
+    }
375
+
376
+    /**
377
+     * Advance the elapsed time of all stationary pointers
378
+     * @param deltaTime
379
+     */
380
+    private _advanceAllStationaryTrackables(deltaTime: number): void
381
+    {
382
+        this._activePointers.forEach((trackable, id) => {
383
+            if(trackable.phase == 'stationary') {
384
+                (trackable as any).elapsedTime += deltaTime;
385
+                /*
386
+                this._activePointers.set(id, Object.assign({}, trackable, {
387
+                    elapsedTime: trackable.elapsedTime + deltaTime
388
+                }));
389
+                */
390
+            }
391
+        });
392
+    }
393
+
394
+    /**
395
+     * Cancel all active pointers and consume all events
396
+     * @param deltaTime
397
+     */
398
+    private _reset(): void
399
+    {
400
+        // cancel all active pointers
401
+        this._updateAllTrackables({
402
+            phase: 'canceled',
403
+            deltaPosition: Vector2.Zero(),
404
+            velocity: Vector2.Zero(),
405
+        });
406
+
407
+        // consume all events
408
+        while(this._source!._consume() !== null);
409
+    }
410
+
411
+    /**
412
+     * Reset in the next update of the tracker
413
+     */
414
+    private _resetInTheNextUpdate(): void
415
+    {
416
+        this._wantToReset = true;
417
+    }
418
+
419
+    /**
340
      * As a convenience, let's make sure that a primary pointer, if any exists,
420
      * As a convenience, let's make sure that a primary pointer, if any exists,
341
      * is at the beginning of the trackables array
421
      * is at the beginning of the trackables array
342
      * @param trackables
422
      * @param trackables
344
      */
424
      */
345
     private _sortTrackables(trackables: TrackablePointer[]): TrackablePointer[]
425
     private _sortTrackables(trackables: TrackablePointer[]): TrackablePointer[]
346
     {
426
     {
427
+        /*
428
+
429
+        Note: the browser may not report a new unique pointer (phase: "began")
430
+        as primary. This logic makes trackables[0] primary, or sort of primary.
431
+
432
+        Behavior on Chrome 130 on Android: when moving multiple touch points,
433
+        remove focus from the browser. Touch points will be canceled as
434
+        expected. When touching the screen again with a single finger, the
435
+        (only one) registered pointer will not be primary. That's undesirable.
436
+        Touching the screen again with multiple fingers (none will be primary),
437
+        and then releasing them, will restore the desired behavior.
438
+
439
+        */
440
+
347
         // nothing to do
441
         // nothing to do
348
         if(trackables.length <= 1 || trackables[0].isPrimary)
442
         if(trackables.length <= 1 || trackables[0].isPrimary)
349
             return trackables;
443
             return trackables;
366
      * Find trackables to remove
460
      * Find trackables to remove
367
      * @returns a list of trackables to remove
461
      * @returns a list of trackables to remove
368
      */
462
      */
369
-    private _findUnwantedTrackables(): TrackablePointer[]
463
+    private _findInactiveTrackables(): TrackablePointer[]
370
     {
464
     {
371
         const trackables: TrackablePointer[] = [];
465
         const trackables: TrackablePointer[] = [];
372
 
466
 

+ 1
- 1
src/trackers/pointer-tracker/trackable-pointer.ts View File

29
  * - "stationary": the user did not move the pointer in this frame
29
  * - "stationary": the user did not move the pointer in this frame
30
  * - "moved": the user moved the pointer in this frame
30
  * - "moved": the user moved the pointer in this frame
31
  * - "ended": the tracking ended in this frame (e.g., a finger has just been lifted from the screen)
31
  * - "ended": the tracking ended in this frame (e.g., a finger has just been lifted from the screen)
32
- * - "canceled": the tracking was canceled in this frame (e.g., the screen orientation of the device has just been changed)
32
+ * - "canceled": the tracking was canceled in this frame (e.g., the page has just lost focus)
33
  */
33
  */
34
 export type TrackablePointerPhase = 'began' | 'moved' | 'stationary' | 'ended' | 'canceled';
34
 export type TrackablePointerPhase = 'began' | 'moved' | 'stationary' | 'ended' | 'canceled';
35
 
35
 

Loading…
Cancel
Save