excalibur 0.32.0-alpha.19 → 0.32.0-alpha.20

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.
@@ -1,4 +1,4 @@
1
- /*! excalibur - 0.32.0-alpha.19+57bc004 - 2025-12-22
1
+ /*! excalibur - 0.32.0-alpha.20+e0a1b33 - 2025-12-23
2
2
  https://github.com/excaliburjs/Excalibur
3
3
  Copyright (c) 2025 Excalibur.js <https://github.com/excaliburjs/Excalibur/graphs/contributors>
4
4
  Licensed BSD-2-Clause
@@ -508,18 +508,13 @@ function approximatelyEqual(val1, val2, tolerance) {
508
508
  return Math.abs(val1 - val2) < tolerance;
509
509
  }
510
510
  function canonicalizeAngle(angle) {
511
- let tmpAngle = angle;
512
- if (angle >= TwoPI) {
513
- while (tmpAngle >= TwoPI) {
514
- tmpAngle -= TwoPI;
515
- }
516
- }
517
- if (angle < 0) {
518
- while (tmpAngle < 0) {
519
- tmpAngle += TwoPI;
520
- }
521
- }
522
- return tmpAngle;
511
+ const TWO_PI = 2 * Math.PI;
512
+ return (angle % TWO_PI + TWO_PI) % TWO_PI;
513
+ }
514
+ function angleDifference(angle1, angle2) {
515
+ const TWO_PI = 2 * Math.PI;
516
+ const diff = Math.abs(angle1 - angle2);
517
+ return Math.min(diff, TWO_PI - diff);
523
518
  }
524
519
  function toDegrees(radians) {
525
520
  return 180 / Math.PI * radians;
@@ -5923,18 +5918,32 @@ class SystemManager {
5923
5918
  * @param elapsed time in milliseconds
5924
5919
  */
5925
5920
  updateSystems(type, scene, elapsed) {
5921
+ var _a, _b, _c;
5926
5922
  const systems = this.systems.filter((s) => s.systemType === type);
5927
- for (const s of systems) {
5928
- if (s.preupdate) {
5929
- s.preupdate(scene, elapsed);
5923
+ const stats = (_c = (_b = (_a = scene == null ? void 0 : scene.engine) == null ? void 0 : _a.stats) == null ? void 0 : _b.currFrame) != null ? _c : { systemDuration: {} };
5924
+ let startTime;
5925
+ let endTime;
5926
+ const systemsLength = systems.length;
5927
+ for (let i = 0; i < systemsLength; i++) {
5928
+ if (systems[i].preupdate) {
5929
+ startTime = performance.now();
5930
+ systems[i].preupdate(scene, elapsed);
5931
+ endTime = performance.now();
5932
+ stats.systemDuration[`${type}:${systems[i].constructor.name}.preupdate`] = endTime - startTime;
5930
5933
  }
5931
5934
  }
5932
- for (const s of systems) {
5933
- s.update(elapsed);
5935
+ for (let i = 0; i < systemsLength; i++) {
5936
+ startTime = performance.now();
5937
+ systems[i].update(elapsed);
5938
+ endTime = performance.now();
5939
+ stats.systemDuration[`${type}:${systems[i].constructor.name}.update`] = endTime - startTime;
5934
5940
  }
5935
- for (const s of systems) {
5936
- if (s.postupdate) {
5937
- s.postupdate(scene, elapsed);
5941
+ for (let i = 0; i < systemsLength; i++) {
5942
+ if (systems[i].postupdate) {
5943
+ startTime = performance.now();
5944
+ systems[i].postupdate(scene, elapsed);
5945
+ endTime = performance.now();
5946
+ stats.systemDuration[`${type}:${systems[i].constructor.name}.postupdate`] = endTime - startTime;
5938
5947
  }
5939
5948
  }
5940
5949
  }
@@ -7192,10 +7201,11 @@ const getDefaultPhysicsConfig = () => ({
7192
7201
  surfaceEpsilon: 0.1
7193
7202
  },
7194
7203
  bodies: {
7195
- canSleepByDefault: false,
7204
+ canSleepByDefault: true,
7196
7205
  sleepEpsilon: 0.07,
7197
7206
  wakeThreshold: 0.07 * 3,
7198
- sleepBias: 0.9,
7207
+ sleepBias: 0.5,
7208
+ sleepTimeThreshold: 1e3,
7199
7209
  defaultMass: 10
7200
7210
  },
7201
7211
  spatialPartition: SpatialPartitionStrategy.SparseHashGrid,
@@ -7835,23 +7845,6 @@ class CollisionContact {
7835
7845
  this.bodyB = this.colliderB.owner.get(BodyComponent);
7836
7846
  }
7837
7847
  }
7838
- /**
7839
- * Match contact awake state, except if body's are Fixed
7840
- */
7841
- matchAwake() {
7842
- const bodyA = this.bodyA;
7843
- const bodyB = this.bodyB;
7844
- if (bodyA && bodyB) {
7845
- if (bodyA.isSleeping !== bodyB.isSleeping) {
7846
- if (bodyA.isSleeping && bodyA.collisionType !== CollisionType.Fixed && bodyB.sleepMotion >= bodyA.wakeThreshold) {
7847
- bodyA.isSleeping = false;
7848
- }
7849
- if (bodyB.isSleeping && bodyB.collisionType !== CollisionType.Fixed && bodyA.sleepMotion >= bodyB.wakeThreshold) {
7850
- bodyB.isSleeping = false;
7851
- }
7852
- }
7853
- }
7854
- }
7855
7848
  isCanceled() {
7856
7849
  return this._canceled;
7857
7850
  }
@@ -12079,7 +12072,7 @@ class GraphicsSystem extends System {
12079
12072
  const optionalBody = ancestor == null ? void 0 : ancestor.get(BodyComponent);
12080
12073
  if (transform) {
12081
12074
  let tx = transform.get();
12082
- if (optionalBody) {
12075
+ if (optionalBody && !optionalBody.isSleeping) {
12083
12076
  if (this._engine.fixedUpdateTimestep && optionalBody.__oldTransformCaptured && optionalBody.enableFixedUpdateInterpolate) {
12084
12077
  const blend = this._engine.currentFrameLagMs / this._engine.fixedUpdateTimestep;
12085
12078
  tx = blendTransform(optionalBody.oldTransform, transform.get(), blend, this._targetInterpolationTransform);
@@ -20746,11 +20739,14 @@ const _BodyComponent = class _BodyComponent2 extends Component {
20746
20739
  this.dependencies = [TransformComponent, MotionComponent];
20747
20740
  this.id = createId("body", _BodyComponent2._ID++);
20748
20741
  this.events = new EventEmitter();
20742
+ this.island = null;
20749
20743
  this.oldTransform = new Transform();
20750
20744
  this.__oldTransformCaptured = false;
20751
20745
  this.enableFixedUpdateInterpolate = true;
20746
+ this.sleepTime = 0;
20752
20747
  this.collisionType = CollisionType.PreventCollision;
20753
20748
  this.group = CollisionGroup.All;
20749
+ this.canSleep = this.collisionType === CollisionType.Active;
20754
20750
  this._sleeping = false;
20755
20751
  this.bounciness = 0.2;
20756
20752
  this.friction = 0.99;
@@ -20793,6 +20789,12 @@ const _BodyComponent = class _BodyComponent2 extends Component {
20793
20789
  this.sleepMotion = this._bodyConfig.sleepEpsilon * 5;
20794
20790
  this.wakeThreshold = this._bodyConfig.wakeThreshold;
20795
20791
  }
20792
+ get canFallAsleep() {
20793
+ return this.canSleep && this.collisionType === CollisionType.Active && this.sleepMotion < this._bodyConfig.sleepEpsilon;
20794
+ }
20795
+ get canWakeUp() {
20796
+ return this.collisionType === CollisionType.Active && this.sleepMotion > this.wakeThreshold;
20797
+ }
20796
20798
  /**
20797
20799
  * Called by excalibur to update defaults
20798
20800
  * @param config
@@ -20825,7 +20827,7 @@ const _BodyComponent = class _BodyComponent2 extends Component {
20825
20827
  * Whether this body is sleeping or not
20826
20828
  */
20827
20829
  get isSleeping() {
20828
- return this._sleeping;
20830
+ return this.canSleep && this._sleeping;
20829
20831
  }
20830
20832
  /**
20831
20833
  * Set the sleep state of the body
@@ -20835,30 +20837,51 @@ const _BodyComponent = class _BodyComponent2 extends Component {
20835
20837
  setSleeping(sleeping) {
20836
20838
  this.isSleeping = sleeping;
20837
20839
  }
20838
- set isSleeping(sleeping) {
20839
- this._sleeping = sleeping;
20840
- if (!sleeping) {
20841
- this.sleepMotion = this._bodyConfig.sleepEpsilon * 5;
20842
- } else {
20840
+ wake() {
20841
+ var _a;
20842
+ if (this._sleeping && this.collisionType === CollisionType.Active) {
20843
+ this._sleeping = false;
20844
+ (_a = this.owner) == null ? void 0 : _a.removeTag("ex.is_sleeping");
20845
+ this.sleepMotion = this._bodyConfig.sleepEpsilon * 2;
20846
+ this.sleepTime = 0;
20847
+ }
20848
+ }
20849
+ sleep() {
20850
+ var _a;
20851
+ if (!this._sleeping && this.canSleep) {
20852
+ this._sleeping = true;
20853
+ (_a = this.owner) == null ? void 0 : _a.addTag("ex.is_sleeping");
20843
20854
  this.vel = Vector.Zero;
20844
20855
  this.acc = Vector.Zero;
20845
20856
  this.angularVelocity = 0;
20846
20857
  this.sleepMotion = 0;
20847
20858
  }
20848
20859
  }
20860
+ set isSleeping(sleeping) {
20861
+ if (sleeping) {
20862
+ this.sleep();
20863
+ } else {
20864
+ this.wake();
20865
+ }
20866
+ }
20849
20867
  /**
20850
20868
  * Update body's {@apilink BodyComponent.sleepMotion} for the purpose of sleeping
20851
20869
  */
20852
- updateMotion() {
20853
- if (this._sleeping) {
20854
- this.isSleeping = true;
20870
+ updateMotion(duration) {
20871
+ if (this.collisionType !== CollisionType.Active) {
20872
+ return;
20855
20873
  }
20856
- const currentMotion = this.vel.magnitude * this.vel.magnitude + Math.abs(this.angularVelocity * this.angularVelocity);
20857
- const bias = this._bodyConfig.sleepBias;
20858
- this.sleepMotion = bias * this.sleepMotion + (1 - bias) * currentMotion;
20874
+ const effectiveVel = this.pos.sub(this.oldPos);
20875
+ const effectiveAngularVel = angleDifference(canonicalizeAngle(this.rotation), canonicalizeAngle(this.oldRotation));
20876
+ const vel = effectiveVel.magnitude;
20877
+ const omega = effectiveAngularVel;
20878
+ const currentMotion = vel * vel + omega * omega;
20879
+ const bias = Math.pow(this._bodyConfig.sleepBias, duration / 1e3);
20880
+ const previousMotion = this.sleepMotion;
20881
+ this.sleepMotion = bias * previousMotion + (1 - bias) * currentMotion;
20859
20882
  this.sleepMotion = clamp(this.sleepMotion, 0, 10 * this._bodyConfig.sleepEpsilon);
20860
- if (this.canSleep && this.sleepMotion < this._bodyConfig.sleepEpsilon) {
20861
- this.isSleeping = true;
20883
+ if (this.canFallAsleep) {
20884
+ this.sleepTime += duration;
20862
20885
  }
20863
20886
  }
20864
20887
  /**
@@ -22087,7 +22110,6 @@ class RealisticSolver {
22087
22110
  "beforecollisionresolve",
22088
22111
  new CollisionPreSolveEvent(contact.colliderB, contact.colliderA, Side.getOpposite(side), contact.mtv.negate(), contact)
22089
22112
  );
22090
- contact.matchAwake();
22091
22113
  }
22092
22114
  const finishedContactIds = Array.from(this.idToContactConstraint.keys());
22093
22115
  for (let i = 0; i < contacts.length; i++) {
@@ -22102,7 +22124,7 @@ class RealisticSolver {
22102
22124
  const colliderA = contact.colliderA;
22103
22125
  const bodyB = contact.bodyB;
22104
22126
  const colliderB = contact.colliderB;
22105
- if (bodyA && bodyB) {
22127
+ if (bodyA && bodyB && (!bodyA.isSleeping || !bodyB.isSleeping)) {
22106
22128
  for (let j = 0; j < contact.points.length; j++) {
22107
22129
  const point2 = contact.points[j];
22108
22130
  const normal = contact.normal;
@@ -22161,8 +22183,6 @@ class RealisticSolver {
22161
22183
  if (bodyA.collisionType === CollisionType.Passive || bodyB.collisionType === CollisionType.Passive) {
22162
22184
  continue;
22163
22185
  }
22164
- bodyA.updateMotion();
22165
- bodyB.updateMotion();
22166
22186
  }
22167
22187
  const side = Side.fromDirection(contact.mtv);
22168
22188
  contact.colliderA.events.emit(
@@ -22198,6 +22218,9 @@ class RealisticSolver {
22198
22218
  const contact = contacts[i];
22199
22219
  const bodyA = contact.bodyA;
22200
22220
  const bodyB = contact.bodyB;
22221
+ if (bodyA.isSleeping && bodyB.isSleeping) {
22222
+ continue;
22223
+ }
22201
22224
  if (bodyA && bodyB) {
22202
22225
  const contactPoints = (_a = this.idToContactConstraint.get(contact.id)) != null ? _a : [];
22203
22226
  for (const point2 of contactPoints) {
@@ -22226,6 +22249,9 @@ class RealisticSolver {
22226
22249
  const contact = contacts[i2];
22227
22250
  const bodyA = contact.bodyA;
22228
22251
  const bodyB = contact.bodyB;
22252
+ if (bodyA.isSleeping && bodyB.isSleeping) {
22253
+ continue;
22254
+ }
22229
22255
  if (bodyA && bodyB) {
22230
22256
  if (bodyA.collisionType === CollisionType.Passive || bodyB.collisionType === CollisionType.Passive) {
22231
22257
  continue;
@@ -22277,6 +22303,9 @@ class RealisticSolver {
22277
22303
  const contact = contacts[i2];
22278
22304
  const bodyA = contact.bodyA;
22279
22305
  const bodyB = contact.bodyB;
22306
+ if (bodyA.isSleeping && bodyB.isSleeping) {
22307
+ continue;
22308
+ }
22280
22309
  if (bodyA && bodyB) {
22281
22310
  if (bodyA.collisionType === CollisionType.Passive || bodyB.collisionType === CollisionType.Passive) {
22282
22311
  continue;
@@ -22318,17 +22347,20 @@ class MotionSystem extends System {
22318
22347
  this.physics = physics;
22319
22348
  this.systemType = SystemType.Update;
22320
22349
  this._physicsConfigDirty = false;
22321
- this.query = this.world.query({
22350
+ this.query = this._createPhysicsQuery(world, physics);
22351
+ physics.$configUpdate.subscribe(() => {
22352
+ this._physicsConfigDirty = true;
22353
+ });
22354
+ }
22355
+ _createPhysicsQuery(world, physics) {
22356
+ return world.query({
22322
22357
  components: {
22323
22358
  all: [TransformComponent, MotionComponent]
22324
22359
  },
22325
22360
  tags: {
22326
- not: this.physics.config.integration.onScreenOnly ? ["ex.offscreen"] : []
22361
+ not: physics.config.integration.onScreenOnly ? ["ex.offscreen", "ex.is_sleeping"] : ["ex.is_sleeping"]
22327
22362
  }
22328
22363
  });
22329
- physics.$configUpdate.subscribe(() => {
22330
- this._physicsConfigDirty = true;
22331
- });
22332
22364
  }
22333
22365
  update(elapsed) {
22334
22366
  let transform;
@@ -22360,14 +22392,7 @@ class MotionSystem extends System {
22360
22392
  }
22361
22393
  if (this._physicsConfigDirty) {
22362
22394
  this._physicsConfigDirty = false;
22363
- this.query = this.world.query({
22364
- components: {
22365
- all: [TransformComponent, MotionComponent]
22366
- },
22367
- tags: {
22368
- not: this.physics.config.integration.onScreenOnly ? ["ex.offscreen"] : []
22369
- }
22370
- });
22395
+ this.query = this._createPhysicsQuery(this.world, this.physics);
22371
22396
  }
22372
22397
  }
22373
22398
  captureOldTransformWithChildren(entity) {
@@ -22379,6 +22404,105 @@ class MotionSystem extends System {
22379
22404
  }
22380
22405
  }
22381
22406
  MotionSystem.priority = SystemPriority.Higher;
22407
+ class Island {
22408
+ constructor(config) {
22409
+ this.config = config;
22410
+ this.bodies = [];
22411
+ this.contacts = [];
22412
+ this.isSleeping = false;
22413
+ }
22414
+ wake() {
22415
+ this.isSleeping = false;
22416
+ for (const body of this.bodies) {
22417
+ body.wake();
22418
+ }
22419
+ }
22420
+ sleep() {
22421
+ this.isSleeping = true;
22422
+ for (const body of this.bodies) {
22423
+ body.sleep();
22424
+ }
22425
+ }
22426
+ updateSleepState(elapsed) {
22427
+ let islandHasMotion = false;
22428
+ let allBodiesCanSleep = true;
22429
+ for (const body of this.bodies) {
22430
+ body.updateMotion(elapsed);
22431
+ if (!body.canFallAsleep) {
22432
+ allBodiesCanSleep && (allBodiesCanSleep = false);
22433
+ }
22434
+ if (body.canWakeUp) {
22435
+ islandHasMotion || (islandHasMotion = true);
22436
+ }
22437
+ }
22438
+ if (islandHasMotion) {
22439
+ this.wake();
22440
+ } else if (allBodiesCanSleep) {
22441
+ let minSleepTime = Infinity;
22442
+ for (const body of this.bodies) {
22443
+ minSleepTime = Math.min(minSleepTime, body.sleepTime);
22444
+ }
22445
+ if (minSleepTime > this.config.sleepTimeThreshold) {
22446
+ this.sleep();
22447
+ }
22448
+ }
22449
+ }
22450
+ }
22451
+ function buildContactIslands(config, bodies, contacts) {
22452
+ const parent = /* @__PURE__ */ new Map();
22453
+ function find(body) {
22454
+ if (!parent.has(body)) {
22455
+ parent.set(body, body);
22456
+ }
22457
+ if (parent.get(body) !== body) {
22458
+ parent.set(body, find(parent.get(body)));
22459
+ }
22460
+ return parent.get(body);
22461
+ }
22462
+ function union(bodyA, bodyB) {
22463
+ const rootA = find(bodyA);
22464
+ const rootB = find(bodyB);
22465
+ if (rootA !== rootB) {
22466
+ parent.set(rootA, rootB);
22467
+ }
22468
+ }
22469
+ const bodyToContacts = /* @__PURE__ */ new Map();
22470
+ for (const contact of contacts) {
22471
+ if (contact.bodyA.collisionType === CollisionType.Active && contact.bodyB.collisionType === CollisionType.Active) {
22472
+ union(contact.bodyA, contact.bodyB);
22473
+ }
22474
+ if (!bodyToContacts.has(contact.bodyA)) {
22475
+ bodyToContacts.set(contact.bodyA, []);
22476
+ }
22477
+ if (!bodyToContacts.has(contact.bodyB)) {
22478
+ bodyToContacts.set(contact.bodyB, []);
22479
+ }
22480
+ if (contact.bodyA.collisionType === CollisionType.Active) {
22481
+ bodyToContacts.get(contact.bodyA).push(contact);
22482
+ }
22483
+ if (contact.bodyB.collisionType === CollisionType.Active) {
22484
+ bodyToContacts.get(contact.bodyB).push(contact);
22485
+ }
22486
+ }
22487
+ const islandMap = /* @__PURE__ */ new Map();
22488
+ for (const body of bodies) {
22489
+ if (body.collisionType !== CollisionType.Active) {
22490
+ continue;
22491
+ }
22492
+ const root = find(body);
22493
+ if (!islandMap.has(root)) {
22494
+ islandMap.set(root, []);
22495
+ }
22496
+ islandMap.get(root).push(body);
22497
+ }
22498
+ return Array.from(islandMap.values()).map((bodies2) => {
22499
+ const island = new Island(config);
22500
+ island.bodies = bodies2;
22501
+ bodies2.forEach((b) => b.island = island);
22502
+ island.contacts = Array.from(new Set(bodies2.flatMap((b) => bodyToContacts.get(b))));
22503
+ return island;
22504
+ });
22505
+ }
22382
22506
  class CollisionSystem extends System {
22383
22507
  constructor(world, _physics) {
22384
22508
  super();
@@ -22387,6 +22511,7 @@ class CollisionSystem extends System {
22387
22511
  this._configDirty = false;
22388
22512
  this._lastFrameContacts = /* @__PURE__ */ new Map();
22389
22513
  this._currentFrameContacts = /* @__PURE__ */ new Map();
22514
+ this._bodies = [];
22390
22515
  this._arcadeSolver = new ArcadeSolver(_physics.config.arcade);
22391
22516
  this._realisticSolver = new RealisticSolver(_physics.config.realistic);
22392
22517
  this._physics.$configUpdate.subscribe(() => this._configDirty = true);
@@ -22410,6 +22535,17 @@ class CollisionSystem extends System {
22410
22535
  }
22411
22536
  });
22412
22537
  this._motionSystem = world.get(MotionSystem);
22538
+ this.bodyQuery = world.query([BodyComponent]);
22539
+ this.bodyQuery.entityAdded$.subscribe((e) => {
22540
+ this._bodies.push(e.get(BodyComponent));
22541
+ });
22542
+ this.bodyQuery.entityRemoved$.subscribe((e) => {
22543
+ const body = e.get(BodyComponent);
22544
+ const indexOf = this._bodies.indexOf(body);
22545
+ if (indexOf > -1) {
22546
+ this._bodies.splice(indexOf, 1);
22547
+ }
22548
+ });
22413
22549
  }
22414
22550
  get _processor() {
22415
22551
  return this._physics.collisionProcessor;
@@ -22455,7 +22591,13 @@ class CollisionSystem extends System {
22455
22591
  }
22456
22592
  if (pairs.length) {
22457
22593
  contacts = this._processor.narrowphase(pairs, (_d = (_c = (_b = this._engine) == null ? void 0 : _b.debug) == null ? void 0 : _c.stats) == null ? void 0 : _d.currFrame);
22458
- contacts = solver.solve(contacts);
22594
+ if (this._physics.config.solver === SolverStrategy.Realistic) {
22595
+ const islands = buildContactIslands(this._physics.config.bodies, this._bodies, contacts);
22596
+ for (const island of islands) {
22597
+ island.updateSleepState(elapsed / substep);
22598
+ }
22599
+ }
22600
+ contacts = solver.solve(contacts, elapsed / substep);
22459
22601
  for (const contact of contacts) {
22460
22602
  if (contact.isCanceled()) {
22461
22603
  continue;
@@ -22511,6 +22653,8 @@ class CollisionSystem extends System {
22511
22653
  if (!this._currentFrameContacts.has(id)) {
22512
22654
  const colliderA = c.colliderA;
22513
22655
  const colliderB = c.colliderB;
22656
+ c.bodyA.isSleeping = false;
22657
+ c.bodyB.isSleeping = false;
22514
22658
  const side = Side.fromDirection(c.mtv);
22515
22659
  const opposite = Side.getOpposite(side);
22516
22660
  colliderA.events.emit("collisionend", new CollisionEndEvent(colliderA, colliderB, side, c));
@@ -27150,7 +27294,7 @@ class DebugSystem extends System {
27150
27294
  cursor = cursor.add(lineHeight);
27151
27295
  }
27152
27296
  if (bodySettings.showAll || bodySettings.showMotion) {
27153
- this._graphicsContext.debug.drawText(`motion(${body.sleepMotion})`, cursor);
27297
+ this._graphicsContext.debug.drawText(`motion(${body.sleepMotion.toFixed(3)})`, cursor);
27154
27298
  cursor = cursor.add(lineHeight);
27155
27299
  }
27156
27300
  if (bodySettings.showAll || bodySettings.showSleeping) {
@@ -29457,6 +29601,7 @@ class FrameStats {
29457
29601
  drawnImages: 0,
29458
29602
  rendererSwaps: 0
29459
29603
  };
29604
+ this.systemDuration = {};
29460
29605
  }
29461
29606
  /**
29462
29607
  * Zero out values or clone other IFrameStat stats. Allows instance reuse.
@@ -29472,6 +29617,9 @@ class FrameStats {
29472
29617
  this.actors.ui = otherStats.actors.ui;
29473
29618
  this.duration.update = otherStats.duration.update;
29474
29619
  this.duration.draw = otherStats.duration.draw;
29620
+ for (const key in otherStats.systemDuration) {
29621
+ this.systemDuration[key] = otherStats.systemDuration[key];
29622
+ }
29475
29623
  this._physicsStats.reset(otherStats.physics);
29476
29624
  this.graphics.drawCalls = otherStats.graphics.drawCalls;
29477
29625
  this.graphics.drawnImages = otherStats.graphics.drawnImages;
@@ -29482,6 +29630,9 @@ class FrameStats {
29482
29630
  this.duration.update = this.duration.draw = 0;
29483
29631
  this._physicsStats.reset();
29484
29632
  this.graphics.drawnImages = this.graphics.drawCalls = this.graphics.rendererSwaps = 0;
29633
+ for (const key in this.systemDuration) {
29634
+ this.systemDuration[key] = 0;
29635
+ }
29485
29636
  }
29486
29637
  }
29487
29638
  /**
@@ -33627,7 +33778,7 @@ class Semaphore {
33627
33778
  this._count += count;
33628
33779
  }
33629
33780
  }
33630
- const EX_VERSION = "0.32.0-alpha.19+57bc004";
33781
+ const EX_VERSION = "0.32.0-alpha.20+e0a1b33";
33631
33782
  polyfill();
33632
33783
  export {
33633
33784
  ActionCompleteEvent,
@@ -33950,6 +34101,7 @@ export {
33950
34101
  WheelDeltaMode,
33951
34102
  WheelEvent,
33952
34103
  World,
34104
+ angleDifference,
33953
34105
  approximatelyEqual,
33954
34106
  assert,
33955
34107
  canonicalizeAngle,