dacha 0.18.0-alpha.7 → 0.18.0-alpha.9

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.
Files changed (71) hide show
  1. package/build/contrib/components/rigid-body/index.d.ts +100 -14
  2. package/build/contrib/components/rigid-body/index.js +183 -22
  3. package/build/contrib/systems/animator/index.d.ts +3 -2
  4. package/build/contrib/systems/animator/index.js +6 -3
  5. package/build/contrib/systems/behavior-system/system.d.ts +4 -2
  6. package/build/contrib/systems/behavior-system/system.js +11 -3
  7. package/build/contrib/systems/behavior-system/types.d.ts +7 -10
  8. package/build/contrib/systems/character-controller/index.d.ts +4 -2
  9. package/build/contrib/systems/character-controller/index.js +22 -18
  10. package/build/contrib/systems/character-controller/one-way-validator.d.ts +15 -0
  11. package/build/contrib/systems/character-controller/one-way-validator.js +73 -0
  12. package/build/contrib/systems/game-stats-meter/index.d.ts +4 -3
  13. package/build/contrib/systems/game-stats-meter/index.js +11 -10
  14. package/build/contrib/systems/physics-system/consts.d.ts +11 -0
  15. package/build/contrib/systems/physics-system/consts.js +14 -0
  16. package/build/contrib/systems/physics-system/physics-system.d.ts +2 -2
  17. package/build/contrib/systems/physics-system/physics-system.js +18 -4
  18. package/build/contrib/systems/physics-system/subsystems/collision-detection/index.js +3 -1
  19. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/box-box/check-boxes-intersection.js +5 -3
  20. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/box-box/utils.d.ts +1 -0
  21. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/box-box/utils.js +2 -1
  22. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/box-capsule/check-box-and-capsule-intersection.js +4 -1
  23. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/box-segment/check-box-and-segment-intersection.js +4 -2
  24. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/common/points.d.ts +2 -0
  25. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/common/points.js +16 -0
  26. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/index.js +2 -0
  27. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/tests/helpers.js +3 -14
  28. package/build/contrib/systems/physics-system/subsystems/collision-detection/shape-cast-checkers/tests/helpers.js +2 -7
  29. package/build/contrib/systems/physics-system/subsystems/collision-detection/tests/assertions.d.ts +1 -0
  30. package/build/contrib/systems/physics-system/subsystems/collision-detection/tests/assertions.js +7 -0
  31. package/build/contrib/systems/physics-system/subsystems/constraint-solver/contact-state-manager.d.ts +63 -0
  32. package/build/contrib/systems/physics-system/subsystems/constraint-solver/contact-state-manager.js +170 -0
  33. package/build/contrib/systems/physics-system/subsystems/constraint-solver/contact-utils.d.ts +5 -0
  34. package/build/contrib/systems/physics-system/subsystems/constraint-solver/contact-utils.js +41 -0
  35. package/build/contrib/systems/physics-system/subsystems/constraint-solver/impulse-utils.d.ts +16 -0
  36. package/build/contrib/systems/physics-system/subsystems/constraint-solver/impulse-utils.js +77 -0
  37. package/build/contrib/systems/physics-system/subsystems/constraint-solver/index.d.ts +53 -4
  38. package/build/contrib/systems/physics-system/subsystems/constraint-solver/index.js +334 -116
  39. package/build/contrib/systems/physics-system/subsystems/constraint-solver/one-way-validator.d.ts +11 -0
  40. package/build/contrib/{utils → systems/physics-system/subsystems/constraint-solver}/one-way-validator.js +22 -25
  41. package/build/contrib/systems/physics-system/subsystems/constraint-solver/sleep-support-tracker.d.ts +13 -0
  42. package/build/contrib/systems/physics-system/subsystems/constraint-solver/sleep-support-tracker.js +42 -0
  43. package/build/contrib/systems/physics-system/subsystems/constraint-solver/tests/helpers.d.ts +4 -0
  44. package/build/contrib/systems/physics-system/subsystems/constraint-solver/tests/helpers.js +20 -0
  45. package/build/contrib/systems/physics-system/subsystems/physics/index.d.ts +15 -4
  46. package/build/contrib/systems/physics-system/subsystems/physics/index.js +131 -53
  47. package/build/contrib/systems/physics-system/subsystems/physics/mass-properties.d.ts +2 -0
  48. package/build/contrib/systems/physics-system/subsystems/physics/mass-properties.js +55 -0
  49. package/build/contrib/systems/physics-system/tests/helpers.d.ts +5 -2
  50. package/build/contrib/systems/physics-system/tests/helpers.js +11 -2
  51. package/build/contrib/systems/physics-system/types.d.ts +6 -0
  52. package/build/contrib/systems/renderer/filters/index.d.ts +1 -1
  53. package/build/contrib/systems/renderer/filters/index.js +1 -1
  54. package/build/contrib/systems/renderer/material/index.d.ts +2 -1
  55. package/build/contrib/systems/renderer/material/index.js +1 -1
  56. package/build/contrib/systems/renderer/renderer.d.ts +2 -2
  57. package/build/contrib/systems/renderer/renderer.js +3 -4
  58. package/build/contrib/systems/renderer/types.d.ts +0 -3
  59. package/build/engine/engine.js +4 -1
  60. package/build/engine/game-loop.d.ts +3 -1
  61. package/build/engine/game-loop.js +8 -5
  62. package/build/engine/scene/scene-manager.d.ts +4 -1
  63. package/build/engine/scene/scene-manager.js +5 -1
  64. package/build/engine/system/index.d.ts +1 -1
  65. package/build/engine/system/system.d.ts +10 -11
  66. package/build/engine/system/system.js +5 -2
  67. package/build/engine/time.d.ts +54 -0
  68. package/build/engine/time.js +77 -0
  69. package/build/index.d.ts +2 -1
  70. package/package.json +1 -1
  71. package/build/contrib/utils/one-way-validator.d.ts +0 -12
@@ -3,8 +3,9 @@ import { SceneSystem } from '../../../engine/system';
3
3
  import { Vector2, VectorOps } from '../../../engine/math-lib';
4
4
  import { Collider, CharacterBody, RigidBody, Transform, } from '../../components';
5
5
  import { PhysicsAPI } from '../physics-system';
6
- import { OneWayValidator } from '../../utils/one-way-validator';
7
6
  import { CharacterHit } from '../../events';
7
+ import { RemoveActor } from '../../../engine/events';
8
+ import { OneWayValidator } from './one-way-validator';
8
9
  import { clipAgainstNormal } from './utils';
9
10
  const DISTANCE_EPSILON = 0.000001;
10
11
  const SNAP_EPSILON = 0.000001;
@@ -21,19 +22,26 @@ const SNAP_EPSILON = 0.000001;
21
22
  export class CharacterController extends SceneSystem {
22
23
  actorQuery;
23
24
  world;
25
+ time;
24
26
  oneWayValidator;
25
27
  constructor(options) {
26
28
  super();
27
29
  this.world = options.world;
30
+ this.time = options.time;
28
31
  this.actorQuery = new ActorQuery({
29
32
  scene: options.scene,
30
33
  filter: [CharacterBody, Transform, Collider, RigidBody],
31
34
  });
35
+ this.actorQuery.addEventListener(RemoveActor, this.handleRemoveActor);
32
36
  this.oneWayValidator = new OneWayValidator();
33
37
  }
34
38
  onSceneDestroy() {
39
+ this.actorQuery.removeEventListener(RemoveActor, this.handleRemoveActor);
35
40
  this.actorQuery.destroy();
36
41
  }
42
+ handleRemoveActor = (event) => {
43
+ this.oneWayValidator.delete(event.actor);
44
+ };
37
45
  isBlockingHit(actor, hit) {
38
46
  const rigidBody = hit.actor.getComponent(RigidBody);
39
47
  if (!rigidBody || rigidBody.disabled) {
@@ -42,22 +50,18 @@ export class CharacterController extends SceneSystem {
42
50
  if (!rigidBody?.oneWay || !rigidBody.oneWayNormal) {
43
51
  return true;
44
52
  }
45
- return this.oneWayValidator.validate(hit.actor, actor, hit.normal);
53
+ return this.oneWayValidator.shouldBlock(hit.actor, actor, hit.normal);
46
54
  }
47
55
  isWalkable(actor, normal) {
48
56
  const character = actor.getComponent(CharacterBody);
49
57
  return (VectorOps.dotProduct(normal, character.upDirection) >=
50
58
  Math.cos(character.maxSlopeAngle));
51
59
  }
52
- isRecoverableOverlap(actor, hit) {
60
+ isRecoverableOverlap(hit) {
53
61
  const rigidBody = hit.actor.getComponent(RigidBody);
54
- if (!rigidBody || rigidBody.disabled || rigidBody.type === 'dynamic') {
55
- return false;
56
- }
57
- if (!rigidBody.oneWay || !rigidBody.oneWayNormal) {
58
- return true;
59
- }
60
- return this.oneWayValidator.validate(hit.actor, actor, hit.normal);
62
+ return (rigidBody !== undefined &&
63
+ !rigidBody.disabled &&
64
+ rigidBody.type !== 'dynamic');
61
65
  }
62
66
  resetGroundState(character) {
63
67
  if (character.onGround) {
@@ -79,6 +83,7 @@ export class CharacterController extends SceneSystem {
79
83
  const character = actor.getComponent(CharacterBody);
80
84
  const transform = actor.getComponent(Transform);
81
85
  const distance = displacement.magnitude;
86
+ this.oneWayValidator.touch(actor);
82
87
  return physicsApi.castActor({
83
88
  actor,
84
89
  offset: {
@@ -87,7 +92,7 @@ export class CharacterController extends SceneSystem {
87
92
  },
88
93
  direction: displacement,
89
94
  maxDistance: distance + character.skinWidth,
90
- hitFilter,
95
+ hitFilter: (hit) => this.isBlockingHit(actor, hit) && hitFilter(hit),
91
96
  });
92
97
  }
93
98
  castMotion(actor, position, displacement) {
@@ -96,11 +101,11 @@ export class CharacterController extends SceneSystem {
96
101
  VectorOps.dotProduct(displacement, hit.normal) > DISTANCE_EPSILON) {
97
102
  return false;
98
103
  }
99
- return this.isBlockingHit(actor, hit);
104
+ return true;
100
105
  });
101
106
  }
102
107
  castGround(actor, position, displacement) {
103
- return this.cast(actor, position, displacement, (hit) => this.isBlockingHit(actor, hit) && this.isWalkable(actor, hit.normal));
108
+ return this.cast(actor, position, displacement, (hit) => this.isWalkable(actor, hit.normal));
104
109
  }
105
110
  recoverOverlaps(actor, position) {
106
111
  const character = actor.getComponent(CharacterBody);
@@ -119,7 +124,7 @@ export class CharacterController extends SceneSystem {
119
124
  });
120
125
  let recovered = false;
121
126
  for (const hit of hits) {
122
- if (!this.isRecoverableOverlap(actor, hit)) {
127
+ if (!this.isRecoverableOverlap(hit)) {
123
128
  continue;
124
129
  }
125
130
  const correction = hit.penetration + character.skinWidth;
@@ -189,7 +194,6 @@ export class CharacterController extends SceneSystem {
189
194
  position.x += direction.x * safeDistance;
190
195
  position.y += direction.y * safeDistance;
191
196
  this.handleHit(actor, hit);
192
- clipAgainstNormal(character.velocity, hit.normal);
193
197
  const remainingDistance = Math.max(distance - safeDistance, 0);
194
198
  displacement.x = direction.x * remainingDistance;
195
199
  displacement.y = direction.y * remainingDistance;
@@ -221,11 +225,11 @@ export class CharacterController extends SceneSystem {
221
225
  target.x += snapDirection.x;
222
226
  target.y += snapDirection.y;
223
227
  }
224
- fixedUpdate(options) {
228
+ fixedUpdate() {
225
229
  if (!this.world.systemApi.has(PhysicsAPI)) {
226
230
  return;
227
231
  }
228
- const deltaTimeInSeconds = options.deltaTime / 1000;
232
+ const { fixedDeltaTime } = this.time;
229
233
  this.oneWayValidator.update();
230
234
  this.actorQuery.getActors().forEach((actor) => {
231
235
  const transform = actor.getComponent(Transform);
@@ -244,7 +248,7 @@ export class CharacterController extends SceneSystem {
244
248
  this.recoverOverlaps(actor, position);
245
249
  const displacement = character.velocity
246
250
  .clone()
247
- .multiplyNumber(deltaTimeInSeconds)
251
+ .multiplyNumber(fixedDeltaTime)
248
252
  .add(character._displacement);
249
253
  const movingUp = VectorOps.dotProduct(displacement, character.upDirection) >
250
254
  SNAP_EPSILON;
@@ -0,0 +1,15 @@
1
+ import type { Actor } from '../../../engine/actor';
2
+ import { type Point } from '../../../engine/math-lib';
3
+ export declare class OneWayValidator {
4
+ private ignoredOneWayContacts;
5
+ private touchedActors;
6
+ private oneWayContactUpdateIndex;
7
+ constructor();
8
+ private clearOneWayContacts;
9
+ private trackOneWayContact;
10
+ touch(actor: Actor): void;
11
+ shouldBlock(oneWayActor: Actor, otherActor: Actor, normal: Point): boolean;
12
+ delete(actor: Actor): void;
13
+ update(): void;
14
+ lateUpdate(): void;
15
+ }
@@ -0,0 +1,73 @@
1
+ import { RigidBody, Transform } from '../../components';
2
+ import { VectorOps } from '../../../engine/math-lib';
3
+ export class OneWayValidator {
4
+ ignoredOneWayContacts;
5
+ touchedActors;
6
+ oneWayContactUpdateIndex;
7
+ constructor() {
8
+ this.ignoredOneWayContacts = new Map();
9
+ this.touchedActors = new Set();
10
+ this.oneWayContactUpdateIndex = 0;
11
+ }
12
+ clearOneWayContacts() {
13
+ this.touchedActors.forEach((actor) => {
14
+ const ignoredContacts = this.ignoredOneWayContacts.get(actor);
15
+ if (!ignoredContacts) {
16
+ return;
17
+ }
18
+ ignoredContacts.forEach((lastSeenUpdate, otherActor) => {
19
+ if (lastSeenUpdate !== this.oneWayContactUpdateIndex) {
20
+ ignoredContacts.delete(otherActor);
21
+ }
22
+ });
23
+ if (ignoredContacts.size === 0) {
24
+ this.ignoredOneWayContacts.delete(actor);
25
+ }
26
+ });
27
+ if (this.ignoredOneWayContacts.size === 0) {
28
+ this.oneWayContactUpdateIndex = 0;
29
+ }
30
+ }
31
+ trackOneWayContact(actor, oneWayActor) {
32
+ let ignoredContacts = this.ignoredOneWayContacts.get(actor);
33
+ if (!ignoredContacts) {
34
+ ignoredContacts = new Map();
35
+ this.ignoredOneWayContacts.set(actor, ignoredContacts);
36
+ }
37
+ ignoredContacts.set(oneWayActor, this.oneWayContactUpdateIndex);
38
+ }
39
+ touch(actor) {
40
+ this.touchedActors.add(actor);
41
+ }
42
+ shouldBlock(oneWayActor, otherActor, normal) {
43
+ if (this.ignoredOneWayContacts.get(otherActor)?.has(oneWayActor)) {
44
+ this.trackOneWayContact(otherActor, oneWayActor);
45
+ return false;
46
+ }
47
+ const rigidBody = oneWayActor.getComponent(RigidBody);
48
+ const transform = oneWayActor.getComponent(Transform);
49
+ const oneWayNormal = VectorOps.rotatePoint(rigidBody.oneWayNormal, transform.world.rotation);
50
+ if (VectorOps.dotProduct(oneWayNormal, normal) > 0) {
51
+ return true;
52
+ }
53
+ this.trackOneWayContact(otherActor, oneWayActor);
54
+ return false;
55
+ }
56
+ delete(actor) {
57
+ this.ignoredOneWayContacts.delete(actor);
58
+ this.touchedActors.delete(actor);
59
+ this.ignoredOneWayContacts.forEach((ignoredContacts, trackedActor) => {
60
+ ignoredContacts.delete(actor);
61
+ if (ignoredContacts.size === 0) {
62
+ this.ignoredOneWayContacts.delete(trackedActor);
63
+ }
64
+ });
65
+ }
66
+ update() {
67
+ this.oneWayContactUpdateIndex += 1;
68
+ }
69
+ lateUpdate() {
70
+ this.clearOneWayContacts();
71
+ this.touchedActors.clear();
72
+ }
73
+ }
@@ -1,5 +1,5 @@
1
1
  import { WorldSystem } from '../../../engine/system';
2
- import type { WorldSystemOptions, UpdateOptions } from '../../../engine/system';
2
+ import type { WorldSystemOptions } from '../../../engine/system';
3
3
  import type { Scene } from '../../../engine/scene';
4
4
  /**
5
5
  * Game statistics meter that tracks and reports performance metrics
@@ -13,12 +13,13 @@ import type { Scene } from '../../../engine/scene';
13
13
  */
14
14
  export declare class GameStatsMeter extends WorldSystem {
15
15
  private world;
16
+ private time;
16
17
  private actorQuery?;
17
18
  private frequency;
18
19
  private fps;
19
- private time;
20
+ private elapsed;
20
21
  constructor(options: WorldSystemOptions);
21
22
  onSceneEnter(scene: Scene): void;
22
23
  onSceneExit(): void;
23
- update(options: UpdateOptions): void;
24
+ update(): void;
24
25
  }
@@ -1,7 +1,7 @@
1
1
  import { WorldSystem } from '../../../engine/system';
2
2
  import { GameStatsUpdate } from '../../events';
3
3
  import { ActorQuery } from '../../../engine/actor';
4
- const MS_IN_SEC = 1000;
4
+ const DEFAULT_FREQUENCY = 1;
5
5
  /**
6
6
  * Game statistics meter that tracks and reports performance metrics
7
7
  *
@@ -14,17 +14,19 @@ const MS_IN_SEC = 1000;
14
14
  */
15
15
  export class GameStatsMeter extends WorldSystem {
16
16
  world;
17
+ time;
17
18
  actorQuery;
18
19
  frequency;
19
20
  fps;
20
- time;
21
+ elapsed;
21
22
  constructor(options) {
22
23
  super();
23
24
  const { world, frequency } = options;
24
25
  this.world = world;
25
- this.frequency = frequency || MS_IN_SEC;
26
+ this.time = options.time;
27
+ this.frequency = frequency || DEFAULT_FREQUENCY;
26
28
  this.fps = 0;
27
- this.time = 0;
29
+ this.elapsed = 0;
28
30
  }
29
31
  onSceneEnter(scene) {
30
32
  this.actorQuery = new ActorQuery({ scene, filter: [] });
@@ -32,17 +34,16 @@ export class GameStatsMeter extends WorldSystem {
32
34
  onSceneExit() {
33
35
  this.actorQuery = undefined;
34
36
  }
35
- update(options) {
36
- const { deltaTime } = options;
37
+ update() {
37
38
  this.fps += 1;
38
- this.time += deltaTime;
39
- if (this.time >= this.frequency) {
39
+ this.elapsed += this.time.deltaTime;
40
+ if (this.elapsed >= this.frequency) {
40
41
  this.world.dispatchEvent(GameStatsUpdate, {
41
- fps: (this.fps * MS_IN_SEC) / this.time,
42
+ fps: this.fps / this.elapsed,
42
43
  actorsCount: this.actorQuery?.getActors().size ?? 0,
43
44
  });
44
45
  this.fps = 0;
45
- this.time = 0;
46
+ this.elapsed = 0;
46
47
  }
47
48
  }
48
49
  }
@@ -0,0 +1,11 @@
1
+ export declare const DEFAULT_SOLVER_ITERATIONS = 8;
2
+ export declare const DEFAULT_LINEAR_SLEEP_THRESHOLD = 2;
3
+ export declare const DEFAULT_ANGULAR_SLEEP_THRESHOLD = 0.05;
4
+ export declare const DEFAULT_SLEEP_TIME_THRESHOLD = 0.5;
5
+ export declare const DEFAULT_MAX_BIAS_VELOCITY = 60;
6
+ export declare const DEFAULT_CONTACT_MAX_ALLOWED_PENETRATION = 0.5;
7
+ export declare const CONTACT_BIAS = 0.2;
8
+ export declare const BIAS_ANGULAR_SLEEP_MULTIPLIER = 2;
9
+ export declare const RESTITUTION_VELOCITY_THRESHOLD = 1;
10
+ export declare const PENETRATION_SLEEP_MARGIN = 0.25;
11
+ export declare const SUPPORT_MIN_GRAVITY_DOT = 0.5;
@@ -0,0 +1,14 @@
1
+ export const DEFAULT_SOLVER_ITERATIONS = 8;
2
+ export const DEFAULT_LINEAR_SLEEP_THRESHOLD = 2;
3
+ export const DEFAULT_ANGULAR_SLEEP_THRESHOLD = 0.05;
4
+ export const DEFAULT_SLEEP_TIME_THRESHOLD = 0.5;
5
+ export const DEFAULT_MAX_BIAS_VELOCITY = 60;
6
+ export const DEFAULT_CONTACT_MAX_ALLOWED_PENETRATION = 0.5;
7
+ export const CONTACT_BIAS = 0.2;
8
+ export const BIAS_ANGULAR_SLEEP_MULTIPLIER = 2;
9
+ export const RESTITUTION_VELOCITY_THRESHOLD = 1;
10
+ // Kept above maxAllowedPenetration so a settled stack resting at the
11
+ // allowed slop does not wake itself from its own steady-state penetration.
12
+ export const PENETRATION_SLEEP_MARGIN = 0.25;
13
+ // 0.5 treats surfaces up to ~60° from horizontal as holding a body up.
14
+ export const SUPPORT_MIN_GRAVITY_DOT = 0.5;
@@ -1,5 +1,5 @@
1
1
  import { SceneSystem } from '../../../engine/system';
2
- import type { SceneSystemOptions, UpdateOptions } from '../../../engine/system';
2
+ import type { SceneSystemOptions } from '../../../engine/system';
3
3
  /**
4
4
  * Physics system that handles 2D physics simulation and collision detection
5
5
  *
@@ -21,5 +21,5 @@ export declare class PhysicsSystem extends SceneSystem {
21
21
  onSceneEnter(): void;
22
22
  onSceneExit(): void;
23
23
  onSceneDestroy(): void;
24
- fixedUpdate(options: UpdateOptions): void;
24
+ fixedUpdate(): void;
25
25
  }
@@ -21,16 +21,27 @@ export class PhysicsSystem extends SceneSystem {
21
21
  gravity;
22
22
  constructor(options) {
23
23
  super();
24
- const { gravityX = 0, gravityY = 0 } = options;
24
+ const { gravityX = 0, gravityY = 0, solverIterations, linearSleepThreshold, angularSleepThreshold, sleepTimeThreshold, maxAllowedPenetration, maxBiasVelocity, } = options;
25
25
  this.gravity = new Vector2(gravityX, gravityY);
26
26
  this.world = options.world;
27
27
  this.physicsSubsystem = new PhysicsSubsystem({
28
28
  scene: options.scene,
29
+ time: options.time,
29
30
  getGravity: () => this.gravity,
31
+ linearSleepThreshold,
32
+ angularSleepThreshold,
33
+ sleepTimeThreshold,
30
34
  });
31
35
  this.collisionDetectionSubsystem = new CollisionDetectionSubsystem(options);
32
36
  this.collisionBroadcastSubsystem = new CollisionBroadcastSubsystem();
33
- this.constraintSolver = new ConstraintSolver();
37
+ this.constraintSolver = new ConstraintSolver({
38
+ time: options.time,
39
+ getGravity: () => this.gravity,
40
+ solverIterations,
41
+ maxAllowedPenetration,
42
+ maxBiasVelocity,
43
+ linearSleepThreshold,
44
+ });
34
45
  this.physicsApi = new PhysicsAPI({
35
46
  raycast: (params) => this.collisionDetectionSubsystem.raycast(params),
36
47
  raycastAll: (params) => this.collisionDetectionSubsystem.raycastAll(params),
@@ -56,10 +67,13 @@ export class PhysicsSystem extends SceneSystem {
56
67
  this.physicsSubsystem.destroy();
57
68
  this.collisionDetectionSubsystem.destroy();
58
69
  }
59
- fixedUpdate(options) {
60
- this.physicsSubsystem.update(options);
70
+ fixedUpdate() {
71
+ this.physicsSubsystem.integrateVelocities();
72
+ this.physicsSubsystem.integrateKinematicPositions();
61
73
  const contacts = this.collisionDetectionSubsystem.update();
62
74
  this.constraintSolver.update(contacts);
75
+ this.physicsSubsystem.integrateDynamicPositions();
76
+ this.physicsSubsystem.updateSleepTimers();
63
77
  this.collisionBroadcastSubsystem.update(contacts);
64
78
  this.physicsSubsystem.lateUpdate();
65
79
  }
@@ -198,7 +198,9 @@ export class CollisionDetectionSubsystem {
198
198
  return (aabb1.max[axis] >= aabb2.min[axis] && aabb1.min[axis] <= aabb2.max[axis]);
199
199
  }
200
200
  testCollisionLayers(proxy1, proxy2) {
201
- return this.collisionMatrix[proxy1.layer]?.[proxy2.layer] ?? true;
201
+ return (this.collisionMatrix[proxy1.layer]?.[proxy2.layer] ??
202
+ this.collisionMatrix[proxy2.layer]?.[proxy1.layer] ??
203
+ true);
202
204
  }
203
205
  testState(proxy1, proxy2) {
204
206
  const actor1 = 'actor' in proxy1 ? proxy1.actor : undefined;
@@ -1,5 +1,6 @@
1
1
  import { orientNormal } from '../common/normals';
2
- import { findMinBoxesOverlap, buildContactPoints } from './utils';
2
+ import { sortPoints } from '../common/points';
3
+ import { findMinBoxesOverlap, buildContactPoints, OVERLAP_TIE_EPSILON, } from './utils';
3
4
  /**
4
5
  * Checks box colliders for intersection.
5
6
  *
@@ -17,7 +18,7 @@ export const checkBoxesIntersection = (geometry1, geometry2) => {
17
18
  if (overlap2 === false) {
18
19
  return false;
19
20
  }
20
- const isArg1Reference = overlap1.overlap <= overlap2.overlap;
21
+ const isArg1Reference = overlap1.overlap <= overlap2.overlap + OVERLAP_TIE_EPSILON;
21
22
  const referenceGeometry = isArg1Reference ? geometry1 : geometry2;
22
23
  const incidentGeometry = isArg1Reference ? geometry2 : geometry1;
23
24
  const referenceOverlap = isArg1Reference ? overlap1 : overlap2;
@@ -26,9 +27,10 @@ export const checkBoxesIntersection = (geometry1, geometry2) => {
26
27
  if (!isArg1Reference) {
27
28
  referenceNormal.multiplyNumber(-1);
28
29
  }
30
+ const contactPoints = buildContactPoints(referenceGeometry, referenceNormal, incidentGeometry);
29
31
  return {
30
32
  normal,
31
33
  penetration: referenceOverlap.overlap,
32
- contactPoints: buildContactPoints(referenceGeometry, referenceNormal, incidentGeometry),
34
+ contactPoints: sortPoints(contactPoints, normal),
33
35
  };
34
36
  };
@@ -1,6 +1,7 @@
1
1
  import { Vector2 } from '../../../../../../../engine/math-lib';
2
2
  import type { BoxGeometry, Point } from '../../types';
3
3
  export declare const CONTACT_EPSILON = 0.0001;
4
+ export declare const OVERLAP_TIE_EPSILON = 0.0001;
4
5
  export declare const MAX_CONTACT_POINTS = 2;
5
6
  export interface AxisOverlap {
6
7
  axis: Vector2;
@@ -1,6 +1,7 @@
1
1
  import { Vector2, VectorOps } from '../../../../../../../engine/math-lib';
2
2
  import { getProjectionOverlap, projectPolygon } from '../common/projections';
3
3
  export const CONTACT_EPSILON = 1e-4;
4
+ export const OVERLAP_TIE_EPSILON = 1e-4;
4
5
  export const MAX_CONTACT_POINTS = 2;
5
6
  /**
6
7
  * Runs the SAT pass for one box's face normals against another box.
@@ -22,7 +23,7 @@ export const findMinBoxesOverlap = (geometry1, geometry2) => {
22
23
  if (overlap === false) {
23
24
  return false;
24
25
  }
25
- if (overlap < minOverlap) {
26
+ if (overlap < minOverlap - OVERLAP_TIE_EPSILON) {
26
27
  minOverlap = overlap;
27
28
  bestAxis = axis;
28
29
  }
@@ -1,4 +1,5 @@
1
1
  import { orientNormal } from '../common/normals';
2
+ import { sortPoints } from '../common/points';
2
3
  import { buildBoxCapsuleIntersection } from './utils';
3
4
  /**
4
5
  * Checks a box against a capsule.
@@ -23,8 +24,10 @@ export const checkBoxAndCapsuleIntersection = (box, capsule) => {
23
24
  if (!intersection) {
24
25
  return false;
25
26
  }
27
+ const normal = orientNormal(intersection.normal, box.center, capsule.center);
26
28
  return {
27
29
  ...intersection,
28
- normal: orientNormal(intersection.normal, box.center, capsule.center),
30
+ normal,
31
+ contactPoints: sortPoints(intersection.contactPoints, normal),
29
32
  };
30
33
  };
@@ -1,5 +1,6 @@
1
1
  import { buildBoxSegmentContactPoints, findMinBoxSegmentOverlap, } from './utils';
2
2
  import { orientNormal } from '../common/normals';
3
+ import { sortPoints } from '../common/points';
3
4
  /**
4
5
  * Checks a box against a segment.
5
6
  *
@@ -17,9 +18,10 @@ export const checkBoxAndSegmentIntersection = (box, segment) => {
17
18
  if (contactPoints.length === 0) {
18
19
  return false;
19
20
  }
21
+ const normal = orientNormal(overlap.axis.clone(), box.center, segment.center);
20
22
  return {
21
- normal: orientNormal(overlap.axis.clone(), box.center, segment.center),
23
+ normal,
22
24
  penetration: overlap.overlap,
23
- contactPoints,
25
+ contactPoints: sortPoints(contactPoints, normal),
24
26
  };
25
27
  };
@@ -1,4 +1,6 @@
1
+ import type { Vector2 } from '../../../../../../../engine/math-lib';
1
2
  import type { Point } from '../../types';
2
3
  export declare const arePointsEqual: (point1: Point, point2: Point) => boolean;
3
4
  export declare const lerpPoint: (point1: Point, point2: Point, value: number) => Point;
4
5
  export declare const getDistanceSquared: (point1: Point, point2: Point) => number;
6
+ export declare const sortPoints: (points: Point[], normal: Vector2) => Point[];
@@ -5,3 +5,19 @@ export const lerpPoint = (point1, point2, value) => ({
5
5
  y: point1.y + (point2.y - point1.y) * value,
6
6
  });
7
7
  export const getDistanceSquared = (point1, point2) => (point1.x - point2.x) ** 2 + (point1.y - point2.y) ** 2;
8
+ export const sortPoints = (points, normal) => {
9
+ if (points.length !== 2) {
10
+ return points;
11
+ }
12
+ const tangentX = -normal.y;
13
+ const tangentY = normal.x;
14
+ const projection1 = points[0].x * tangentX + points[0].y * tangentY;
15
+ const projection2 = points[1].x * tangentX + points[1].y * tangentY;
16
+ if (projection1 <= projection2) {
17
+ return points;
18
+ }
19
+ const point = points[0];
20
+ points[0] = points[1];
21
+ points[1] = point;
22
+ return points;
23
+ };
@@ -1,3 +1,4 @@
1
+ import { sortPoints } from './common/points';
1
2
  import { checkBoxAndCapsuleIntersection } from './box-capsule/check-box-and-capsule-intersection';
2
3
  import { checkBoxAndCircleIntersection } from './box-circle/check-box-and-circle-intersection';
3
4
  import { checkBoxAndSegmentIntersection } from './box-segment/check-box-and-segment-intersection';
@@ -18,6 +19,7 @@ const swapArgs = (checker) => {
18
19
  return false;
19
20
  }
20
21
  intersection.normal.multiplyNumber(-1);
22
+ sortPoints(intersection.contactPoints, intersection.normal);
21
23
  return intersection;
22
24
  };
23
25
  };
@@ -5,6 +5,7 @@ import { buildCircleGeometry } from '../../geometry-builders/build-circle-geomet
5
5
  import { buildPointGeometry } from '../../geometry-builders/build-point-geometry';
6
6
  import { buildRayGeometry } from '../../geometry-builders/build-ray-geometry';
7
7
  import { buildSegmentGeometry } from '../../geometry-builders/build-segment-geometry';
8
+ import { expectHit } from '../../tests/assertions';
8
9
  export const createBoxGeometry = (centerX, centerY, sizeX, sizeY) => {
9
10
  return createRotatedBoxGeometry(centerX, centerY, sizeX, sizeY, 0);
10
11
  };
@@ -71,20 +72,8 @@ export const expectToBeClose = (point, x, y, digits = 6) => {
71
72
  expect(point.x).toBeCloseTo(x, digits);
72
73
  expect(point.y).toBeCloseTo(y, digits);
73
74
  };
74
- export const expectIntersection = (intersection) => {
75
- expect(intersection).not.toBe(false);
76
- if (intersection === false) {
77
- throw new Error('Expected intersection, received false');
78
- }
79
- return intersection;
80
- };
81
- export const expectCastHit = (hit) => {
82
- expect(hit).not.toBe(false);
83
- if (hit === false) {
84
- throw new Error('Expected cast hit, received false');
85
- }
86
- return hit;
87
- };
75
+ export const expectIntersection = (intersection) => expectHit(intersection, 'intersection');
76
+ export const expectCastHit = (hit) => expectHit(hit, 'cast hit');
88
77
  export const sortPoints = (points) => [...points].sort((point1, point2) => {
89
78
  if (point1.x !== point2.x) {
90
79
  return point1.x - point2.x;
@@ -2,6 +2,7 @@ import { Vector2 } from '../../../../../../../engine/math-lib';
2
2
  import { buildBoxCastGeometry } from '../../geometry-builders/build-box-cast-geometry';
3
3
  import { buildCapsuleCastGeometry } from '../../geometry-builders/build-capsule-cast-geometry';
4
4
  import { buildCircleCastGeometry } from '../../geometry-builders/build-circle-cast-geometry';
5
+ import { expectHit } from '../../tests/assertions';
5
6
  export const createCircleCastGeometry = (centerX, centerY, radius, directionX, directionY, maxDistance) => buildCircleCastGeometry({
6
7
  shape: {
7
8
  type: 'circle',
@@ -30,10 +31,4 @@ export const createBoxCastGeometry = (centerX, centerY, sizeX, sizeY, directionX
30
31
  direction: new Vector2(directionX, directionY),
31
32
  maxDistance,
32
33
  });
33
- export const expectShapeCastHit = (hit) => {
34
- expect(hit).not.toBe(false);
35
- if (hit === false) {
36
- throw new Error('Expected shape-cast hit, received false');
37
- }
38
- return hit;
39
- };
34
+ export const expectShapeCastHit = (hit) => expectHit(hit, 'shape-cast hit');
@@ -0,0 +1 @@
1
+ export declare const expectHit: <T>(hit: false | T, label: string) => T;
@@ -0,0 +1,7 @@
1
+ export const expectHit = (hit, label) => {
2
+ expect(hit).not.toBe(false);
3
+ if (hit === false) {
4
+ throw new Error(`Expected ${label}, received false`);
5
+ }
6
+ return hit;
7
+ };