dacha 0.18.0-alpha.8 → 0.18.0

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 (110) hide show
  1. package/build/contrib/components/index.d.ts +1 -0
  2. package/build/contrib/components/index.js +1 -0
  3. package/build/contrib/components/interpolation/index.d.ts +85 -0
  4. package/build/contrib/components/interpolation/index.js +101 -0
  5. package/build/contrib/components/rigid-body/index.d.ts +108 -14
  6. package/build/contrib/components/rigid-body/index.js +191 -22
  7. package/build/contrib/systems/animator/index.d.ts +3 -2
  8. package/build/contrib/systems/animator/index.js +6 -3
  9. package/build/contrib/systems/behavior-system/system.d.ts +4 -2
  10. package/build/contrib/systems/behavior-system/system.js +11 -3
  11. package/build/contrib/systems/behavior-system/types.d.ts +7 -10
  12. package/build/contrib/systems/character-controller/index.d.ts +4 -2
  13. package/build/contrib/systems/character-controller/index.js +22 -17
  14. package/build/contrib/systems/character-controller/one-way-validator.d.ts +15 -0
  15. package/build/contrib/systems/character-controller/one-way-validator.js +73 -0
  16. package/build/contrib/systems/game-stats-meter/index.d.ts +4 -3
  17. package/build/contrib/systems/game-stats-meter/index.js +11 -10
  18. package/build/contrib/systems/index.d.ts +2 -0
  19. package/build/contrib/systems/index.js +1 -0
  20. package/build/contrib/systems/interpolator/api.d.ts +40 -0
  21. package/build/contrib/systems/interpolator/api.js +70 -0
  22. package/build/contrib/systems/interpolator/index.d.ts +3 -0
  23. package/build/contrib/systems/interpolator/index.js +2 -0
  24. package/build/contrib/systems/interpolator/system.d.ts +31 -0
  25. package/build/contrib/systems/interpolator/system.js +96 -0
  26. package/build/contrib/systems/interpolator/tests/helpers.d.ts +13 -0
  27. package/build/contrib/systems/interpolator/tests/helpers.js +45 -0
  28. package/build/contrib/systems/interpolator/utils.d.ts +5 -0
  29. package/build/contrib/systems/interpolator/utils.js +41 -0
  30. package/build/contrib/systems/physics-system/api.d.ts +45 -1
  31. package/build/contrib/systems/physics-system/api.js +49 -0
  32. package/build/contrib/systems/physics-system/consts.d.ts +13 -0
  33. package/build/contrib/systems/physics-system/consts.js +17 -0
  34. package/build/contrib/systems/physics-system/physics-system.d.ts +2 -2
  35. package/build/contrib/systems/physics-system/physics-system.js +24 -4
  36. package/build/contrib/systems/physics-system/subsystems/collision-detection/dynamic-aabb-tree/index.d.ts +1 -1
  37. package/build/contrib/systems/physics-system/subsystems/collision-detection/dynamic-aabb-tree/index.js +21 -15
  38. package/build/contrib/systems/physics-system/subsystems/collision-detection/index.d.ts +11 -11
  39. package/build/contrib/systems/physics-system/subsystems/collision-detection/index.js +133 -125
  40. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/box-box/check-boxes-intersection.js +5 -3
  41. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/box-box/utils.d.ts +1 -0
  42. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/box-box/utils.js +2 -1
  43. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/box-capsule/check-box-and-capsule-intersection.js +4 -1
  44. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/box-segment/check-box-and-segment-intersection.js +4 -2
  45. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/common/points.d.ts +2 -0
  46. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/common/points.js +16 -0
  47. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/index.js +2 -0
  48. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/tests/helpers.js +3 -14
  49. package/build/contrib/systems/physics-system/subsystems/collision-detection/query-utils.d.ts +5 -0
  50. package/build/contrib/systems/physics-system/subsystems/collision-detection/query-utils.js +97 -81
  51. package/build/contrib/systems/physics-system/subsystems/collision-detection/shape-cast-checkers/tests/helpers.js +2 -7
  52. package/build/contrib/systems/physics-system/subsystems/collision-detection/tests/assertions.d.ts +1 -0
  53. package/build/contrib/systems/physics-system/subsystems/collision-detection/tests/assertions.js +7 -0
  54. package/build/contrib/systems/physics-system/subsystems/collision-detection/types.d.ts +0 -15
  55. package/build/contrib/systems/physics-system/subsystems/collision-detection/utils.d.ts +3 -0
  56. package/build/contrib/systems/physics-system/subsystems/collision-detection/utils.js +9 -0
  57. package/build/contrib/systems/physics-system/subsystems/constraint-solver/contact-state-manager.d.ts +63 -0
  58. package/build/contrib/systems/physics-system/subsystems/constraint-solver/contact-state-manager.js +170 -0
  59. package/build/contrib/systems/physics-system/subsystems/constraint-solver/contact-utils.d.ts +5 -0
  60. package/build/contrib/systems/physics-system/subsystems/constraint-solver/contact-utils.js +41 -0
  61. package/build/contrib/systems/physics-system/subsystems/constraint-solver/impulse-utils.d.ts +16 -0
  62. package/build/contrib/systems/physics-system/subsystems/constraint-solver/impulse-utils.js +77 -0
  63. package/build/contrib/systems/physics-system/subsystems/constraint-solver/index.d.ts +53 -4
  64. package/build/contrib/systems/physics-system/subsystems/constraint-solver/index.js +334 -116
  65. package/build/contrib/systems/physics-system/subsystems/constraint-solver/one-way-validator.d.ts +11 -0
  66. package/build/contrib/{utils → systems/physics-system/subsystems/constraint-solver}/one-way-validator.js +22 -25
  67. package/build/contrib/systems/physics-system/subsystems/constraint-solver/sleep-support-tracker.d.ts +13 -0
  68. package/build/contrib/systems/physics-system/subsystems/constraint-solver/sleep-support-tracker.js +42 -0
  69. package/build/contrib/systems/physics-system/subsystems/constraint-solver/tests/helpers.d.ts +4 -0
  70. package/build/contrib/systems/physics-system/subsystems/constraint-solver/tests/helpers.js +20 -0
  71. package/build/contrib/systems/physics-system/subsystems/physics/index.d.ts +15 -4
  72. package/build/contrib/systems/physics-system/subsystems/physics/index.js +131 -53
  73. package/build/contrib/systems/physics-system/subsystems/physics/mass-properties.d.ts +2 -0
  74. package/build/contrib/systems/physics-system/subsystems/physics/mass-properties.js +55 -0
  75. package/build/contrib/systems/physics-system/tests/helpers.d.ts +5 -2
  76. package/build/contrib/systems/physics-system/tests/helpers.js +11 -2
  77. package/build/contrib/systems/physics-system/types.d.ts +18 -2
  78. package/build/contrib/systems/renderer/actor-render-tree.js +15 -6
  79. package/build/contrib/systems/renderer/filters/index.d.ts +1 -1
  80. package/build/contrib/systems/renderer/filters/index.js +1 -1
  81. package/build/contrib/systems/renderer/material/index.d.ts +2 -1
  82. package/build/contrib/systems/renderer/material/index.js +1 -1
  83. package/build/contrib/systems/renderer/renderer.d.ts +2 -2
  84. package/build/contrib/systems/renderer/renderer.js +3 -4
  85. package/build/contrib/systems/renderer/types.d.ts +0 -3
  86. package/build/engine/data-lib/index.d.ts +1 -1
  87. package/build/engine/data-lib/index.js +1 -1
  88. package/build/engine/data-lib/pool.d.ts +9 -0
  89. package/build/engine/data-lib/pool.js +19 -0
  90. package/build/engine/engine.js +4 -1
  91. package/build/engine/game-loop.d.ts +3 -1
  92. package/build/engine/game-loop.js +11 -8
  93. package/build/engine/math-lib/math/ops.d.ts +16 -0
  94. package/build/engine/math-lib/math/ops.js +25 -0
  95. package/build/engine/scene/scene-manager.d.ts +4 -1
  96. package/build/engine/scene/scene-manager.js +5 -1
  97. package/build/engine/system/index.d.ts +1 -1
  98. package/build/engine/system/system.d.ts +10 -11
  99. package/build/engine/system/system.js +5 -2
  100. package/build/engine/time.d.ts +54 -0
  101. package/build/engine/time.js +77 -0
  102. package/build/index.d.ts +2 -1
  103. package/package.json +1 -1
  104. package/build/contrib/systems/physics-system/subsystems/collision-detection/dispersion-calculator/index.d.ts +0 -11
  105. package/build/contrib/systems/physics-system/subsystems/collision-detection/dispersion-calculator/index.js +0 -41
  106. package/build/contrib/utils/one-way-validator.d.ts +0 -12
  107. package/build/engine/data-lib/sort/index.d.ts +0 -1
  108. package/build/engine/data-lib/sort/index.js +0 -1
  109. package/build/engine/data-lib/sort/insertion-sort.d.ts +0 -1
  110. package/build/engine/data-lib/sort/insertion-sort.js +0 -14
@@ -0,0 +1,77 @@
1
+ export const getContactRestitution = (rigidBody1, rigidBody2) => {
2
+ return Math.max(rigidBody1.restitution, rigidBody2.restitution);
3
+ };
4
+ export const getContactFriction = (rigidBody1, rigidBody2) => {
5
+ return Math.sqrt(rigidBody1.friction * rigidBody2.friction);
6
+ };
7
+ /**
8
+ * Returns the effective mass at a contact point along a unit direction.
9
+ * It represents how strongly the two bodies resist an impulse applied at
10
+ * that point in that direction.
11
+ */
12
+ export const getEffectiveMass = (invMassA, invInertiaA, anchorAX, anchorAY, invMassB, invInertiaB, anchorBX, anchorBY, directionX, directionY) => {
13
+ const crossA = anchorAX * directionY - anchorAY * directionX;
14
+ const crossB = anchorBX * directionY - anchorBY * directionX;
15
+ return (invMassA +
16
+ invMassB +
17
+ crossA * crossA * invInertiaA +
18
+ crossB * crossB * invInertiaB);
19
+ };
20
+ /**
21
+ * Relative velocity of body B against body A at a contact point, projected onto
22
+ * a direction, using the supplied velocity fields (live, previous, or bias).
23
+ */
24
+ const getRelativeVelocityAtPoint = (linearVelocityA, angularVelocityA, linearVelocityB, angularVelocityB, point, directionX, directionY) => {
25
+ const velocityAX = linearVelocityA.x - angularVelocityA * point.anchorAY;
26
+ const velocityAY = linearVelocityA.y + angularVelocityA * point.anchorAX;
27
+ const velocityBX = linearVelocityB.x - angularVelocityB * point.anchorBY;
28
+ const velocityBY = linearVelocityB.y + angularVelocityB * point.anchorBX;
29
+ return ((velocityBX - velocityAX) * directionX +
30
+ (velocityBY - velocityAY) * directionY);
31
+ };
32
+ export const getNormalVelocity = (state, point) => getRelativeVelocityAtPoint(state.bodyA.linearVelocity, state.bodyA.angularVelocity, state.bodyB.linearVelocity, state.bodyB.angularVelocity, point, state.normalX, state.normalY);
33
+ export const getTangentVelocity = (state, point) => getRelativeVelocityAtPoint(state.bodyA.linearVelocity, state.bodyA.angularVelocity, state.bodyB.linearVelocity, state.bodyB.angularVelocity, point, state.tangentX, state.tangentY);
34
+ export const getPrevNormalVelocity = (state, point) => getRelativeVelocityAtPoint(state.bodyA._prevLinearVelocity, state.bodyA._prevAngularVelocity, state.bodyB._prevLinearVelocity, state.bodyB._prevAngularVelocity, point, state.normalX, state.normalY);
35
+ export const getBiasNormalVelocity = (state, point) => getRelativeVelocityAtPoint(state.bodyA._biasLinearVelocity, state.bodyA._biasAngularVelocity, state.bodyB._biasLinearVelocity, state.bodyB._biasAngularVelocity, point, state.normalX, state.normalY);
36
+ export const applyImpulse = (state, point, impulseX, impulseY) => {
37
+ const { bodyA, bodyB } = state;
38
+ if (state.invMassA > 0) {
39
+ bodyA.linearVelocity.x -= impulseX * state.invMassA;
40
+ bodyA.linearVelocity.y -= impulseY * state.invMassA;
41
+ }
42
+ if (state.invInertiaA > 0) {
43
+ bodyA.angularVelocity -=
44
+ (point.anchorAX * impulseY - point.anchorAY * impulseX) *
45
+ state.invInertiaA;
46
+ }
47
+ if (state.invMassB > 0) {
48
+ bodyB.linearVelocity.x += impulseX * state.invMassB;
49
+ bodyB.linearVelocity.y += impulseY * state.invMassB;
50
+ }
51
+ if (state.invInertiaB > 0) {
52
+ bodyB.angularVelocity +=
53
+ (point.anchorBX * impulseY - point.anchorBY * impulseX) *
54
+ state.invInertiaB;
55
+ }
56
+ };
57
+ export const applyBiasImpulse = (state, point, impulseX, impulseY) => {
58
+ const { bodyA, bodyB } = state;
59
+ if (state.invMassA > 0) {
60
+ bodyA._biasLinearVelocity.x -= impulseX * state.invMassA;
61
+ bodyA._biasLinearVelocity.y -= impulseY * state.invMassA;
62
+ }
63
+ if (state.invInertiaA > 0) {
64
+ bodyA._biasAngularVelocity -=
65
+ (point.anchorAX * impulseY - point.anchorAY * impulseX) *
66
+ state.invInertiaA;
67
+ }
68
+ if (state.invMassB > 0) {
69
+ bodyB._biasLinearVelocity.x += impulseX * state.invMassB;
70
+ bodyB._biasLinearVelocity.y += impulseY * state.invMassB;
71
+ }
72
+ if (state.invInertiaB > 0) {
73
+ bodyB._biasAngularVelocity +=
74
+ (point.anchorBX * impulseY - point.anchorBY * impulseX) *
75
+ state.invInertiaB;
76
+ }
77
+ };
@@ -1,13 +1,62 @@
1
+ import type { Time } from '../../../../../engine/time';
2
+ import { type Vector2 } from '../../../../../engine/math-lib';
1
3
  import type { Contact } from '../collision-detection/types';
4
+ interface ConstraintSolverOptions {
5
+ time: Time;
6
+ getGravity: () => Vector2;
7
+ solverIterations?: number;
8
+ maxAllowedPenetration?: number;
9
+ maxBiasVelocity?: number;
10
+ linearSleepThreshold?: number;
11
+ }
2
12
  export declare class ConstraintSolver {
3
- private validContacts;
4
13
  private oneWayValidator;
5
- constructor();
14
+ private contactStateManager;
15
+ private sleepSupportTracker;
16
+ private time;
17
+ private getGravity;
18
+ private restitutionThreshold;
19
+ private solverIterations;
20
+ private maxAllowedPenetration;
21
+ private maxBiasVelocity;
22
+ private linearSleepThreshold;
23
+ private penetrationSleepThreshold;
24
+ private flippedNormal;
25
+ private blockImpulse0;
26
+ private blockImpulse1;
27
+ constructor(options: ConstraintSolverOptions);
6
28
  private validateCollision;
7
29
  private validateOneWayContact;
8
- private getInverseMass;
30
+ private validateSleepContact;
31
+ private getPoint;
32
+ private prepareContact;
33
+ private prepareBlockSolver;
34
+ private prepareDecisions;
35
+ private applyWarmStartImpulse;
9
36
  private applyNormalImpulse;
37
+ private applyPointNormalImpulse;
38
+ private applyBlockNormalImpulse;
39
+ /**
40
+ * Solves the normal impulses for both contact points of a two-point
41
+ * manifold together, instead of one at a time.
42
+ *
43
+ * A contact point can only push, never pull (its impulse
44
+ * must stay >= 0), and a point that's already pulling apart on its own
45
+ * shouldn't be pushed at all. Which point(s) actually need pushing this
46
+ * step isn't known upfront, so this tries the possibilities from most to
47
+ * least likely and uses the first one that comes out consistent:
48
+ * 1. Both points push — solve the 2x2 system directly; only valid if both
49
+ * resulting impulses are non-negative.
50
+ * 2. Only point0 pushes, point1 is left alone — only valid if point1
51
+ * isn't still closing in without help (that would mean it needed a
52
+ * push too, contradicting the assumption).
53
+ * 3. Only point1 pushes, point0 is left alone — mirror of case 2.
54
+ * 4. Neither pushes — only valid if both points are already separating
55
+ * on their own.
56
+ */
57
+ private solveBlockNormalImpulse;
10
58
  private applyFrictionImpulse;
11
- private applyPositionCorrection;
59
+ private applyBiasImpulse;
12
60
  update(contacts: Contact[]): void;
13
61
  }
62
+ export {};
@@ -1,16 +1,57 @@
1
+ import { MathOps, } from '../../../../../engine/math-lib';
1
2
  import { RigidBody } from '../../../../components/rigid-body';
2
3
  import { Transform } from '../../../../components/transform';
3
- import { OneWayValidator } from '../../../../utils/one-way-validator';
4
- const SOLVER_ITERATIONS = 8;
5
- const POSITION_CORRECTION_PERCENT = 0.8;
6
- const PENETRATION_SLOP = 0.01;
7
- const DEFAULT_CONTACT_FRICTION = 0.6;
4
+ import { DEFAULT_SOLVER_ITERATIONS, DEFAULT_CONTACT_MAX_ALLOWED_PENETRATION, CONTACT_BIAS, DEFAULT_MAX_BIAS_VELOCITY, RESTITUTION_VELOCITY_THRESHOLD, DEFAULT_LINEAR_SLEEP_THRESHOLD, PENETRATION_SLEEP_MARGIN, } from '../../consts';
5
+ import { ContactStateManager, } from './contact-state-manager';
6
+ import { OneWayValidator } from './one-way-validator';
7
+ import { getContactFriction, getContactRestitution, getEffectiveMass, getNormalVelocity, getTangentVelocity, getPrevNormalVelocity, getBiasNormalVelocity, applyImpulse, applyBiasImpulse, } from './impulse-utils';
8
+ import { shouldWakeSleepingContact } from './contact-utils';
9
+ import { SleepSupportTracker } from './sleep-support-tracker';
10
+ const BLOCK_SOLVER_MIN_DETERMINANT = 1e-8;
11
+ const BLOCK_SOLVER_TOLERANCE = 1e-8;
12
+ /**
13
+ * A resting body gains g * dt of downward velocity from gravity before the
14
+ * solver runs. The restitution threshold must be higher than this velocity,
15
+ * otherwise resting contacts may be treated as impacts and keep bouncing.
16
+ *
17
+ * This factor keeps the threshold safely above one frame of gravity while
18
+ * never allowing it to fall below the base threshold.
19
+ */
20
+ const RESTITUTION_GRAVITY_THRESHOLD_FACTOR = 2;
8
21
  export class ConstraintSolver {
9
- validContacts;
10
22
  oneWayValidator;
11
- constructor() {
12
- this.validContacts = [];
23
+ contactStateManager;
24
+ sleepSupportTracker;
25
+ time;
26
+ getGravity;
27
+ restitutionThreshold;
28
+ solverIterations;
29
+ maxAllowedPenetration;
30
+ maxBiasVelocity;
31
+ linearSleepThreshold;
32
+ penetrationSleepThreshold;
33
+ flippedNormal;
34
+ blockImpulse0;
35
+ blockImpulse1;
36
+ constructor(options) {
13
37
  this.oneWayValidator = new OneWayValidator();
38
+ this.contactStateManager = new ContactStateManager();
39
+ this.sleepSupportTracker = new SleepSupportTracker(options.getGravity);
40
+ this.time = options.time;
41
+ this.getGravity = options.getGravity;
42
+ this.restitutionThreshold = RESTITUTION_VELOCITY_THRESHOLD;
43
+ this.solverIterations =
44
+ options.solverIterations ?? DEFAULT_SOLVER_ITERATIONS;
45
+ this.maxAllowedPenetration =
46
+ options.maxAllowedPenetration ?? DEFAULT_CONTACT_MAX_ALLOWED_PENETRATION;
47
+ this.maxBiasVelocity = options.maxBiasVelocity ?? DEFAULT_MAX_BIAS_VELOCITY;
48
+ this.linearSleepThreshold =
49
+ options.linearSleepThreshold ?? DEFAULT_LINEAR_SLEEP_THRESHOLD;
50
+ this.penetrationSleepThreshold =
51
+ this.maxAllowedPenetration + PENETRATION_SLEEP_MARGIN;
52
+ this.flippedNormal = { x: 0, y: 0 };
53
+ this.blockImpulse0 = 0;
54
+ this.blockImpulse1 = 0;
14
55
  }
15
56
  validateCollision(actor1, actor2) {
16
57
  const rigidBody1 = actor1.getComponent(RigidBody);
@@ -30,124 +71,292 @@ export class ConstraintSolver {
30
71
  const rigidBody1 = contact.actor1.getComponent(RigidBody);
31
72
  const rigidBody2 = contact.actor2.getComponent(RigidBody);
32
73
  if (rigidBody1.oneWay &&
33
- !this.oneWayValidator.validate(contact.actor1, contact.actor2, contact.normal)) {
74
+ rigidBody2.type === 'dynamic' &&
75
+ !this.oneWayValidator.shouldBlock(contact.actor1, contact.actor2, contact.normal)) {
34
76
  return false;
35
77
  }
36
- if (!rigidBody2.oneWay) {
78
+ if (!rigidBody2.oneWay || rigidBody1.type !== 'dynamic') {
37
79
  return true;
38
80
  }
39
- return this.oneWayValidator.validate(contact.actor2, contact.actor1, {
40
- x: -contact.normal.x,
41
- y: -contact.normal.y,
42
- });
81
+ this.flippedNormal.x = -contact.normal.x;
82
+ this.flippedNormal.y = -contact.normal.y;
83
+ return this.oneWayValidator.shouldBlock(contact.actor2, contact.actor1, this.flippedNormal);
43
84
  }
44
- getInverseMass(rigidBody) {
45
- if (rigidBody.type === 'static' || rigidBody.type === 'kinematic') {
46
- return 0;
85
+ validateSleepContact(contact) {
86
+ const rigidBody1 = contact.actor1.getComponent(RigidBody);
87
+ const rigidBody2 = contact.actor2.getComponent(RigidBody);
88
+ const isDeepContact = contact.penetration > this.penetrationSleepThreshold;
89
+ const isFastContact = shouldWakeSleepingContact(contact, this.linearSleepThreshold);
90
+ if (!isDeepContact && !isFastContact) {
91
+ this.sleepSupportTracker.trackContact(contact);
47
92
  }
48
- return rigidBody.inverseMass;
93
+ if (rigidBody1.sleeping && rigidBody2.sleeping) {
94
+ return false;
95
+ }
96
+ if ((rigidBody1.sleeping || rigidBody2.sleeping) &&
97
+ (isDeepContact || isFastContact)) {
98
+ rigidBody1.wakeUp();
99
+ rigidBody2.wakeUp();
100
+ }
101
+ return true;
49
102
  }
50
- applyNormalImpulse(contact) {
51
- const { actor1, actor2, normal } = contact;
52
- const rigidBody1 = actor1.getComponent(RigidBody);
53
- const rigidBody2 = actor2.getComponent(RigidBody);
54
- const inverseMass1 = this.getInverseMass(rigidBody1);
55
- const inverseMass2 = this.getInverseMass(rigidBody2);
56
- const inverseMassSum = inverseMass1 + inverseMass2;
57
- if (inverseMassSum === 0) {
58
- return 0;
59
- }
60
- const relativeVelocityX = rigidBody2.linearVelocity.x - rigidBody1.linearVelocity.x;
61
- const relativeVelocityY = rigidBody2.linearVelocity.y - rigidBody1.linearVelocity.y;
62
- const velocityAlongNormal = relativeVelocityX * normal.x + relativeVelocityY * normal.y;
63
- if (velocityAlongNormal >= 0) {
64
- return 0;
65
- }
66
- const impulseMagnitude = -velocityAlongNormal / inverseMassSum;
67
- const impulseX = normal.x * impulseMagnitude;
68
- const impulseY = normal.y * impulseMagnitude;
69
- if (inverseMass1 > 0) {
70
- rigidBody1.linearVelocity.x -= impulseX * inverseMass1;
71
- rigidBody1.linearVelocity.y -= impulseY * inverseMass1;
72
- }
73
- if (inverseMass2 > 0) {
74
- rigidBody2.linearVelocity.x += impulseX * inverseMass2;
75
- rigidBody2.linearVelocity.y += impulseY * inverseMass2;
76
- }
77
- return impulseMagnitude;
78
- }
79
- applyFrictionImpulse(contact, normalImpulseMagnitude) {
80
- if (normalImpulseMagnitude <= 0) {
103
+ getPoint(state, index) {
104
+ return index === 0 ? state.point0 : state.point1;
105
+ }
106
+ prepareContact(state) {
107
+ const { normal } = state.contact;
108
+ const bodyA = state.actor1.getComponent(RigidBody);
109
+ const bodyB = state.actor2.getComponent(RigidBody);
110
+ const transformA = state.actor1.getComponent(Transform);
111
+ const transformB = state.actor2.getComponent(Transform);
112
+ const invMassA = bodyA.inverseMass;
113
+ const invMassB = bodyB.inverseMass;
114
+ const invInertiaA = bodyA.inverseInertia;
115
+ const invInertiaB = bodyB.inverseInertia;
116
+ state.bodyA = bodyA;
117
+ state.bodyB = bodyB;
118
+ state.invMassA = invMassA;
119
+ state.invInertiaA = invInertiaA;
120
+ state.invMassB = invMassB;
121
+ state.invInertiaB = invInertiaB;
122
+ state.normalX = normal.x;
123
+ state.normalY = normal.y;
124
+ state.tangentX = -normal.y;
125
+ state.tangentY = normal.x;
126
+ state.friction = getContactFriction(bodyA, bodyB);
127
+ state.restitution = getContactRestitution(bodyA, bodyB);
128
+ state.active = invMassA + invMassB + invInertiaA + invInertiaB > 0;
129
+ if (!state.active) {
81
130
  return;
82
131
  }
83
- const { normal } = contact;
84
- const rigidBody1 = contact.actor1.getComponent(RigidBody);
85
- const rigidBody2 = contact.actor2.getComponent(RigidBody);
86
- const inverseMass1 = this.getInverseMass(rigidBody1);
87
- const inverseMass2 = this.getInverseMass(rigidBody2);
88
- const inverseMassSum = inverseMass1 + inverseMass2;
89
- if (inverseMassSum === 0) {
132
+ const posAX = transformA.world.position.x;
133
+ const posAY = transformA.world.position.y;
134
+ const posBX = transformB.world.position.x;
135
+ const posBY = transformB.world.position.y;
136
+ let minPrevNormalVelocity = Infinity;
137
+ for (let index = 0; index < state.pointCount; index += 1) {
138
+ const point = this.getPoint(state, index);
139
+ point.anchorAX = point.positionX - posAX;
140
+ point.anchorAY = point.positionY - posAY;
141
+ point.anchorBX = point.positionX - posBX;
142
+ point.anchorBY = point.positionY - posBY;
143
+ point.normalMass = getEffectiveMass(invMassA, invInertiaA, point.anchorAX, point.anchorAY, invMassB, invInertiaB, point.anchorBX, point.anchorBY, state.normalX, state.normalY);
144
+ point.tangentMass = getEffectiveMass(invMassA, invInertiaA, point.anchorAX, point.anchorAY, invMassB, invInertiaB, point.anchorBX, point.anchorBY, state.tangentX, state.tangentY);
145
+ const prevNormalVelocity = getPrevNormalVelocity(state, point);
146
+ if (prevNormalVelocity < minPrevNormalVelocity) {
147
+ minPrevNormalVelocity = prevNormalVelocity;
148
+ }
149
+ if (state.restitution > 0 &&
150
+ -prevNormalVelocity > this.restitutionThreshold) {
151
+ point.velocityBias = -state.restitution * prevNormalVelocity;
152
+ }
153
+ else {
154
+ point.velocityBias = 0;
155
+ }
156
+ }
157
+ this.prepareBlockSolver(state);
158
+ this.prepareDecisions(state, minPrevNormalVelocity);
159
+ }
160
+ prepareBlockSolver(state) {
161
+ if (state.pointCount !== 2 ||
162
+ state.point0.normalMass === 0 ||
163
+ state.point1.normalMass === 0) {
164
+ state.blockSolvable = false;
90
165
  return;
91
166
  }
92
- const relativeVelocityX = rigidBody2.linearVelocity.x - rigidBody1.linearVelocity.x;
93
- const relativeVelocityY = rigidBody2.linearVelocity.y - rigidBody1.linearVelocity.y;
94
- const velocityAlongNormal = relativeVelocityX * normal.x + relativeVelocityY * normal.y;
95
- const tangentX = relativeVelocityX - normal.x * velocityAlongNormal;
96
- const tangentY = relativeVelocityY - normal.y * velocityAlongNormal;
97
- const tangentMagnitude = Math.sqrt(tangentX ** 2 + tangentY ** 2);
98
- if (tangentMagnitude === 0) {
167
+ const { point0, point1 } = state;
168
+ const crossA0 = point0.anchorAX * state.normalY - point0.anchorAY * state.normalX;
169
+ const crossB0 = point0.anchorBX * state.normalY - point0.anchorBY * state.normalX;
170
+ const crossA1 = point1.anchorAX * state.normalY - point1.anchorAY * state.normalX;
171
+ const crossB1 = point1.anchorBX * state.normalY - point1.anchorBY * state.normalX;
172
+ state.k01 =
173
+ state.invMassA +
174
+ state.invMassB +
175
+ crossA0 * crossA1 * state.invInertiaA +
176
+ crossB0 * crossB1 * state.invInertiaB;
177
+ state.determinant =
178
+ point0.normalMass * point1.normalMass - state.k01 * state.k01;
179
+ state.blockSolvable =
180
+ Math.abs(state.determinant) >= BLOCK_SOLVER_MIN_DETERMINANT;
181
+ }
182
+ prepareDecisions(state, minPrevNormalVelocity) {
183
+ const hasRestitution = state.restitution > 0;
184
+ const isBouncing = hasRestitution && -minPrevNormalVelocity > this.restitutionThreshold;
185
+ const eitherSleeping = state.bodyA.sleeping || state.bodyB.sleeping;
186
+ state.warmStartAllowed =
187
+ (!hasRestitution || !isBouncing) && !eitherSleeping;
188
+ state.skipBias =
189
+ isBouncing && (state.invMassA === 0 || state.invMassB === 0);
190
+ }
191
+ applyWarmStartImpulse(state) {
192
+ if (!state.warmStartAllowed) {
193
+ this.contactStateManager.clearWarmStartImpulses(state);
99
194
  return;
100
195
  }
101
- const normalizedTangentX = tangentX / tangentMagnitude;
102
- const normalizedTangentY = tangentY / tangentMagnitude;
103
- const velocityAlongTangent = relativeVelocityX * normalizedTangentX +
104
- relativeVelocityY * normalizedTangentY;
105
- const unclampedImpulseMagnitude = -velocityAlongTangent / inverseMassSum;
106
- const maxFrictionImpulseMagnitude = DEFAULT_CONTACT_FRICTION * normalImpulseMagnitude;
107
- const impulseMagnitude = Math.max(-maxFrictionImpulseMagnitude, Math.min(unclampedImpulseMagnitude, maxFrictionImpulseMagnitude));
108
- const impulseX = normalizedTangentX * impulseMagnitude;
109
- const impulseY = normalizedTangentY * impulseMagnitude;
110
- if (inverseMass1 > 0) {
111
- rigidBody1.linearVelocity.x -= impulseX * inverseMass1;
112
- rigidBody1.linearVelocity.y -= impulseY * inverseMass1;
113
- }
114
- if (inverseMass2 > 0) {
115
- rigidBody2.linearVelocity.x += impulseX * inverseMass2;
116
- rigidBody2.linearVelocity.y += impulseY * inverseMass2;
117
- }
118
- }
119
- applyPositionCorrection(contact) {
120
- const { actor1, actor2, normal, penetration } = contact;
121
- const rigidBody1 = actor1.getComponent(RigidBody);
122
- const rigidBody2 = actor2.getComponent(RigidBody);
123
- const inverseMass1 = this.getInverseMass(rigidBody1);
124
- const inverseMass2 = this.getInverseMass(rigidBody2);
125
- const inverseMassSum = inverseMass1 + inverseMass2;
126
- if (inverseMassSum === 0) {
196
+ for (let index = 0; index < state.pointCount; index += 1) {
197
+ const point = this.getPoint(state, index);
198
+ if (point.normalImpulse === 0 && point.tangentImpulse === 0) {
199
+ continue;
200
+ }
201
+ applyImpulse(state, point, state.normalX * point.normalImpulse +
202
+ state.tangentX * point.tangentImpulse, state.normalY * point.normalImpulse +
203
+ state.tangentY * point.tangentImpulse);
204
+ }
205
+ }
206
+ applyNormalImpulse(state) {
207
+ if (state.blockSolvable) {
208
+ this.applyBlockNormalImpulse(state);
127
209
  return;
128
210
  }
129
- const correctionMagnitude = (Math.max(penetration - PENETRATION_SLOP, 0) *
130
- POSITION_CORRECTION_PERCENT) /
131
- inverseMassSum;
132
- if (correctionMagnitude === 0) {
211
+ for (let index = 0; index < state.pointCount; index += 1) {
212
+ this.applyPointNormalImpulse(state, index);
213
+ }
214
+ }
215
+ applyPointNormalImpulse(state, index) {
216
+ const point = this.getPoint(state, index);
217
+ if (point.normalMass === 0) {
133
218
  return;
134
219
  }
135
- const correctionX = normal.x * correctionMagnitude;
136
- const correctionY = normal.y * correctionMagnitude;
137
- const transform1 = actor1.getComponent(Transform);
138
- const transform2 = actor2.getComponent(Transform);
139
- if (inverseMass1 > 0) {
140
- transform1.world.position.x -= correctionX * inverseMass1;
141
- transform1.world.position.y -= correctionY * inverseMass1;
220
+ const velocityAlongNormal = getNormalVelocity(state, point);
221
+ const oldImpulse = point.normalImpulse;
222
+ const newImpulse = Math.max(oldImpulse +
223
+ (point.velocityBias - velocityAlongNormal) / point.normalMass, 0);
224
+ point.normalImpulse = newImpulse;
225
+ const impulse = newImpulse - oldImpulse;
226
+ if (impulse === 0) {
227
+ return;
142
228
  }
143
- if (inverseMass2 > 0) {
144
- transform2.world.position.x += correctionX * inverseMass2;
145
- transform2.world.position.y += correctionY * inverseMass2;
229
+ applyImpulse(state, point, state.normalX * impulse, state.normalY * impulse);
230
+ }
231
+ applyBlockNormalImpulse(state) {
232
+ const { point0, point1 } = state;
233
+ const mass00 = point0.normalMass;
234
+ const mass11 = point1.normalMass;
235
+ const mass01 = state.k01;
236
+ const velocity0 = getNormalVelocity(state, point0) - point0.velocityBias;
237
+ const velocity1 = getNormalVelocity(state, point1) - point1.velocityBias;
238
+ const oldImpulse0 = point0.normalImpulse;
239
+ const oldImpulse1 = point1.normalImpulse;
240
+ const target0 = mass00 * oldImpulse0 + mass01 * oldImpulse1 - velocity0;
241
+ const target1 = mass01 * oldImpulse0 + mass11 * oldImpulse1 - velocity1;
242
+ if (!this.solveBlockNormalImpulse(mass00, mass01, mass11, target0, target1, velocity0, velocity1, oldImpulse0, oldImpulse1, state.determinant)) {
243
+ this.applyPointNormalImpulse(state, 0);
244
+ this.applyPointNormalImpulse(state, 1);
245
+ return;
246
+ }
247
+ const impulseDelta0 = this.blockImpulse0 - oldImpulse0;
248
+ const impulseDelta1 = this.blockImpulse1 - oldImpulse1;
249
+ point0.normalImpulse = this.blockImpulse0;
250
+ point1.normalImpulse = this.blockImpulse1;
251
+ if (impulseDelta0 !== 0) {
252
+ applyImpulse(state, point0, state.normalX * impulseDelta0, state.normalY * impulseDelta0);
253
+ }
254
+ if (impulseDelta1 !== 0) {
255
+ applyImpulse(state, point1, state.normalX * impulseDelta1, state.normalY * impulseDelta1);
256
+ }
257
+ }
258
+ /**
259
+ * Solves the normal impulses for both contact points of a two-point
260
+ * manifold together, instead of one at a time.
261
+ *
262
+ * A contact point can only push, never pull (its impulse
263
+ * must stay >= 0), and a point that's already pulling apart on its own
264
+ * shouldn't be pushed at all. Which point(s) actually need pushing this
265
+ * step isn't known upfront, so this tries the possibilities from most to
266
+ * least likely and uses the first one that comes out consistent:
267
+ * 1. Both points push — solve the 2x2 system directly; only valid if both
268
+ * resulting impulses are non-negative.
269
+ * 2. Only point0 pushes, point1 is left alone — only valid if point1
270
+ * isn't still closing in without help (that would mean it needed a
271
+ * push too, contradicting the assumption).
272
+ * 3. Only point1 pushes, point0 is left alone — mirror of case 2.
273
+ * 4. Neither pushes — only valid if both points are already separating
274
+ * on their own.
275
+ */
276
+ solveBlockNormalImpulse(mass00, mass01, mass11, target0, target1, velocity0, velocity1, oldImpulse0, oldImpulse1, determinant) {
277
+ const bothImpulse0 = (mass11 * target0 - mass01 * target1) / determinant;
278
+ const bothImpulse1 = (mass00 * target1 - mass01 * target0) / determinant;
279
+ if (bothImpulse0 >= 0 && bothImpulse1 >= 0) {
280
+ this.blockImpulse0 = bothImpulse0;
281
+ this.blockImpulse1 = bothImpulse1;
282
+ return true;
283
+ }
284
+ const point0Impulse = target0 / mass00;
285
+ const point0InactiveVelocity = velocity1 + mass01 * (point0Impulse - oldImpulse0) - mass11 * oldImpulse1;
286
+ if (point0Impulse >= 0 &&
287
+ point0InactiveVelocity >= -BLOCK_SOLVER_TOLERANCE) {
288
+ this.blockImpulse0 = point0Impulse;
289
+ this.blockImpulse1 = 0;
290
+ return true;
291
+ }
292
+ const point1Impulse = target1 / mass11;
293
+ const point1InactiveVelocity = velocity0 - mass00 * oldImpulse0 + mass01 * (point1Impulse - oldImpulse1);
294
+ if (point1Impulse >= 0 &&
295
+ point1InactiveVelocity >= -BLOCK_SOLVER_TOLERANCE) {
296
+ this.blockImpulse0 = 0;
297
+ this.blockImpulse1 = point1Impulse;
298
+ return true;
299
+ }
300
+ const inactiveVelocity0 = velocity0 - mass00 * oldImpulse0 - mass01 * oldImpulse1;
301
+ const inactiveVelocity1 = velocity1 - mass01 * oldImpulse0 - mass11 * oldImpulse1;
302
+ if (inactiveVelocity0 >= -BLOCK_SOLVER_TOLERANCE &&
303
+ inactiveVelocity1 >= -BLOCK_SOLVER_TOLERANCE) {
304
+ this.blockImpulse0 = 0;
305
+ this.blockImpulse1 = 0;
306
+ return true;
307
+ }
308
+ return false;
309
+ }
310
+ applyFrictionImpulse(state) {
311
+ for (let index = 0; index < state.pointCount; index += 1) {
312
+ const point = this.getPoint(state, index);
313
+ if (point.tangentMass === 0) {
314
+ continue;
315
+ }
316
+ const maxFrictionImpulse = state.friction * point.normalImpulse;
317
+ const velocityAlongTangent = getTangentVelocity(state, point);
318
+ const oldImpulse = point.tangentImpulse;
319
+ const newImpulse = MathOps.clamp(oldImpulse - velocityAlongTangent / point.tangentMass, -maxFrictionImpulse, maxFrictionImpulse);
320
+ point.tangentImpulse = newImpulse;
321
+ const impulse = newImpulse - oldImpulse;
322
+ if (impulse === 0) {
323
+ continue;
324
+ }
325
+ applyImpulse(state, point, state.tangentX * impulse, state.tangentY * impulse);
326
+ }
327
+ }
328
+ applyBiasImpulse(state, deltaTime) {
329
+ const targetBiasVelocity = Math.min((Math.max(state.contact.penetration - this.maxAllowedPenetration, 0) *
330
+ CONTACT_BIAS) /
331
+ deltaTime, this.maxBiasVelocity);
332
+ if (targetBiasVelocity === 0) {
333
+ return;
334
+ }
335
+ for (let index = 0; index < state.pointCount; index += 1) {
336
+ const point = this.getPoint(state, index);
337
+ if (point.normalMass === 0) {
338
+ continue;
339
+ }
340
+ const biasVelocityAlongNormal = getBiasNormalVelocity(state, point);
341
+ const oldImpulse = point.biasImpulse;
342
+ const newImpulse = Math.max(oldImpulse +
343
+ (targetBiasVelocity - biasVelocityAlongNormal) / point.normalMass, 0);
344
+ point.biasImpulse = newImpulse;
345
+ const impulse = newImpulse - oldImpulse;
346
+ if (impulse === 0) {
347
+ continue;
348
+ }
349
+ applyBiasImpulse(state, point, state.normalX * impulse, state.normalY * impulse);
146
350
  }
147
351
  }
148
352
  update(contacts) {
149
- this.oneWayValidator.update();
150
- let validContactsCount = 0;
353
+ this.oneWayValidator.updateVersion();
354
+ this.contactStateManager.updateVersion();
355
+ this.sleepSupportTracker.beginFrame();
356
+ const deltaTime = this.time.fixedDeltaTime;
357
+ this.restitutionThreshold = Math.max(RESTITUTION_VELOCITY_THRESHOLD, RESTITUTION_GRAVITY_THRESHOLD_FACTOR *
358
+ this.getGravity().magnitude *
359
+ deltaTime);
151
360
  contacts.forEach((contact) => {
152
361
  if (!this.validateCollision(contact.actor1, contact.actor2)) {
153
362
  return;
@@ -155,19 +364,28 @@ export class ConstraintSolver {
155
364
  if (!this.validateOneWayContact(contact)) {
156
365
  return;
157
366
  }
158
- this.validContacts[validContactsCount] = contact;
159
- validContactsCount += 1;
367
+ if (!this.validateSleepContact(contact)) {
368
+ return;
369
+ }
370
+ const state = this.contactStateManager.acquire(contact);
371
+ this.prepareContact(state);
372
+ this.applyWarmStartImpulse(state);
160
373
  });
161
- this.validContacts.length = validContactsCount;
162
- for (let iteration = 0; iteration < SOLVER_ITERATIONS; iteration += 1) {
163
- this.validContacts.forEach((contact) => {
164
- const normalImpulseMagnitude = this.applyNormalImpulse(contact);
165
- this.applyFrictionImpulse(contact, normalImpulseMagnitude);
374
+ this.sleepSupportTracker.wakeUnsupportedBodies();
375
+ this.contactStateManager.pruneStaleStates();
376
+ for (let iteration = 0; iteration < this.solverIterations; iteration += 1) {
377
+ this.contactStateManager.forEach((state) => {
378
+ if (!state.active) {
379
+ return;
380
+ }
381
+ this.applyNormalImpulse(state);
382
+ this.applyFrictionImpulse(state);
383
+ if (!state.skipBias) {
384
+ this.applyBiasImpulse(state, deltaTime);
385
+ }
166
386
  });
167
387
  }
168
- this.validContacts.forEach((contact) => {
169
- this.applyPositionCorrection(contact);
170
- });
171
- this.oneWayValidator.lateUpdate();
388
+ this.oneWayValidator.clearOneWayContacts();
389
+ this.sleepSupportTracker.endFrame();
172
390
  }
173
391
  }
@@ -0,0 +1,11 @@
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 version;
6
+ constructor();
7
+ private trackOneWayContact;
8
+ shouldBlock(oneWayActor: Actor, otherActor: Actor, normal: Point): boolean;
9
+ updateVersion(): void;
10
+ clearOneWayContacts(): void;
11
+ }