@codexo/exojs 0.6.6 → 0.6.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/exo.esm.js CHANGED
@@ -10851,6 +10851,89 @@ var ChannelOffset;
10851
10851
  ChannelOffset[ChannelOffset["Pointers"] = 256] = "Pointers";
10852
10852
  ChannelOffset[ChannelOffset["Gamepads"] = 512] = "Gamepads";
10853
10853
  })(ChannelOffset || (ChannelOffset = {}));
10854
+ /** Maximum number of simultaneous tracked pointers (mouse / touch / pen). */
10855
+ const maxPointers = 16;
10856
+ /** Number of channel slots reserved per pointer. 16 pointers × 16 slots = 256 (fills the Pointers category exactly). */
10857
+ const pointerSlotSize = 16;
10858
+ /**
10859
+ * Channel offsets for unified pointer (mouse / touch / pen) state.
10860
+ *
10861
+ * The un-prefixed aliases (Active, X, Y, …) are identical to Slot0Active, Slot0X, Slot0Y, …
10862
+ * and address the primary pointer (slot 0). Use Slot{N}Active / Slot{N}X / Slot{N}Y for
10863
+ * multi-pointer (e.g. pinch) access. Other per-slot channels beyond Active/X/Y are reachable
10864
+ * via arithmetic: `Pointer.X + slotIndex * pointerSlotSize + channelOffset` (Pointer.X = PointerChannel.X).
10865
+ *
10866
+ * @internal — accessed publicly via the `Pointer` class namespace (see Pointer.ts).
10867
+ */
10868
+ var PointerChannel;
10869
+ (function (PointerChannel) {
10870
+ // --- Convenience aliases for the primary pointer (slot 0) ---
10871
+ PointerChannel[PointerChannel["Active"] = 256] = "Active";
10872
+ PointerChannel[PointerChannel["X"] = 257] = "X";
10873
+ PointerChannel[PointerChannel["Y"] = 258] = "Y";
10874
+ PointerChannel[PointerChannel["Pressure"] = 259] = "Pressure";
10875
+ PointerChannel[PointerChannel["Width"] = 260] = "Width";
10876
+ PointerChannel[PointerChannel["Height"] = 261] = "Height";
10877
+ PointerChannel[PointerChannel["Twist"] = 262] = "Twist";
10878
+ PointerChannel[PointerChannel["TiltX"] = 263] = "TiltX";
10879
+ PointerChannel[PointerChannel["TiltY"] = 264] = "TiltY";
10880
+ PointerChannel[PointerChannel["Left"] = 265] = "Left";
10881
+ PointerChannel[PointerChannel["Right"] = 266] = "Right";
10882
+ PointerChannel[PointerChannel["Middle"] = 267] = "Middle";
10883
+ PointerChannel[PointerChannel["IsMouse"] = 268] = "IsMouse";
10884
+ PointerChannel[PointerChannel["IsTouch"] = 269] = "IsTouch";
10885
+ PointerChannel[PointerChannel["IsPen"] = 270] = "IsPen";
10886
+ PointerChannel[PointerChannel["IsPrimary"] = 271] = "IsPrimary";
10887
+ // --- Per-slot Active/X/Y for multi-pointer access ---
10888
+ PointerChannel[PointerChannel["Slot0Active"] = 256] = "Slot0Active";
10889
+ PointerChannel[PointerChannel["Slot0X"] = 257] = "Slot0X";
10890
+ PointerChannel[PointerChannel["Slot0Y"] = 258] = "Slot0Y";
10891
+ PointerChannel[PointerChannel["Slot1Active"] = 272] = "Slot1Active";
10892
+ PointerChannel[PointerChannel["Slot1X"] = 273] = "Slot1X";
10893
+ PointerChannel[PointerChannel["Slot1Y"] = 274] = "Slot1Y";
10894
+ PointerChannel[PointerChannel["Slot2Active"] = 288] = "Slot2Active";
10895
+ PointerChannel[PointerChannel["Slot2X"] = 289] = "Slot2X";
10896
+ PointerChannel[PointerChannel["Slot2Y"] = 290] = "Slot2Y";
10897
+ PointerChannel[PointerChannel["Slot3Active"] = 304] = "Slot3Active";
10898
+ PointerChannel[PointerChannel["Slot3X"] = 305] = "Slot3X";
10899
+ PointerChannel[PointerChannel["Slot3Y"] = 306] = "Slot3Y";
10900
+ PointerChannel[PointerChannel["Slot4Active"] = 320] = "Slot4Active";
10901
+ PointerChannel[PointerChannel["Slot4X"] = 321] = "Slot4X";
10902
+ PointerChannel[PointerChannel["Slot4Y"] = 322] = "Slot4Y";
10903
+ PointerChannel[PointerChannel["Slot5Active"] = 336] = "Slot5Active";
10904
+ PointerChannel[PointerChannel["Slot5X"] = 337] = "Slot5X";
10905
+ PointerChannel[PointerChannel["Slot5Y"] = 338] = "Slot5Y";
10906
+ PointerChannel[PointerChannel["Slot6Active"] = 352] = "Slot6Active";
10907
+ PointerChannel[PointerChannel["Slot6X"] = 353] = "Slot6X";
10908
+ PointerChannel[PointerChannel["Slot6Y"] = 354] = "Slot6Y";
10909
+ PointerChannel[PointerChannel["Slot7Active"] = 368] = "Slot7Active";
10910
+ PointerChannel[PointerChannel["Slot7X"] = 369] = "Slot7X";
10911
+ PointerChannel[PointerChannel["Slot7Y"] = 370] = "Slot7Y";
10912
+ PointerChannel[PointerChannel["Slot8Active"] = 384] = "Slot8Active";
10913
+ PointerChannel[PointerChannel["Slot8X"] = 385] = "Slot8X";
10914
+ PointerChannel[PointerChannel["Slot8Y"] = 386] = "Slot8Y";
10915
+ PointerChannel[PointerChannel["Slot9Active"] = 400] = "Slot9Active";
10916
+ PointerChannel[PointerChannel["Slot9X"] = 401] = "Slot9X";
10917
+ PointerChannel[PointerChannel["Slot9Y"] = 402] = "Slot9Y";
10918
+ PointerChannel[PointerChannel["Slot10Active"] = 416] = "Slot10Active";
10919
+ PointerChannel[PointerChannel["Slot10X"] = 417] = "Slot10X";
10920
+ PointerChannel[PointerChannel["Slot10Y"] = 418] = "Slot10Y";
10921
+ PointerChannel[PointerChannel["Slot11Active"] = 432] = "Slot11Active";
10922
+ PointerChannel[PointerChannel["Slot11X"] = 433] = "Slot11X";
10923
+ PointerChannel[PointerChannel["Slot11Y"] = 434] = "Slot11Y";
10924
+ PointerChannel[PointerChannel["Slot12Active"] = 448] = "Slot12Active";
10925
+ PointerChannel[PointerChannel["Slot12X"] = 449] = "Slot12X";
10926
+ PointerChannel[PointerChannel["Slot12Y"] = 450] = "Slot12Y";
10927
+ PointerChannel[PointerChannel["Slot13Active"] = 464] = "Slot13Active";
10928
+ PointerChannel[PointerChannel["Slot13X"] = 465] = "Slot13X";
10929
+ PointerChannel[PointerChannel["Slot13Y"] = 466] = "Slot13Y";
10930
+ PointerChannel[PointerChannel["Slot14Active"] = 480] = "Slot14Active";
10931
+ PointerChannel[PointerChannel["Slot14X"] = 481] = "Slot14X";
10932
+ PointerChannel[PointerChannel["Slot14Y"] = 482] = "Slot14Y";
10933
+ PointerChannel[PointerChannel["Slot15Active"] = 496] = "Slot15Active";
10934
+ PointerChannel[PointerChannel["Slot15X"] = 497] = "Slot15X";
10935
+ PointerChannel[PointerChannel["Slot15Y"] = 498] = "Slot15Y";
10936
+ })(PointerChannel || (PointerChannel = {}));
10854
10937
  var Keyboard;
10855
10938
  (function (Keyboard) {
10856
10939
  Keyboard[Keyboard["Backspace"] = 8] = "Backspace";
@@ -11132,14 +11215,21 @@ class Pointer {
11132
11215
  tilt;
11133
11216
  stateFlags = new Flags();
11134
11217
  _canvas;
11218
+ _channels;
11219
+ _slotIndex;
11220
+ _channelBase;
11135
11221
  _buttons;
11136
11222
  _pressure;
11137
11223
  _rotation;
11224
+ _isPrimary;
11138
11225
  _currentState = PointerState.Unknown;
11139
- constructor(event, canvas) {
11140
- const { pointerId, pointerType, clientX, clientY, width, height, tiltX, tiltY, buttons, pressure, twist } = event;
11226
+ constructor(event, canvas, channels, slotIndex) {
11227
+ const { pointerId, pointerType, clientX, clientY, width, height, tiltX, tiltY, buttons, pressure, twist, isPrimary } = event;
11141
11228
  const { left, top } = canvas.getBoundingClientRect();
11142
11229
  this._canvas = canvas;
11230
+ this._channels = channels;
11231
+ this._slotIndex = slotIndex;
11232
+ this._channelBase = ChannelOffset.Pointers + slotIndex * pointerSlotSize;
11143
11233
  this.id = pointerId;
11144
11234
  this.type = pointerType;
11145
11235
  this.position = new Vector(clientX - left, clientY - top);
@@ -11148,7 +11238,9 @@ class Pointer {
11148
11238
  this._buttons = buttons;
11149
11239
  this._pressure = pressure;
11150
11240
  this._rotation = twist;
11241
+ this._isPrimary = isPrimary;
11151
11242
  this.stateFlags.push(PointerStateFlag.Over);
11243
+ this._writeChannels(true);
11152
11244
  }
11153
11245
  get x() {
11154
11246
  return this.position.x;
@@ -11171,48 +11263,71 @@ class Pointer {
11171
11263
  get rotation() {
11172
11264
  return this._rotation;
11173
11265
  }
11266
+ get twist() {
11267
+ return this._rotation;
11268
+ }
11269
+ get tiltX() {
11270
+ return this.tilt.x;
11271
+ }
11272
+ get tiltY() {
11273
+ return this.tilt.y;
11274
+ }
11275
+ get isPrimary() {
11276
+ return this._isPrimary;
11277
+ }
11278
+ get slotIndex() {
11279
+ return this._slotIndex;
11280
+ }
11174
11281
  get currentState() {
11175
11282
  return this._currentState;
11176
11283
  }
11177
11284
  handleEnter(event) {
11178
11285
  this.handleEvent(event);
11179
11286
  this._currentState = PointerState.InsideCanvas;
11287
+ this._writeChannels(true);
11180
11288
  }
11181
11289
  handleLeave(event) {
11182
11290
  this.handleEvent(event);
11183
11291
  this.stateFlags.push(PointerStateFlag.Leave);
11184
11292
  this._currentState = PointerState.OutsideCanvas;
11293
+ this._writeChannels(false);
11185
11294
  }
11186
11295
  handlePress(event) {
11187
11296
  this.handleEvent(event);
11188
11297
  this.startPos.copy(this.position);
11189
11298
  this.stateFlags.push(PointerStateFlag.Down);
11190
11299
  this._currentState = PointerState.Pressed;
11300
+ this._writeChannels(true);
11191
11301
  }
11192
11302
  handleMove(event) {
11193
11303
  this.handleEvent(event);
11194
11304
  this.stateFlags.push(PointerStateFlag.Move);
11195
11305
  this._currentState = PointerState.Moving;
11306
+ this._writeChannels(true);
11196
11307
  }
11197
11308
  handleRelease(event) {
11198
11309
  this.handleEvent(event);
11199
11310
  this.stateFlags.push(PointerStateFlag.Up);
11200
11311
  this._currentState = PointerState.Released;
11312
+ this._writeChannels(true);
11201
11313
  }
11202
11314
  handleCancel(event) {
11203
11315
  this.handleEvent(event);
11204
11316
  this.stateFlags.push(PointerStateFlag.Cancel);
11205
11317
  this._currentState = PointerState.Cancelled;
11318
+ this._writeChannels(false);
11206
11319
  }
11207
11320
  destroy() {
11321
+ this._clearChannels();
11208
11322
  this.position.destroy();
11209
11323
  this.startPos.destroy();
11210
11324
  this.size.destroy();
11211
11325
  this.tilt.destroy();
11212
11326
  this._canvas = null;
11327
+ this._channels = null;
11213
11328
  }
11214
11329
  handleEvent(event) {
11215
- const { clientX, clientY, width, height, tiltX, tiltY, buttons, pressure, twist } = event;
11330
+ const { clientX, clientY, width, height, tiltX, tiltY, buttons, pressure, twist, isPrimary } = event;
11216
11331
  const { left, top } = this._canvas.getBoundingClientRect();
11217
11332
  this.position.set(clientX - left, clientY - top);
11218
11333
  this.size.set(width, height);
@@ -11220,8 +11335,263 @@ class Pointer {
11220
11335
  this._buttons = buttons;
11221
11336
  this._pressure = pressure;
11222
11337
  this._rotation = twist;
11338
+ this._isPrimary = isPrimary;
11223
11339
  return this;
11224
11340
  }
11341
+ /** Write the full 16-channel per-pointer state into the shared channel buffer. */
11342
+ _writeChannels(active) {
11343
+ const ch = this._channels;
11344
+ const canvas = this._canvas;
11345
+ if (!ch || !canvas) {
11346
+ return;
11347
+ }
11348
+ const base = this._channelBase;
11349
+ const w = canvas.width || 1;
11350
+ const h = canvas.height || 1;
11351
+ if (!active) {
11352
+ // Zero the entire slot for a clean release.
11353
+ for (let i = 0; i < pointerSlotSize; i++) {
11354
+ ch[base + i] = 0;
11355
+ }
11356
+ return;
11357
+ }
11358
+ const x = Math.min(1, Math.max(0, this.position.x / w));
11359
+ const y = Math.min(1, Math.max(0, this.position.y / h));
11360
+ ch[base + 0] = 1; // active
11361
+ ch[base + 1] = x; // x (normalized)
11362
+ ch[base + 2] = y; // y (normalized)
11363
+ ch[base + 3] = this._pressure; // pressure
11364
+ ch[base + 4] = Math.min(1, this.size.width / w); // width (normalized)
11365
+ ch[base + 5] = Math.min(1, this.size.height / h); // height (normalized)
11366
+ ch[base + 6] = this._rotation / 359; // twist (0..359 → 0..1)
11367
+ ch[base + 7] = (this.tilt.x + 90) / 180; // tiltX (-90..90 → 0..1)
11368
+ ch[base + 8] = (this.tilt.y + 90) / 180; // tiltY (-90..90 → 0..1)
11369
+ ch[base + 9] = (this._buttons & 1) ? 1 : 0; // button.left
11370
+ ch[base + 10] = (this._buttons & 2) ? 1 : 0; // button.right
11371
+ ch[base + 11] = (this._buttons & 4) ? 1 : 0; // button.middle
11372
+ ch[base + 12] = this.type === 'mouse' ? 1 : 0; // isMouse
11373
+ ch[base + 13] = this.type === 'touch' ? 1 : 0; // isTouch
11374
+ ch[base + 14] = this.type === 'pen' ? 1 : 0; // isPen
11375
+ ch[base + 15] = this._isPrimary ? 1 : 0; // isPrimary
11376
+ }
11377
+ /** Zero the slot when this pointer is fully released/destroyed. */
11378
+ _clearChannels() {
11379
+ const ch = this._channels;
11380
+ if (!ch) {
11381
+ return;
11382
+ }
11383
+ const base = this._channelBase;
11384
+ for (let i = 0; i < pointerSlotSize; i++) {
11385
+ ch[base + i] = 0;
11386
+ }
11387
+ }
11388
+ }
11389
+ /**
11390
+ * Namespace merged onto the `Pointer` class to expose channel-offset constants.
11391
+ * All members mirror `PointerChannel` so callers can write `Pointer.Active`, `Pointer.X`, etc.
11392
+ *
11393
+ * The un-prefixed members (Active, X, Y, …) address slot 0 (the primary pointer).
11394
+ * For multi-touch access use `Pointer.Slot{N}Active / Slot{N}X / Slot{N}Y`, or compute:
11395
+ * `Pointer.X + slotIndex * pointerSlotSize + channelOffset`.
11396
+ */
11397
+ // eslint-disable-next-line @typescript-eslint/no-namespace
11398
+ (function (Pointer) {
11399
+ /* eslint-disable @typescript-eslint/naming-convention */
11400
+ // --- Primary-pointer convenience aliases (slot 0) ---
11401
+ Pointer.Active = PointerChannel.Active;
11402
+ Pointer.X = PointerChannel.X;
11403
+ Pointer.Y = PointerChannel.Y;
11404
+ Pointer.Pressure = PointerChannel.Pressure;
11405
+ Pointer.Width = PointerChannel.Width;
11406
+ Pointer.Height = PointerChannel.Height;
11407
+ Pointer.Twist = PointerChannel.Twist;
11408
+ Pointer.TiltX = PointerChannel.TiltX;
11409
+ Pointer.TiltY = PointerChannel.TiltY;
11410
+ Pointer.Left = PointerChannel.Left;
11411
+ Pointer.Right = PointerChannel.Right;
11412
+ Pointer.Middle = PointerChannel.Middle;
11413
+ Pointer.IsMouse = PointerChannel.IsMouse;
11414
+ Pointer.IsTouch = PointerChannel.IsTouch;
11415
+ Pointer.IsPen = PointerChannel.IsPen;
11416
+ Pointer.IsPrimary = PointerChannel.IsPrimary;
11417
+ // --- Per-slot Active/X/Y for multi-pointer access ---
11418
+ Pointer.Slot0Active = PointerChannel.Slot0Active;
11419
+ Pointer.Slot0X = PointerChannel.Slot0X;
11420
+ Pointer.Slot0Y = PointerChannel.Slot0Y;
11421
+ Pointer.Slot1Active = PointerChannel.Slot1Active;
11422
+ Pointer.Slot1X = PointerChannel.Slot1X;
11423
+ Pointer.Slot1Y = PointerChannel.Slot1Y;
11424
+ Pointer.Slot2Active = PointerChannel.Slot2Active;
11425
+ Pointer.Slot2X = PointerChannel.Slot2X;
11426
+ Pointer.Slot2Y = PointerChannel.Slot2Y;
11427
+ Pointer.Slot3Active = PointerChannel.Slot3Active;
11428
+ Pointer.Slot3X = PointerChannel.Slot3X;
11429
+ Pointer.Slot3Y = PointerChannel.Slot3Y;
11430
+ Pointer.Slot4Active = PointerChannel.Slot4Active;
11431
+ Pointer.Slot4X = PointerChannel.Slot4X;
11432
+ Pointer.Slot4Y = PointerChannel.Slot4Y;
11433
+ Pointer.Slot5Active = PointerChannel.Slot5Active;
11434
+ Pointer.Slot5X = PointerChannel.Slot5X;
11435
+ Pointer.Slot5Y = PointerChannel.Slot5Y;
11436
+ Pointer.Slot6Active = PointerChannel.Slot6Active;
11437
+ Pointer.Slot6X = PointerChannel.Slot6X;
11438
+ Pointer.Slot6Y = PointerChannel.Slot6Y;
11439
+ Pointer.Slot7Active = PointerChannel.Slot7Active;
11440
+ Pointer.Slot7X = PointerChannel.Slot7X;
11441
+ Pointer.Slot7Y = PointerChannel.Slot7Y;
11442
+ Pointer.Slot8Active = PointerChannel.Slot8Active;
11443
+ Pointer.Slot8X = PointerChannel.Slot8X;
11444
+ Pointer.Slot8Y = PointerChannel.Slot8Y;
11445
+ Pointer.Slot9Active = PointerChannel.Slot9Active;
11446
+ Pointer.Slot9X = PointerChannel.Slot9X;
11447
+ Pointer.Slot9Y = PointerChannel.Slot9Y;
11448
+ Pointer.Slot10Active = PointerChannel.Slot10Active;
11449
+ Pointer.Slot10X = PointerChannel.Slot10X;
11450
+ Pointer.Slot10Y = PointerChannel.Slot10Y;
11451
+ Pointer.Slot11Active = PointerChannel.Slot11Active;
11452
+ Pointer.Slot11X = PointerChannel.Slot11X;
11453
+ Pointer.Slot11Y = PointerChannel.Slot11Y;
11454
+ Pointer.Slot12Active = PointerChannel.Slot12Active;
11455
+ Pointer.Slot12X = PointerChannel.Slot12X;
11456
+ Pointer.Slot12Y = PointerChannel.Slot12Y;
11457
+ Pointer.Slot13Active = PointerChannel.Slot13Active;
11458
+ Pointer.Slot13X = PointerChannel.Slot13X;
11459
+ Pointer.Slot13Y = PointerChannel.Slot13Y;
11460
+ Pointer.Slot14Active = PointerChannel.Slot14Active;
11461
+ Pointer.Slot14X = PointerChannel.Slot14X;
11462
+ Pointer.Slot14Y = PointerChannel.Slot14Y;
11463
+ Pointer.Slot15Active = PointerChannel.Slot15Active;
11464
+ Pointer.Slot15X = PointerChannel.Slot15X;
11465
+ Pointer.Slot15Y = PointerChannel.Slot15Y;
11466
+ /* eslint-enable @typescript-eslint/naming-convention */
11467
+ })(Pointer || (Pointer = {}));
11468
+
11469
+ /** Long-press threshold in milliseconds. */
11470
+ const longPressMs = 500;
11471
+ /**
11472
+ * Internal gesture recognizer — NOT exported publicly.
11473
+ * Receives pointer lifecycle events from InputManager and fires gesture signals.
11474
+ */
11475
+ class GestureRecognizer {
11476
+ onPinch;
11477
+ onRotate;
11478
+ onLongPress;
11479
+ distanceThreshold;
11480
+ // Active touch pointers (only touch type; index in order of arrival).
11481
+ touchPointers = new Map();
11482
+ // Long-press state per pointer.
11483
+ longPressEntries = new Map();
11484
+ // Previous two-touch distance and angle for incremental deltas.
11485
+ prevDistance = -1;
11486
+ prevAngle = 0;
11487
+ // Reusable Vector for center dispatches (avoids heap churn).
11488
+ centerVec = new Vector();
11489
+ constructor(distanceThreshold, onPinch, onRotate, onLongPress) {
11490
+ this.distanceThreshold = distanceThreshold;
11491
+ this.onPinch = onPinch;
11492
+ this.onRotate = onRotate;
11493
+ this.onLongPress = onLongPress;
11494
+ }
11495
+ onPointerDown(pointer) {
11496
+ if (pointer.type === 'touch') {
11497
+ this.touchPointers.set(pointer.id, pointer);
11498
+ this._resetTwoTouchBaseline();
11499
+ }
11500
+ // Start long-press timer for every pointer type.
11501
+ const timerId = setTimeout(() => {
11502
+ this.longPressEntries.delete(pointer.id);
11503
+ this.onLongPress.dispatch(pointer);
11504
+ }, longPressMs);
11505
+ this.longPressEntries.set(pointer.id, {
11506
+ pointerId: pointer.id,
11507
+ pointer,
11508
+ timerId,
11509
+ startX: pointer.x,
11510
+ startY: pointer.y,
11511
+ });
11512
+ }
11513
+ onPointerMove(pointer, distanceThreshold) {
11514
+ // Cancel long-press if moved beyond threshold.
11515
+ const entry = this.longPressEntries.get(pointer.id);
11516
+ if (entry) {
11517
+ const dx = pointer.x - entry.startX;
11518
+ const dy = pointer.y - entry.startY;
11519
+ if (Math.sqrt(dx * dx + dy * dy) > distanceThreshold) {
11520
+ clearTimeout(entry.timerId);
11521
+ this.longPressEntries.delete(pointer.id);
11522
+ }
11523
+ }
11524
+ if (pointer.type !== 'touch') {
11525
+ return;
11526
+ }
11527
+ // Update the stored pointer reference's position via the live object.
11528
+ // (We store the actual Pointer object so position is already updated by the caller.)
11529
+ if (this.touchPointers.size < 2) {
11530
+ return;
11531
+ }
11532
+ this._processTwoTouchGestures();
11533
+ }
11534
+ onPointerUp(pointer) {
11535
+ this._cancelLongPress(pointer.id);
11536
+ }
11537
+ onPointerLeave(pointer) {
11538
+ this._cancelLongPress(pointer.id);
11539
+ if (pointer.type === 'touch') {
11540
+ this.touchPointers.delete(pointer.id);
11541
+ this._resetTwoTouchBaseline();
11542
+ }
11543
+ }
11544
+ onPointerCancel(pointer) {
11545
+ this._cancelLongPress(pointer.id);
11546
+ if (pointer.type === 'touch') {
11547
+ this.touchPointers.delete(pointer.id);
11548
+ this._resetTwoTouchBaseline();
11549
+ }
11550
+ }
11551
+ destroy() {
11552
+ for (const entry of this.longPressEntries.values()) {
11553
+ clearTimeout(entry.timerId);
11554
+ }
11555
+ this.longPressEntries.clear();
11556
+ this.touchPointers.clear();
11557
+ this.centerVec.destroy();
11558
+ }
11559
+ _cancelLongPress(pointerId) {
11560
+ const entry = this.longPressEntries.get(pointerId);
11561
+ if (entry) {
11562
+ clearTimeout(entry.timerId);
11563
+ this.longPressEntries.delete(pointerId);
11564
+ }
11565
+ }
11566
+ _resetTwoTouchBaseline() {
11567
+ this.prevDistance = -1;
11568
+ this.prevAngle = 0;
11569
+ }
11570
+ _processTwoTouchGestures() {
11571
+ const iter = this.touchPointers.values();
11572
+ const pA = iter.next().value;
11573
+ const pB = iter.next().value;
11574
+ const dx = pB.x - pA.x;
11575
+ const dy = pB.y - pA.y;
11576
+ const currentDistance = Math.sqrt(dx * dx + dy * dy);
11577
+ const currentAngle = Math.atan2(dy, dx);
11578
+ const centerX = (pA.x + pB.x) / 2;
11579
+ const centerY = (pA.y + pB.y) / 2;
11580
+ this.centerVec.set(centerX, centerY);
11581
+ if (this.prevDistance > 0) {
11582
+ const scale = currentDistance / this.prevDistance;
11583
+ // Only fire if there's a meaningful distance change.
11584
+ if (Math.abs(scale - 1) > 0.0001) {
11585
+ this.onPinch.dispatch(scale, this.centerVec);
11586
+ }
11587
+ const angleDelta = currentAngle - this.prevAngle;
11588
+ if (Math.abs(angleDelta) > 0.0001) {
11589
+ this.onRotate.dispatch(angleDelta, this.centerVec);
11590
+ }
11591
+ }
11592
+ this.prevDistance = currentDistance;
11593
+ this.prevAngle = currentAngle;
11594
+ }
11225
11595
  }
11226
11596
 
11227
11597
  var GamepadChannel;
@@ -11602,6 +11972,10 @@ class InputManager {
11602
11972
  channelsPressed = [];
11603
11973
  channelsReleased = [];
11604
11974
  gamepadDefinitions;
11975
+ // Slot allocation for unified pointer tracking (mouse / touch / pen).
11976
+ pointerSlots = new Map(); // pointerId → slotIndex
11977
+ freeSlots = Array.from({ length: maxPointers }, (_, i) => i);
11978
+ gestureRecognizer;
11605
11979
  canvasFocusedValue;
11606
11980
  pointerDistanceThreshold;
11607
11981
  keyDownHandler = this.handleKeyDown.bind(this);
@@ -11630,12 +12004,23 @@ class InputManager {
11630
12004
  onGamepadConnected = new Signal();
11631
12005
  onGamepadDisconnected = new Signal();
11632
12006
  onGamepadUpdated = new Signal();
12007
+ /** Fires on every two-touch-pointer move where the distance between them changed. `scale` > 1 = spreading, < 1 = pinching. */
12008
+ onPinch = new Signal();
12009
+ /** Fires on every two-touch-pointer move where the angle between them changed. `angleDelta` is in radians. */
12010
+ onRotate = new Signal();
12011
+ /** Fires when a pointer has been held without significant movement for ≥ 500 ms. */
12012
+ onLongPress = new Signal();
11633
12013
  constructor(app) {
11634
12014
  const { gamepadDefinitions = [], pointerDistanceThreshold } = app.options;
11635
12015
  this.canvas = app.canvas;
11636
12016
  this.canvasFocusedValue = document.activeElement === this.canvas;
11637
12017
  this.pointerDistanceThreshold = pointerDistanceThreshold;
11638
12018
  this.gamepadDefinitions = [...gamepadDefinitions, ...builtInGamepadDefinitions];
12019
+ // Disable the browser's default pan/zoom/double-tap-zoom on touch devices so
12020
+ // pointer events reach the canvas without being swallowed by the browser's
12021
+ // native touch gestures.
12022
+ this.canvas.style.touchAction = 'none';
12023
+ this.gestureRecognizer = new GestureRecognizer(pointerDistanceThreshold, this.onPinch, this.onRotate, this.onLongPress);
11639
12024
  this.addEventListeners();
11640
12025
  }
11641
12026
  get pointersInCanvas() {
@@ -11688,6 +12073,7 @@ class InputManager {
11688
12073
  }
11689
12074
  destroy() {
11690
12075
  this.removeEventListeners();
12076
+ this.gestureRecognizer.destroy();
11691
12077
  for (const pointer of Object.values(this.pointers)) {
11692
12078
  pointer.destroy();
11693
12079
  }
@@ -11699,6 +12085,8 @@ class InputManager {
11699
12085
  this.gamepadsValue.length = 0;
11700
12086
  this.channelsPressed.length = 0;
11701
12087
  this.channelsReleased.length = 0;
12088
+ this.pointerSlots.clear();
12089
+ this.freeSlots.length = 0;
11702
12090
  this.wheelOffset.destroy();
11703
12091
  this.flags.destroy();
11704
12092
  this.onPointerEnter.destroy();
@@ -11715,6 +12103,28 @@ class InputManager {
11715
12103
  this.onGamepadConnected.destroy();
11716
12104
  this.onGamepadDisconnected.destroy();
11717
12105
  this.onGamepadUpdated.destroy();
12106
+ this.onPinch.destroy();
12107
+ this.onRotate.destroy();
12108
+ this.onLongPress.destroy();
12109
+ }
12110
+ _assignSlot(pointerId) {
12111
+ if (this.pointerSlots.has(pointerId)) {
12112
+ return this.pointerSlots.get(pointerId);
12113
+ }
12114
+ if (this.freeSlots.length === 0) {
12115
+ return null; // All 16 slots occupied — silently drop.
12116
+ }
12117
+ const slot = this.freeSlots.shift();
12118
+ this.pointerSlots.set(pointerId, slot);
12119
+ return slot;
12120
+ }
12121
+ _releaseSlot(pointerId) {
12122
+ const slot = this.pointerSlots.get(pointerId);
12123
+ if (slot !== undefined) {
12124
+ this.pointerSlots.delete(pointerId);
12125
+ // Push to the front so slot 0 is recovered first, keeping allocation predictable.
12126
+ this.freeSlots.unshift(slot);
12127
+ }
11718
12128
  }
11719
12129
  handleKeyDown(event) {
11720
12130
  // Game-engine convention: keys only register while the canvas
@@ -11743,17 +12153,32 @@ class InputManager {
11743
12153
  stopEvent(event);
11744
12154
  }
11745
12155
  handlePointerOver(event) {
11746
- this.pointers[event.pointerId] = new Pointer(event, this.canvas);
12156
+ const slot = this._assignSlot(event.pointerId);
12157
+ if (slot === null) {
12158
+ return; // 17th+ simultaneous pointer — silently drop.
12159
+ }
12160
+ this.pointers[event.pointerId] = new Pointer(event, this.canvas, this.channels, slot);
11747
12161
  this.flags.push(InputManagerFlag.PointerUpdate);
11748
12162
  }
11749
12163
  handlePointerLeave(event) {
11750
- this.pointers[event.pointerId].handleLeave(event);
12164
+ const pointer = this.pointers[event.pointerId];
12165
+ if (!pointer) {
12166
+ return;
12167
+ }
12168
+ pointer.handleLeave(event);
12169
+ this.gestureRecognizer.onPointerLeave(pointer);
12170
+ this._releaseSlot(event.pointerId);
11751
12171
  this.flags.push(InputManagerFlag.PointerUpdate);
11752
12172
  }
11753
12173
  handlePointerDown(event) {
11754
12174
  this.canvas.focus();
11755
12175
  this.canvasFocusedValue = true;
11756
- this.pointers[event.pointerId].handlePress(event);
12176
+ const pointer = this.pointers[event.pointerId];
12177
+ if (!pointer) {
12178
+ return;
12179
+ }
12180
+ pointer.handlePress(event);
12181
+ this.gestureRecognizer.onPointerDown(pointer);
11757
12182
  this.flags.push(InputManagerFlag.PointerUpdate);
11758
12183
  // preventDefault stops native drag / text-selection;
11759
12184
  // stopImmediatePropagation prevents bubbling to host-page click
@@ -11762,16 +12187,32 @@ class InputManager {
11762
12187
  stopEvent(event);
11763
12188
  }
11764
12189
  handlePointerMove(event) {
11765
- this.pointers[event.pointerId].handleMove(event);
12190
+ const pointer = this.pointers[event.pointerId];
12191
+ if (!pointer) {
12192
+ return;
12193
+ }
12194
+ pointer.handleMove(event);
12195
+ this.gestureRecognizer.onPointerMove(pointer, this.pointerDistanceThreshold);
11766
12196
  this.flags.push(InputManagerFlag.PointerUpdate);
11767
12197
  }
11768
12198
  handlePointerUp(event) {
11769
- this.pointers[event.pointerId].handleRelease(event);
12199
+ const pointer = this.pointers[event.pointerId];
12200
+ if (!pointer) {
12201
+ return;
12202
+ }
12203
+ pointer.handleRelease(event);
12204
+ this.gestureRecognizer.onPointerUp(pointer);
11770
12205
  this.flags.push(InputManagerFlag.PointerUpdate);
11771
12206
  stopEvent(event);
11772
12207
  }
11773
12208
  handlePointerCancel(event) {
11774
- this.pointers[event.pointerId].handleCancel(event);
12209
+ const pointer = this.pointers[event.pointerId];
12210
+ if (!pointer) {
12211
+ return;
12212
+ }
12213
+ pointer.handleCancel(event);
12214
+ this.gestureRecognizer.onPointerCancel(pointer);
12215
+ this._releaseSlot(event.pointerId);
11775
12216
  this.flags.push(InputManagerFlag.PointerUpdate);
11776
12217
  }
11777
12218
  handleMouseWheel(event) {
@@ -18078,474 +18519,5 @@ class IndexedDbStore {
18078
18519
  }
18079
18520
  }
18080
18521
 
18081
- const rapierModuleName = '@dimforge/rapier2d-compat';
18082
- const maxCollisionGroup = 15;
18083
- const fullGroupMask = 0xFFFF;
18084
- const defaultDeltaSeconds = 1 / 60;
18085
- const defaultDebugDrawOptions = {
18086
- lineWidth: 1,
18087
- solidLineColor: new Color(64, 196, 255, 1),
18088
- solidFillColor: new Color(64, 196, 255, 0.12),
18089
- triggerLineColor: new Color(255, 180, 48, 1),
18090
- triggerFillColor: new Color(255, 180, 48, 0.08),
18091
- };
18092
- const resolveRapierModule = (module) => {
18093
- const rapier = module;
18094
- const vector2 = rapier.Vector2;
18095
- const worldConstructor = rapier.World;
18096
- const eventQueueConstructor = rapier.EventQueue;
18097
- const rigidBodyDescFactory = rapier.RigidBodyDesc;
18098
- const colliderDescFactory = rapier.ColliderDesc;
18099
- if (typeof vector2 !== 'function' || typeof worldConstructor !== 'function' || typeof eventQueueConstructor !== 'function') {
18100
- throw new Error('Invalid Rapier module loader result. Expected Vector2, World, and EventQueue exports.');
18101
- }
18102
- if (typeof rigidBodyDescFactory !== 'object'
18103
- || rigidBodyDescFactory === null
18104
- || typeof colliderDescFactory !== 'object'
18105
- || colliderDescFactory === null) {
18106
- throw new Error('Invalid Rapier module loader result. Expected RigidBodyDesc and ColliderDesc exports.');
18107
- }
18108
- const activeEvents = rapier.ActiveEvents;
18109
- const activeCollisionEvents = typeof activeEvents?.COLLISION_EVENTS === 'number'
18110
- ? activeEvents.COLLISION_EVENTS
18111
- : undefined;
18112
- return {
18113
- init: typeof rapier.init === 'function'
18114
- ? rapier.init
18115
- : undefined,
18116
- vector2: vector2,
18117
- worldConstructor: worldConstructor,
18118
- eventQueueConstructor: eventQueueConstructor,
18119
- rigidBodyDescFactory: rigidBodyDescFactory,
18120
- colliderDescFactory: colliderDescFactory,
18121
- activeCollisionEvents,
18122
- };
18123
- };
18124
- const defaultRapierModuleLoader = async () => {
18125
- return await import(rapierModuleName);
18126
- };
18127
- const assertFiniteNumber = (value, label) => {
18128
- if (!Number.isFinite(value)) {
18129
- throw new Error(`${label} must be a finite number.`);
18130
- }
18131
- };
18132
- const assertPositiveNumber = (value, label) => {
18133
- assertFiniteNumber(value, label);
18134
- if (value <= 0) {
18135
- throw new Error(`${label} must be greater than zero.`);
18136
- }
18137
- };
18138
- const assertGroup = (group, label) => {
18139
- if (!Number.isInteger(group) || group < 0 || group > maxCollisionGroup) {
18140
- throw new Error(`${label} must be an integer between 0 and ${maxCollisionGroup}.`);
18141
- }
18142
- };
18143
- const toGroupMask = (groups, fallback) => {
18144
- if (groups === undefined) {
18145
- return fallback;
18146
- }
18147
- if (typeof groups === 'number') {
18148
- assertGroup(groups, 'collision group');
18149
- return 1 << groups;
18150
- }
18151
- if (groups.length === 0) {
18152
- return 0;
18153
- }
18154
- return groups.reduce((mask, group, index) => {
18155
- assertGroup(group, `collision groups[${index}]`);
18156
- return mask | (1 << group);
18157
- }, 0);
18158
- };
18159
- const toPackedCollisionGroups = (filter) => {
18160
- const membershipMask = toGroupMask(filter?.membership, 1 << 0);
18161
- const collisionMask = toGroupMask(filter?.collidesWith, fullGroupMask);
18162
- return ((membershipMask & fullGroupMask) << 16) | (collisionMask & fullGroupMask);
18163
- };
18164
- const getOffset = (shape) => ({
18165
- x: shape.offsetX ?? 0,
18166
- y: shape.offsetY ?? 0,
18167
- });
18168
- const rotatePoint = (x, y, rotation) => {
18169
- const cos = Math.cos(rotation);
18170
- const sin = Math.sin(rotation);
18171
- return {
18172
- x: (x * cos) - (y * sin),
18173
- y: (x * sin) + (y * cos),
18174
- };
18175
- };
18176
- const assertShape = (shape) => {
18177
- if (shape.type === 'box') {
18178
- assertPositiveNumber(shape.width, 'Box width');
18179
- assertPositiveNumber(shape.height, 'Box height');
18180
- assertFiniteNumber(shape.offsetX ?? 0, 'Box offsetX');
18181
- assertFiniteNumber(shape.offsetY ?? 0, 'Box offsetY');
18182
- assertFiniteNumber(shape.offsetRotation ?? 0, 'Box offsetRotation');
18183
- return;
18184
- }
18185
- assertPositiveNumber(shape.radius, 'Circle radius');
18186
- assertFiniteNumber(shape.offsetX ?? 0, 'Circle offsetX');
18187
- assertFiniteNumber(shape.offsetY ?? 0, 'Circle offsetY');
18188
- };
18189
- const assertBodyOptions = (options) => {
18190
- assertShape(options.shape);
18191
- assertFiniteNumber(options.friction ?? 0, 'friction');
18192
- assertFiniteNumber(options.restitution ?? 0, 'restitution');
18193
- assertFiniteNumber(options.density ?? 0, 'density');
18194
- assertFiniteNumber(options.gravityScale ?? 1, 'gravityScale');
18195
- assertFiniteNumber(options.linearDamping ?? 0, 'linearDamping');
18196
- assertFiniteNumber(options.angularDamping ?? 0, 'angularDamping');
18197
- toPackedCollisionGroups(options.collisionFilter);
18198
- };
18199
- class RapierPhysicsBinding {
18200
- _world;
18201
- _body;
18202
- _collider;
18203
- _syncMode;
18204
- node;
18205
- bodyType;
18206
- shape;
18207
- trigger;
18208
- constructor(world, node, body, collider, options) {
18209
- this._world = world;
18210
- this.node = node;
18211
- this._body = body;
18212
- this._collider = collider;
18213
- this.bodyType = options.type ?? 'dynamic';
18214
- this.shape = options.shape;
18215
- this.trigger = options.trigger ?? false;
18216
- this._syncMode = options.syncMode ?? 'physicsToNode';
18217
- }
18218
- get bodyHandle() {
18219
- return this._body.handle;
18220
- }
18221
- get colliderHandle() {
18222
- return this._collider.handle;
18223
- }
18224
- getBody() {
18225
- return this._body;
18226
- }
18227
- get syncMode() {
18228
- return this._syncMode;
18229
- }
18230
- set syncMode(syncMode) {
18231
- this._syncMode = syncMode;
18232
- }
18233
- get x() {
18234
- return this._body.translation().x;
18235
- }
18236
- get y() {
18237
- return this._body.translation().y;
18238
- }
18239
- get rotation() {
18240
- return this._body.rotation();
18241
- }
18242
- setSyncMode(syncMode) {
18243
- this._syncMode = syncMode;
18244
- return this;
18245
- }
18246
- teleport(x, y, rotation = this.node.rotation) {
18247
- this._world.writeBodyTransform(this._body, x, y, rotation, true);
18248
- this.node.setPosition(x, y);
18249
- this.node.setRotation(rotation);
18250
- return this;
18251
- }
18252
- syncBodyFromNode(wakeUp = true) {
18253
- this._world.writeBodyTransform(this._body, this.node.x, this.node.y, this.node.rotation, wakeUp);
18254
- return this;
18255
- }
18256
- syncNodeFromBody() {
18257
- const translation = this._body.translation();
18258
- this.node.setPosition(translation.x, translation.y);
18259
- this.node.setRotation(this._body.rotation());
18260
- return this;
18261
- }
18262
- setCollisionFilter(filter) {
18263
- const groups = toPackedCollisionGroups(filter);
18264
- this._collider.setCollisionGroups(groups);
18265
- this._collider.setSolverGroups(groups);
18266
- return this;
18267
- }
18268
- destroy() {
18269
- this._world.removeBinding(this);
18270
- }
18271
- }
18272
- class RapierPhysicsWorld {
18273
- onCollisionEnter = new Signal();
18274
- onCollisionExit = new Signal();
18275
- onTriggerEnter = new Signal();
18276
- onTriggerExit = new Signal();
18277
- _rapier;
18278
- _world;
18279
- _eventQueue;
18280
- _bindings = new Set();
18281
- _nodeBindings = new Map();
18282
- _colliderBindings = new Map();
18283
- constructor(rapier, world, eventQueue) {
18284
- this._rapier = rapier;
18285
- this._world = world;
18286
- this._eventQueue = eventQueue;
18287
- }
18288
- static async create(options = {}) {
18289
- const moduleLoader = options.moduleLoader ?? defaultRapierModuleLoader;
18290
- let loadedModule = null;
18291
- try {
18292
- loadedModule = await moduleLoader();
18293
- }
18294
- catch (error) {
18295
- const message = error instanceof Error ? error.message : String(error);
18296
- throw new Error(`Rapier physics module is unavailable. Install "${rapierModuleName}" or provide a custom module loader. Original error: ${message}`, { cause: error });
18297
- }
18298
- const rapier = resolveRapierModule(loadedModule);
18299
- if (typeof rapier.init === 'function') {
18300
- await rapier.init();
18301
- }
18302
- const gravityX = options.gravityX ?? 0;
18303
- const gravityY = options.gravityY ?? 9.81;
18304
- assertFiniteNumber(gravityX, 'gravityX');
18305
- assertFiniteNumber(gravityY, 'gravityY');
18306
- const world = new rapier.worldConstructor(new rapier.vector2(gravityX, gravityY));
18307
- const eventQueue = new rapier.eventQueueConstructor(true);
18308
- return new RapierPhysicsWorld(rapier, world, eventQueue);
18309
- }
18310
- get gravity() {
18311
- return {
18312
- x: this._world.gravity.x,
18313
- y: this._world.gravity.y,
18314
- };
18315
- }
18316
- setGravity(x, y) {
18317
- assertFiniteNumber(x, 'gravityX');
18318
- assertFiniteNumber(y, 'gravityY');
18319
- this._world.gravity.x = x;
18320
- this._world.gravity.y = y;
18321
- return this;
18322
- }
18323
- hasNode(node) {
18324
- return this._nodeBindings.has(node);
18325
- }
18326
- getBinding(node) {
18327
- return this._nodeBindings.get(node) ?? null;
18328
- }
18329
- attachNode(node, options) {
18330
- if (this._nodeBindings.has(node)) {
18331
- throw new Error('This SceneNode is already attached to a physics body.');
18332
- }
18333
- assertBodyOptions(options);
18334
- const bodyDescriptor = this.createBodyDescriptor(node, options);
18335
- const body = this._world.createRigidBody(bodyDescriptor);
18336
- const colliderDescriptor = this.createColliderDescriptor(options);
18337
- const collider = this._world.createCollider(colliderDescriptor, body);
18338
- const binding = new RapierPhysicsBinding(this, node, body, collider, options);
18339
- this._bindings.add(binding);
18340
- this._nodeBindings.set(node, binding);
18341
- this._colliderBindings.set(collider.handle, binding);
18342
- return binding;
18343
- }
18344
- detachNode(node) {
18345
- const binding = this._nodeBindings.get(node);
18346
- if (binding) {
18347
- this.removeBinding(binding);
18348
- }
18349
- return this;
18350
- }
18351
- step(deltaSeconds = defaultDeltaSeconds) {
18352
- assertFiniteNumber(deltaSeconds, 'deltaSeconds');
18353
- if (deltaSeconds < 0) {
18354
- throw new Error('deltaSeconds must be zero or greater.');
18355
- }
18356
- this.applyStepDelta(deltaSeconds);
18357
- this._world.step(this._eventQueue);
18358
- this.drainCollisionEvents();
18359
- this.syncNodeTransforms();
18360
- return this;
18361
- }
18362
- syncNodeTransforms() {
18363
- for (const binding of this._bindings) {
18364
- if (binding.syncMode === 'physicsToNode') {
18365
- binding.syncNodeFromBody();
18366
- }
18367
- }
18368
- return this;
18369
- }
18370
- createDebugGraphics(options = {}) {
18371
- const graphics = new Graphics();
18372
- graphics.setCullable(false);
18373
- this.updateDebugGraphics(graphics, options);
18374
- return graphics;
18375
- }
18376
- updateDebugGraphics(graphics, options = {}) {
18377
- const lineWidth = options.lineWidth ?? defaultDebugDrawOptions.lineWidth;
18378
- const solidLineColor = options.solidLineColor ?? defaultDebugDrawOptions.solidLineColor;
18379
- const solidFillColor = options.solidFillColor ?? defaultDebugDrawOptions.solidFillColor;
18380
- const triggerLineColor = options.triggerLineColor ?? defaultDebugDrawOptions.triggerLineColor;
18381
- const triggerFillColor = options.triggerFillColor ?? defaultDebugDrawOptions.triggerFillColor;
18382
- graphics.clear();
18383
- graphics.lineWidth = lineWidth;
18384
- for (const binding of this._bindings) {
18385
- const bodyTranslation = {
18386
- x: binding.x,
18387
- y: binding.y,
18388
- };
18389
- const rotation = binding.rotation;
18390
- const lineColor = binding.trigger ? triggerLineColor : solidLineColor;
18391
- const fillColor = binding.trigger ? triggerFillColor : solidFillColor;
18392
- graphics.lineColor = lineColor;
18393
- graphics.fillColor = fillColor;
18394
- if (binding.shape.type === 'box') {
18395
- const path = this.buildBoxPath(binding.shape, bodyTranslation.x, bodyTranslation.y, rotation);
18396
- graphics.drawPolygon(path);
18397
- }
18398
- else {
18399
- const offset = getOffset(binding.shape);
18400
- const rotatedOffset = rotatePoint(offset.x, offset.y, rotation);
18401
- graphics.drawCircle(bodyTranslation.x + rotatedOffset.x, bodyTranslation.y + rotatedOffset.y, binding.shape.radius);
18402
- }
18403
- }
18404
- return graphics;
18405
- }
18406
- destroy() {
18407
- for (const binding of Array.from(this._bindings)) {
18408
- this.removeBinding(binding);
18409
- }
18410
- this.onCollisionEnter.destroy();
18411
- this.onCollisionExit.destroy();
18412
- this.onTriggerEnter.destroy();
18413
- this.onTriggerExit.destroy();
18414
- }
18415
- writeBodyTransform(body, x, y, rotation, wakeUp) {
18416
- body.setTranslation(new this._rapier.vector2(x, y), wakeUp);
18417
- body.setRotation(rotation, wakeUp);
18418
- }
18419
- removeBinding(binding) {
18420
- if (!this._bindings.has(binding)) {
18421
- return;
18422
- }
18423
- this._bindings.delete(binding);
18424
- this._nodeBindings.delete(binding.node);
18425
- this._colliderBindings.delete(binding.colliderHandle);
18426
- this._world.removeRigidBody(binding.getBody());
18427
- }
18428
- createBodyDescriptor(node, options) {
18429
- const type = options.type ?? 'dynamic';
18430
- let descriptor = this._rapier.rigidBodyDescFactory.dynamic();
18431
- switch (type) {
18432
- case 'static':
18433
- descriptor = this._rapier.rigidBodyDescFactory.fixed();
18434
- break;
18435
- case 'kinematic':
18436
- if (typeof this._rapier.rigidBodyDescFactory.kinematicPositionBased === 'function') {
18437
- descriptor = this._rapier.rigidBodyDescFactory.kinematicPositionBased();
18438
- }
18439
- else if (typeof this._rapier.rigidBodyDescFactory.kinematicVelocityBased === 'function') {
18440
- descriptor = this._rapier.rigidBodyDescFactory.kinematicVelocityBased();
18441
- }
18442
- else {
18443
- throw new Error('Rapier module does not expose a kinematic rigid-body descriptor.');
18444
- }
18445
- break;
18446
- default:
18447
- descriptor = this._rapier.rigidBodyDescFactory.dynamic();
18448
- break;
18449
- }
18450
- descriptor
18451
- .setTranslation(node.x, node.y)
18452
- .setRotation(node.rotation)
18453
- .setLinearDamping(options.linearDamping ?? 0)
18454
- .setAngularDamping(options.angularDamping ?? 0)
18455
- .setGravityScale(options.gravityScale ?? 1)
18456
- .lockRotations(options.lockRotation ?? false);
18457
- return descriptor;
18458
- }
18459
- createColliderDescriptor(options) {
18460
- const shape = options.shape;
18461
- const descriptor = shape.type === 'box'
18462
- ? this._rapier.colliderDescFactory.cuboid(shape.width / 2, shape.height / 2)
18463
- : this._rapier.colliderDescFactory.ball(shape.radius);
18464
- const offset = getOffset(shape);
18465
- const groups = toPackedCollisionGroups(options.collisionFilter);
18466
- descriptor
18467
- .setTranslation(offset.x, offset.y)
18468
- .setRotation(shape.type === 'box' ? (shape.offsetRotation ?? 0) : 0)
18469
- .setSensor(options.trigger ?? false)
18470
- .setFriction(options.friction ?? 0.5)
18471
- .setRestitution(options.restitution ?? 0)
18472
- .setDensity(options.density ?? 1)
18473
- .setCollisionGroups(groups)
18474
- .setSolverGroups(groups);
18475
- const collisionEvents = this._rapier.activeCollisionEvents;
18476
- if (typeof collisionEvents === 'number') {
18477
- descriptor.setActiveEvents(collisionEvents);
18478
- }
18479
- return descriptor;
18480
- }
18481
- applyStepDelta(deltaSeconds) {
18482
- const worldWithTimestep = this._world;
18483
- if (typeof worldWithTimestep.timestep === 'number') {
18484
- worldWithTimestep.timestep = deltaSeconds;
18485
- }
18486
- if (worldWithTimestep.integrationParameters
18487
- && typeof worldWithTimestep.integrationParameters.dt === 'number') {
18488
- worldWithTimestep.integrationParameters.dt = deltaSeconds;
18489
- }
18490
- }
18491
- drainCollisionEvents() {
18492
- this._eventQueue.drainCollisionEvents((handleA, handleB, started) => {
18493
- const first = this._colliderBindings.get(handleA);
18494
- const second = this._colliderBindings.get(handleB);
18495
- if (!first || !second) {
18496
- return;
18497
- }
18498
- const trigger = first.trigger || second.trigger;
18499
- const event = {
18500
- started,
18501
- trigger,
18502
- first,
18503
- second,
18504
- };
18505
- if (trigger) {
18506
- if (started) {
18507
- this.onTriggerEnter.dispatch(event);
18508
- }
18509
- else {
18510
- this.onTriggerExit.dispatch(event);
18511
- }
18512
- return;
18513
- }
18514
- if (started) {
18515
- this.onCollisionEnter.dispatch(event);
18516
- }
18517
- else {
18518
- this.onCollisionExit.dispatch(event);
18519
- }
18520
- });
18521
- }
18522
- buildBoxPath(shape, bodyX, bodyY, bodyRotation) {
18523
- const halfWidth = shape.width / 2;
18524
- const halfHeight = shape.height / 2;
18525
- const corners = [
18526
- { x: -halfWidth, y: -halfHeight },
18527
- { x: halfWidth, y: -halfHeight },
18528
- { x: halfWidth, y: halfHeight },
18529
- { x: -halfWidth, y: halfHeight },
18530
- ];
18531
- const offset = getOffset(shape);
18532
- const offsetRotation = shape.offsetRotation ?? 0;
18533
- const path = [];
18534
- for (const corner of corners) {
18535
- const local = rotatePoint(corner.x, corner.y, offsetRotation);
18536
- const withOffset = {
18537
- x: local.x + offset.x,
18538
- y: local.y + offset.y,
18539
- };
18540
- const worldPoint = rotatePoint(withOffset.x, withOffset.y, bodyRotation);
18541
- path.push(bodyX + worldPoint.x, bodyY + worldPoint.y);
18542
- }
18543
- return path;
18544
- }
18545
- }
18546
- const createRapierPhysicsWorld = async (options = {}) => {
18547
- return await RapierPhysicsWorld.create(options);
18548
- };
18549
-
18550
- export { AbstractAssetFactory, AbstractMedia, AbstractWebGl2BatchedRenderer, AbstractWebGl2Renderer, AbstractWebGpuRenderer, AnimatedSprite, Application, ApplicationStatus, ArcadeStickGamepadMapping, AudioAnalyser, BinaryFactory, BlendModes, BlurFilter, Bounds, BufferTypes, BufferUsage, BundleLoadError, CacheFirstStrategy, CallbackRenderPass, Capabilities, ChannelOffset, ChannelSize, Circle, Clock, CollisionType, Color, ColorAffector, ColorFilter, Container, Drawable, Ellipse, FactoryRegistry, Filter, Flags, FontFactory, ForceAffector, GameCubeGamepadMapping, Gamepad, GamepadChannel, GamepadControl, GamepadMapping, GamepadMappingFamily, GamepadPromptLayouts, GenericDualAnalogGamepadMapping, Graphics, ImageFactory, IndexedDbDatabase, IndexedDbStore, Input, InputManager, Interval, JoyConLeftGamepadMapping, JoyConRightGamepadMapping, Json, JsonFactory, Keyboard, Line, Loader, Matrix, Mesh, Music, MusicFactory, NetworkOnlyStrategy, ObservableSize, ObservableVector, Particle, ParticleOptions, ParticleSystem, PlayStationGamepadMapping, Pointer, PointerState, PointerStateFlag, PolarVector, Polygon, Quadtree, Random, RapierPhysicsBinding, RapierPhysicsWorld, Rectangle, RenderBackendType, RenderNode, RenderTarget, RenderTargetPass, RenderTexture, RendererRegistry, RenderingPrimitives, Sampler, ScaleAffector, ScaleModes, Scene, SceneManager, SceneNode, Segment, Shader, ShaderAttribute, ShaderPrimitives, ShaderUniform, Signal, Size, Sound, SoundFactory, Sprite, SpriteFlags, Spritesheet, SteamControllerGamepadMapping, SvgAsset, SvgFactory, SwitchProGamepadMapping, Text, TextAsset, TextFactory, TextStyle, Texture, TextureFactory, Time, Timer, TorqueAffector, UniversalEmitter, Vector, Video, VideoFactory, View, ViewFlags, VoronoiRegion, VttAsset, VttFactory, WasmFactory, WebGl2Backend, WebGl2MeshRenderer, WebGl2ParticleRenderer, WebGl2RenderBuffer, WebGl2ShaderBlock, WebGl2SpriteRenderer, WebGl2VertexArrayObject, WebGpuBackend, WebGpuMeshRenderer, WebGpuParticleRenderer, WebGpuSpriteRenderer, WrapModes, XboxGamepadMapping, bezierCurveTo, buildCircle, buildEllipse, buildLine, buildPath, buildPolygon, buildRectangle, buildStar, builtInGamepadDefinitions, canvasSourceToDataUrl, clamp, createRapierPhysicsWorld, createRenderStats, createWebGl2ShaderProgram, decodeAudioData, defineAssetManifest, degreesPerRadian, degreesToRadians, determineMimeType, emptyArrayBuffer, getAudioContext, getCanvasSourceSize, getCollisionCircleCircle, getCollisionCircleRectangle, getCollisionPolygonCircle, getCollisionRectangleRectangle, getCollisionSat, getDistance, getOfflineAudioContext, getPreciseTime, getTextureSourceSize, getVoronoiRegion$1 as getVoronoiRegion, getWebGpuBlendState, hours, inRange, intersectionCircleCircle, intersectionCircleEllipse, intersectionCirclePoly, intersectionEllipseEllipse, intersectionEllipsePoly, intersectionLineCircle, intersectionLineEllipse, intersectionLineLine, intersectionLinePoly, intersectionLineRect, intersectionPointCircle, intersectionPointEllipse, intersectionPointLine, intersectionPointPoint, intersectionPointPoly, intersectionPointRect, intersectionPolyPoly, intersectionRectCircle, intersectionRectEllipse, intersectionRectPoly, intersectionRectRect, intersectionSat, isAudioContextReady, isPowerOfTwo, lerp, matchesIds, milliseconds, minutes, noop$1 as noop, normalizeIds, onAudioContextReady, parseGamepadDescriptor, quadraticCurveTo, radiansPerDegree, radiansToDegrees, rand, removeArrayItems, resetRenderStats, resolveDefinition, resolveGamepadDefinition, seconds, sign$1 as sign, stopEvent, supportsCodec, supportsEventOptions, supportsIndexedDb, supportsPointerEvents, supportsTouchEvents, supportsWebAudio, tau, trimRotation, webGl2PrimitiveArrayConstructors, webGl2PrimitiveByteSizeMapping, webGl2PrimitiveTypeNames };
18522
+ export { AbstractAssetFactory, AbstractMedia, AbstractWebGl2BatchedRenderer, AbstractWebGl2Renderer, AbstractWebGpuRenderer, AnimatedSprite, Application, ApplicationStatus, ArcadeStickGamepadMapping, AudioAnalyser, BinaryFactory, BlendModes, BlurFilter, Bounds, BufferTypes, BufferUsage, BundleLoadError, CacheFirstStrategy, CallbackRenderPass, Capabilities, ChannelOffset, ChannelSize, Circle, Clock, CollisionType, Color, ColorAffector, ColorFilter, Container, Drawable, Ellipse, FactoryRegistry, Filter, Flags, FontFactory, ForceAffector, GameCubeGamepadMapping, Gamepad, GamepadChannel, GamepadControl, GamepadMapping, GamepadMappingFamily, GamepadPromptLayouts, GenericDualAnalogGamepadMapping, Graphics, ImageFactory, IndexedDbDatabase, IndexedDbStore, Input, InputManager, Interval, JoyConLeftGamepadMapping, JoyConRightGamepadMapping, Json, JsonFactory, Keyboard, Line, Loader, Matrix, Mesh, Music, MusicFactory, NetworkOnlyStrategy, ObservableSize, ObservableVector, Particle, ParticleOptions, ParticleSystem, PlayStationGamepadMapping, Pointer, PointerState, PointerStateFlag, PolarVector, Polygon, Quadtree, Random, Rectangle, RenderBackendType, RenderNode, RenderTarget, RenderTargetPass, RenderTexture, RendererRegistry, RenderingPrimitives, Sampler, ScaleAffector, ScaleModes, Scene, SceneManager, SceneNode, Segment, Shader, ShaderAttribute, ShaderPrimitives, ShaderUniform, Signal, Size, Sound, SoundFactory, Sprite, SpriteFlags, Spritesheet, SteamControllerGamepadMapping, SvgAsset, SvgFactory, SwitchProGamepadMapping, Text, TextAsset, TextFactory, TextStyle, Texture, TextureFactory, Time, Timer, TorqueAffector, UniversalEmitter, Vector, Video, VideoFactory, View, ViewFlags, VoronoiRegion, VttAsset, VttFactory, WasmFactory, WebGl2Backend, WebGl2MeshRenderer, WebGl2ParticleRenderer, WebGl2RenderBuffer, WebGl2ShaderBlock, WebGl2SpriteRenderer, WebGl2VertexArrayObject, WebGpuBackend, WebGpuMeshRenderer, WebGpuParticleRenderer, WebGpuSpriteRenderer, WrapModes, XboxGamepadMapping, bezierCurveTo, buildCircle, buildEllipse, buildLine, buildPath, buildPolygon, buildRectangle, buildStar, builtInGamepadDefinitions, canvasSourceToDataUrl, clamp, createRenderStats, createWebGl2ShaderProgram, decodeAudioData, defineAssetManifest, degreesPerRadian, degreesToRadians, determineMimeType, emptyArrayBuffer, getAudioContext, getCanvasSourceSize, getCollisionCircleCircle, getCollisionCircleRectangle, getCollisionPolygonCircle, getCollisionRectangleRectangle, getCollisionSat, getDistance, getOfflineAudioContext, getPreciseTime, getTextureSourceSize, getVoronoiRegion$1 as getVoronoiRegion, getWebGpuBlendState, hours, inRange, intersectionCircleCircle, intersectionCircleEllipse, intersectionCirclePoly, intersectionEllipseEllipse, intersectionEllipsePoly, intersectionLineCircle, intersectionLineEllipse, intersectionLineLine, intersectionLinePoly, intersectionLineRect, intersectionPointCircle, intersectionPointEllipse, intersectionPointLine, intersectionPointPoint, intersectionPointPoly, intersectionPointRect, intersectionPolyPoly, intersectionRectCircle, intersectionRectEllipse, intersectionRectPoly, intersectionRectRect, intersectionSat, isAudioContextReady, isPowerOfTwo, lerp, matchesIds, maxPointers, milliseconds, minutes, noop$1 as noop, normalizeIds, onAudioContextReady, parseGamepadDescriptor, pointerSlotSize, quadraticCurveTo, radiansPerDegree, radiansToDegrees, rand, removeArrayItems, resetRenderStats, resolveDefinition, resolveGamepadDefinition, seconds, sign$1 as sign, stopEvent, supportsCodec, supportsEventOptions, supportsIndexedDb, supportsPointerEvents, supportsTouchEvents, supportsWebAudio, tau, trimRotation, webGl2PrimitiveArrayConstructors, webGl2PrimitiveByteSizeMapping, webGl2PrimitiveTypeNames };
18551
18523
  //# sourceMappingURL=exo.esm.js.map