dacha 0.17.2 → 0.18.0-alpha.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 (53) hide show
  1. package/build/contrib/components/collider/index.d.ts +6 -2
  2. package/build/contrib/components/collider/index.js +7 -2
  3. package/build/contrib/components/rigid-body/index.d.ts +29 -17
  4. package/build/contrib/components/rigid-body/index.js +64 -21
  5. package/build/contrib/events/index.d.ts +10 -75
  6. package/build/contrib/events/index.js +0 -36
  7. package/build/contrib/systems/physics-system/index.d.ts +0 -1
  8. package/build/contrib/systems/physics-system/index.js +6 -11
  9. package/build/contrib/systems/physics-system/subsystems/collision-broadcast/collision.d.ts +5 -3
  10. package/build/contrib/systems/physics-system/subsystems/collision-broadcast/collision.js +7 -5
  11. package/build/contrib/systems/physics-system/subsystems/collision-broadcast/index.d.ts +4 -6
  12. package/build/contrib/systems/physics-system/subsystems/collision-broadcast/index.js +20 -17
  13. package/build/contrib/systems/physics-system/subsystems/collision-detection/index.d.ts +12 -9
  14. package/build/contrib/systems/physics-system/subsystems/collision-detection/index.js +107 -93
  15. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/box-box/check-boxes-intersection.d.ts +10 -0
  16. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/box-box/check-boxes-intersection.js +36 -0
  17. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/box-box/utils.d.ts +23 -0
  18. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/box-box/utils.js +143 -0
  19. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/box-circle/check-box-and-circle-intersection.d.ts +9 -0
  20. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/box-circle/check-box-and-circle-intersection.js +47 -0
  21. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/circle-circle/check-circles-intersection.d.ts +12 -0
  22. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/circle-circle/check-circles-intersection.js +49 -0
  23. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/index.d.ts +2 -2
  24. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/index.js +3 -3
  25. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/tests/helpers.d.ts +17 -0
  26. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/tests/helpers.js +66 -0
  27. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/utils.d.ts +4 -0
  28. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/utils.js +9 -0
  29. package/build/contrib/systems/physics-system/subsystems/collision-detection/reorientation-checkers/check-collider.js +3 -0
  30. package/build/contrib/systems/physics-system/subsystems/collision-detection/types.d.ts +14 -5
  31. package/build/contrib/systems/physics-system/subsystems/collision-detection/types.js +0 -3
  32. package/build/contrib/systems/physics-system/subsystems/constraint-solver/index.d.ts +7 -10
  33. package/build/contrib/systems/physics-system/subsystems/constraint-solver/index.js +117 -79
  34. package/build/contrib/systems/physics-system/subsystems/index.d.ts +0 -1
  35. package/build/contrib/systems/physics-system/subsystems/index.js +0 -1
  36. package/build/contrib/systems/physics-system/subsystems/physics/index.d.ts +3 -9
  37. package/build/contrib/systems/physics-system/subsystems/physics/index.js +57 -93
  38. package/build/contrib/systems/physics-system/types.d.ts +9 -0
  39. package/build/engine/math-lib/vector/ops.d.ts +3 -3
  40. package/build/engine/math-lib/vector/ops.js +8 -5
  41. package/build/engine/math-lib/vector/vector2.d.ts +22 -7
  42. package/build/engine/math-lib/vector/vector2.js +29 -5
  43. package/build/events/index.d.ts +2 -2
  44. package/build/events/index.js +1 -1
  45. package/package.json +1 -1
  46. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/check-box-and-circle-intersection.d.ts +0 -16
  47. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/check-box-and-circle-intersection.js +0 -80
  48. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/check-boxes-intersection.d.ts +0 -6
  49. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/check-boxes-intersection.js +0 -72
  50. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/check-circles-intersection.d.ts +0 -11
  51. package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/check-circles-intersection.js +0 -39
  52. package/build/contrib/systems/physics-system/subsystems/collision-solver/index.d.ts +0 -10
  53. package/build/contrib/systems/physics-system/subsystems/collision-solver/index.js +0 -50
@@ -1,7 +1,6 @@
1
1
  import { ActorQuery } from '../../../../../engine/actor';
2
2
  import { Transform, Collider, RigidBody } from '../../../../components';
3
3
  import { AddActor, RemoveActor } from '../../../../../engine/events';
4
- import { Collision } from '../../../../events';
5
4
  import { insertionSort } from '../../../../../engine/data-lib';
6
5
  import { geometryBuilders } from './geometry-builders';
7
6
  import { aabbBuilders } from './aabb-builders';
@@ -10,17 +9,18 @@ import { DispersionCalculator } from './dispersion-calculator';
10
9
  import { checkTransform, checkCollider } from './reorientation-checkers';
11
10
  export class CollisionDetectionSubsystem {
12
11
  actorQuery;
13
- scene;
14
12
  axis;
15
- entriesMap;
16
- collisionPairs;
17
- entriesToDelete;
13
+ proxiesByActorId;
14
+ proxyPairs;
15
+ contacts;
16
+ actorIdsToDelete;
17
+ collisionMatrix;
18
18
  constructor(options) {
19
+ const settings = options.globalOptions.physics;
19
20
  this.actorQuery = new ActorQuery({
20
21
  scene: options.scene,
21
22
  filter: [Collider, Transform],
22
23
  });
23
- this.scene = options.scene;
24
24
  this.axis = {
25
25
  x: {
26
26
  sortedList: [],
@@ -31,12 +31,12 @@ export class CollisionDetectionSubsystem {
31
31
  dispersionCalculator: new DispersionCalculator('y'),
32
32
  },
33
33
  };
34
- this.entriesMap = new Map();
35
- this.collisionPairs = [];
36
- this.entriesToDelete = new Set();
37
- this.actorQuery
38
- .getActors()
39
- .forEach((actor) => this.addCollisionEntry(actor));
34
+ this.proxiesByActorId = new Map();
35
+ this.proxyPairs = [];
36
+ this.contacts = [];
37
+ this.actorIdsToDelete = new Set();
38
+ this.collisionMatrix = settings?.collisionMatrix ?? {};
39
+ this.actorQuery.getActors().forEach((actor) => this.addProxy(actor));
40
40
  this.actorQuery.addEventListener(AddActor, this.handleActorAdd);
41
41
  this.actorQuery.addEventListener(RemoveActor, this.handleActorRemove);
42
42
  }
@@ -45,20 +45,20 @@ export class CollisionDetectionSubsystem {
45
45
  this.actorQuery.removeEventListener(RemoveActor, this.handleActorRemove);
46
46
  }
47
47
  handleActorAdd = (event) => {
48
- this.addCollisionEntry(event.actor);
48
+ this.addProxy(event.actor);
49
49
  };
50
50
  handleActorRemove = (event) => {
51
- this.entriesToDelete.add(event.actor.id);
51
+ this.actorIdsToDelete.add(event.actor.id);
52
52
  };
53
53
  checkOnReorientation(actor) {
54
- const entry = this.entriesMap.get(actor.id);
55
- if (!entry) {
54
+ const proxy = this.proxiesByActorId.get(actor.id);
55
+ if (!proxy) {
56
56
  return true;
57
57
  }
58
58
  const transform = actor.getComponent(Transform);
59
59
  const collider = actor.getComponent(Collider);
60
- const transformOld = entry.orientationData.transform;
61
- const colliderOld = entry.orientationData.collider;
60
+ const transformOld = proxy.orientationData.transform;
61
+ const colliderOld = proxy.orientationData.collider;
62
62
  return (checkTransform(transform, transformOld) ||
63
63
  checkCollider(collider, colliderOld));
64
64
  }
@@ -80,155 +80,169 @@ export class CollisionDetectionSubsystem {
80
80
  sizeX: collider.sizeX,
81
81
  sizeY: collider.sizeY,
82
82
  radius: collider.radius,
83
+ layer: collider.layer,
83
84
  },
84
85
  };
85
86
  }
86
- addCollisionEntry(actor) {
87
+ addProxy(actor) {
87
88
  const transform = actor.getComponent(Transform);
88
89
  const collider = actor.getComponent(Collider);
89
90
  const geometry = geometryBuilders[collider.type](collider, transform);
90
91
  const aabb = aabbBuilders[collider.type](geometry);
91
- const entry = {
92
+ const proxy = {
92
93
  actor,
93
94
  aabb,
94
95
  geometry,
95
96
  orientationData: this.getOrientationData(actor),
96
97
  };
97
98
  this.axis.x.dispersionCalculator.addToSample(aabb);
98
- this.addToSortedList(entry, 'x');
99
+ this.addToSortedList(proxy, 'x');
99
100
  this.axis.y.dispersionCalculator.addToSample(aabb);
100
- this.addToSortedList(entry, 'y');
101
- this.entriesMap.set(actor.id, entry);
101
+ this.addToSortedList(proxy, 'y');
102
+ this.proxiesByActorId.set(actor.id, proxy);
102
103
  }
103
- updateCollisionEntry(actor) {
104
+ updateProxy(actor) {
104
105
  const transform = actor.getComponent(Transform);
105
106
  const collider = actor.getComponent(Collider);
106
107
  const geometry = geometryBuilders[collider.type](collider, transform);
107
108
  const aabb = aabbBuilders[collider.type](geometry);
108
- const entry = this.entriesMap.get(actor.id);
109
- const prevAABB = entry.aabb;
110
- entry.aabb = aabb;
111
- entry.geometry = geometry;
112
- entry.orientationData = this.getOrientationData(actor);
109
+ const proxy = this.proxiesByActorId.get(actor.id);
110
+ const prevAABB = proxy.aabb;
111
+ proxy.aabb = aabb;
112
+ proxy.geometry = geometry;
113
+ proxy.orientationData = this.getOrientationData(actor);
113
114
  this.axis.x.dispersionCalculator.removeFromSample(prevAABB);
114
115
  this.axis.x.dispersionCalculator.addToSample(aabb);
115
- this.updateSortedList(entry, 'x');
116
+ this.updateSortedList(proxy, 'x');
116
117
  this.axis.y.dispersionCalculator.removeFromSample(prevAABB);
117
118
  this.axis.y.dispersionCalculator.addToSample(aabb);
118
- this.updateSortedList(entry, 'y');
119
+ this.updateSortedList(proxy, 'y');
119
120
  }
120
- addToSortedList(entry, axis) {
121
- const min = { value: entry.aabb.min[axis], entry };
122
- const max = { value: entry.aabb.max[axis], entry };
121
+ addToSortedList(proxy, axis) {
122
+ const min = { value: proxy.aabb.min[axis], proxy };
123
+ const max = { value: proxy.aabb.max[axis], proxy };
123
124
  this.axis[axis].sortedList.push(min, max);
124
- entry.edges ??= {};
125
- entry.edges[axis] = [min, max];
125
+ proxy.edges ??= {};
126
+ proxy.edges[axis] = [min, max];
126
127
  }
127
- updateSortedList(entry, axis) {
128
- const [min, max] = entry.edges[axis];
129
- min.value = entry.aabb.min[axis];
130
- min.entry = entry;
131
- max.value = entry.aabb.max[axis];
132
- max.entry = entry;
128
+ updateSortedList(proxy, axis) {
129
+ const [min, max] = proxy.edges[axis];
130
+ min.value = proxy.aabb.min[axis];
131
+ min.proxy = proxy;
132
+ max.value = proxy.aabb.max[axis];
133
+ max.proxy = proxy;
133
134
  }
134
135
  clearSortedList(axis) {
135
- this.axis[axis].sortedList = this.axis[axis].sortedList.filter((item) => !this.entriesToDelete.has(item.entry.actor.id));
136
+ this.axis[axis].sortedList = this.axis[axis].sortedList.filter((item) => !this.actorIdsToDelete.has(item.proxy.actor.id));
136
137
  }
137
138
  getAxes() {
138
139
  const xDispersion = this.axis.x.dispersionCalculator.getDispersion();
139
140
  const yDispersion = this.axis.y.dispersionCalculator.getDispersion();
140
141
  return xDispersion >= yDispersion ? ['x', 'y'] : ['y', 'x'];
141
142
  }
142
- areStaticBodies(entry1, entry2) {
143
- const { actor: actor1 } = entry1;
144
- const { actor: actor2 } = entry2;
143
+ areStaticBodies(proxy1, proxy2) {
144
+ const { actor: actor1 } = proxy1;
145
+ const { actor: actor2 } = proxy2;
145
146
  const rigidBody1 = actor1.getComponent(RigidBody);
146
147
  const rigidBody2 = actor2.getComponent(RigidBody);
147
148
  return rigidBody1?.type === 'static' && rigidBody2?.type === 'static';
148
149
  }
149
- testAABB(entry1, entry2, axis) {
150
- const aabb1 = entry1.aabb;
151
- const aabb2 = entry2.aabb;
150
+ testAABB(proxy1, proxy2, axis) {
151
+ const aabb1 = proxy1.aabb;
152
+ const aabb2 = proxy2.aabb;
152
153
  return (aabb1.max[axis] > aabb2.min[axis] && aabb1.min[axis] < aabb2.max[axis]);
153
154
  }
155
+ testCollisionLayers(proxy1, proxy2) {
156
+ const collider1 = proxy1.actor.getComponent(Collider);
157
+ const collider2 = proxy2.actor.getComponent(Collider);
158
+ return this.collisionMatrix[collider1.layer]?.[collider2.layer] ?? true;
159
+ }
154
160
  sweepAndPrune() {
155
161
  const [mainAxis, secondAxis] = this.getAxes();
156
162
  const { sortedList } = this.axis[mainAxis];
157
163
  insertionSort(sortedList, (arg1, arg2) => arg1.value - arg2.value);
158
- const activeEntries = new Set();
159
- let collisionIndex = 0;
164
+ const activeProxies = new Set();
165
+ let proxyPairIndex = 0;
160
166
  for (const item of sortedList) {
161
- const { entry } = item;
162
- if (!activeEntries.has(entry)) {
163
- activeEntries.forEach((activeEntry) => {
164
- if (!this.testAABB(entry, activeEntry, secondAxis)) {
167
+ const { proxy } = item;
168
+ if (!activeProxies.has(proxy)) {
169
+ activeProxies.forEach((activeProxy) => {
170
+ if (!this.testAABB(proxy, activeProxy, secondAxis)) {
171
+ return;
172
+ }
173
+ if (this.areStaticBodies(proxy, activeProxy)) {
165
174
  return;
166
175
  }
167
- if (this.areStaticBodies(entry, activeEntry)) {
176
+ if (!this.testCollisionLayers(proxy, activeProxy)) {
168
177
  return;
169
178
  }
170
- this.collisionPairs[collisionIndex] = [entry, activeEntry];
171
- collisionIndex += 1;
179
+ this.proxyPairs[proxyPairIndex] = [proxy, activeProxy];
180
+ proxyPairIndex += 1;
172
181
  });
173
- activeEntries.add(entry);
182
+ activeProxies.add(proxy);
174
183
  }
175
184
  else {
176
- activeEntries.delete(entry);
185
+ activeProxies.delete(proxy);
177
186
  }
178
187
  }
179
- if (this.collisionPairs.length > collisionIndex) {
180
- this.collisionPairs.length = collisionIndex;
188
+ if (this.proxyPairs.length > proxyPairIndex) {
189
+ this.proxyPairs.length = proxyPairIndex;
181
190
  }
182
191
  }
183
- checkOnIntersection(pair) {
184
- const [arg1, arg2] = pair;
185
- const type1 = arg1.actor.getComponent(Collider).type;
186
- const type2 = arg2.actor.getComponent(Collider).type;
187
- return intersectionCheckers[type1][type2](arg1, arg2);
192
+ checkOnIntersection(proxyPair) {
193
+ const [proxy1, proxy2] = proxyPair;
194
+ const type1 = proxy1.actor.getComponent(Collider).type;
195
+ const type2 = proxy2.actor.getComponent(Collider).type;
196
+ return intersectionCheckers[type1][type2](proxy1, proxy2);
188
197
  }
189
- sendCollisionEvent(actor1, actor2, intersection) {
190
- const { mtv1, mtv2 } = intersection;
191
- this.scene.dispatchEventImmediately(Collision, {
198
+ storeContact(contactIndex, actor1, actor2, intersection) {
199
+ this.contacts[contactIndex] ??= {
192
200
  actor1,
193
201
  actor2,
194
- mtv1,
195
- mtv2,
196
- });
197
- this.scene.dispatchEventImmediately(Collision, {
198
- actor1: actor2,
199
- actor2: actor1,
200
- mtv1: mtv2,
201
- mtv2: mtv1,
202
- });
203
- }
204
- clearDeletedEntries() {
205
- if (this.entriesToDelete.size === 0) {
202
+ normal: intersection.normal,
203
+ penetration: intersection.penetration,
204
+ contactPoints: intersection.contactPoints,
205
+ };
206
+ this.contacts[contactIndex].actor1 = actor1;
207
+ this.contacts[contactIndex].actor2 = actor2;
208
+ this.contacts[contactIndex].normal = intersection.normal;
209
+ this.contacts[contactIndex].penetration = intersection.penetration;
210
+ this.contacts[contactIndex].contactPoints = intersection.contactPoints;
211
+ }
212
+ clearDeletedProxies() {
213
+ if (this.actorIdsToDelete.size === 0) {
206
214
  return;
207
215
  }
208
216
  this.clearSortedList('x');
209
217
  this.clearSortedList('y');
210
- this.entriesToDelete.forEach((id) => {
211
- const entry = this.entriesMap.get(id);
212
- this.axis.x.dispersionCalculator.removeFromSample(entry.aabb);
213
- this.axis.y.dispersionCalculator.removeFromSample(entry.aabb);
214
- this.entriesMap.delete(id);
218
+ this.actorIdsToDelete.forEach((id) => {
219
+ const proxy = this.proxiesByActorId.get(id);
220
+ this.axis.x.dispersionCalculator.removeFromSample(proxy.aabb);
221
+ this.axis.y.dispersionCalculator.removeFromSample(proxy.aabb);
222
+ this.proxiesByActorId.delete(id);
215
223
  });
216
- this.entriesToDelete.clear();
224
+ this.actorIdsToDelete.clear();
217
225
  }
218
226
  update() {
219
- this.clearDeletedEntries();
227
+ this.clearDeletedProxies();
220
228
  this.actorQuery.getActors().forEach((actor) => {
221
229
  if (!this.checkOnReorientation(actor)) {
222
230
  return;
223
231
  }
224
- this.updateCollisionEntry(actor);
232
+ this.updateProxy(actor);
225
233
  });
226
234
  this.sweepAndPrune();
227
- this.collisionPairs.forEach((pair) => {
228
- const intersection = this.checkOnIntersection(pair);
235
+ let contactIndex = 0;
236
+ this.proxyPairs.forEach((proxyPair) => {
237
+ const intersection = this.checkOnIntersection(proxyPair);
229
238
  if (intersection) {
230
- this.sendCollisionEvent(pair[0].actor, pair[1].actor, intersection);
239
+ this.storeContact(contactIndex, proxyPair[0].actor, proxyPair[1].actor, intersection);
240
+ contactIndex += 1;
231
241
  }
232
242
  });
243
+ if (this.contacts.length > contactIndex) {
244
+ this.contacts.length = contactIndex;
245
+ }
246
+ return this.contacts;
233
247
  }
234
248
  }
@@ -0,0 +1,10 @@
1
+ import type { Proxy, Intersection } from '../../types';
2
+ /**
3
+ * Checks box colliders for intersection.
4
+ *
5
+ * The SAT (separating axis theorem) is used to find the collision normal
6
+ * and penetration depth. Once the best axis is known, the incident edge is
7
+ * clipped against the reference edge to produce up to two stable contact
8
+ * points for impulse-based collision resolution.
9
+ */
10
+ export declare const checkBoxesIntersection: (arg1: Proxy, arg2: Proxy) => Intersection | false;
@@ -0,0 +1,36 @@
1
+ import { orientNormal } from '../utils';
2
+ import { findMinBoxesOverlap, buildContactPoints } from './utils';
3
+ /**
4
+ * Checks box colliders for intersection.
5
+ *
6
+ * The SAT (separating axis theorem) is used to find the collision normal
7
+ * and penetration depth. Once the best axis is known, the incident edge is
8
+ * clipped against the reference edge to produce up to two stable contact
9
+ * points for impulse-based collision resolution.
10
+ */
11
+ export const checkBoxesIntersection = (arg1, arg2) => {
12
+ const geometry1 = arg1.geometry;
13
+ const geometry2 = arg2.geometry;
14
+ const overlap1 = findMinBoxesOverlap(geometry1, geometry2);
15
+ if (overlap1 === false) {
16
+ return false;
17
+ }
18
+ const overlap2 = findMinBoxesOverlap(geometry2, geometry1);
19
+ if (overlap2 === false) {
20
+ return false;
21
+ }
22
+ const isArg1Reference = overlap1.overlap <= overlap2.overlap;
23
+ const referenceGeometry = isArg1Reference ? geometry1 : geometry2;
24
+ const incidentGeometry = isArg1Reference ? geometry2 : geometry1;
25
+ const referenceOverlap = isArg1Reference ? overlap1 : overlap2;
26
+ const normal = orientNormal(referenceOverlap.axis.clone(), arg1.geometry.center, arg2.geometry.center);
27
+ const referenceNormal = normal.clone();
28
+ if (!isArg1Reference) {
29
+ referenceNormal.multiplyNumber(-1);
30
+ }
31
+ return {
32
+ normal,
33
+ penetration: referenceOverlap.overlap,
34
+ contactPoints: buildContactPoints(referenceGeometry, referenceNormal, incidentGeometry),
35
+ };
36
+ };
@@ -0,0 +1,23 @@
1
+ import { Vector2 } from '../../../../../../../engine/math-lib';
2
+ import type { BoxGeometry, Point } from '../../types';
3
+ export declare const CONTACT_EPSILON = 0.0001;
4
+ export declare const MAX_CONTACT_POINTS = 2;
5
+ interface Projection {
6
+ min: number;
7
+ max: number;
8
+ }
9
+ export interface AxisOverlap {
10
+ axis: Vector2;
11
+ overlap: number;
12
+ }
13
+ export declare const projectPolygon: (polygon: BoxGeometry, axis: Vector2) => Projection;
14
+ export declare const findMinBoxesOverlap: (geometry1: BoxGeometry, geometry2: BoxGeometry) => AxisOverlap | false;
15
+ /**
16
+ * Builds up to two box-vs-box contact points by clipping the incident edge
17
+ * against the side planes of the reference edge.
18
+ *
19
+ * The returned points lie on the reference face plane, which makes them
20
+ * suitable to use as world-space contact points in a simple impulse solver.
21
+ */
22
+ export declare const buildContactPoints: (referencePolygon: BoxGeometry, referenceNormal: Vector2, incidentPolygon: BoxGeometry) => Point[];
23
+ export {};
@@ -0,0 +1,143 @@
1
+ import { Vector2, VectorOps } from '../../../../../../../engine/math-lib';
2
+ import { INTERSECTION_EPSILON } from '../utils';
3
+ export const CONTACT_EPSILON = 1e-4;
4
+ export const MAX_CONTACT_POINTS = 2;
5
+ export const projectPolygon = (polygon, axis) => {
6
+ const initialProjection = VectorOps.dotProduct(polygon.points[0], axis);
7
+ const projection = {
8
+ min: initialProjection,
9
+ max: initialProjection,
10
+ };
11
+ for (let i = 1; i < polygon.points.length; i += 1) {
12
+ const value = VectorOps.dotProduct(polygon.points[i], axis);
13
+ if (value < projection.min) {
14
+ projection.min = value;
15
+ }
16
+ else if (value > projection.max) {
17
+ projection.max = value;
18
+ }
19
+ }
20
+ return projection;
21
+ };
22
+ export const findMinBoxesOverlap = (geometry1, geometry2) => {
23
+ let minOverlap = Infinity;
24
+ let bestAxis = geometry1.edges[0].normal;
25
+ for (const edge of geometry1.edges) {
26
+ const axis = edge.normal;
27
+ const projection1 = projectPolygon(geometry1, axis);
28
+ const projection2 = projectPolygon(geometry2, axis);
29
+ const distance1 = projection1.min - projection2.max;
30
+ const distance2 = projection2.min - projection1.max;
31
+ if (distance1 > INTERSECTION_EPSILON || distance2 > INTERSECTION_EPSILON) {
32
+ return false;
33
+ }
34
+ const overlap = Math.min(Math.abs(distance1), Math.abs(distance2));
35
+ if (overlap < minOverlap) {
36
+ minOverlap = overlap;
37
+ bestAxis = axis;
38
+ }
39
+ }
40
+ return {
41
+ axis: bestAxis,
42
+ overlap: minOverlap,
43
+ };
44
+ };
45
+ const clipSegmentToLine = (vertices, normal, offset) => {
46
+ const clipped = [];
47
+ const distance1 = VectorOps.dotProduct(vertices[0], normal) - offset;
48
+ const distance2 = VectorOps.dotProduct(vertices[1], normal) - offset;
49
+ if (distance1 <= CONTACT_EPSILON) {
50
+ clipped.push(vertices[0]);
51
+ }
52
+ if (distance2 <= CONTACT_EPSILON) {
53
+ clipped.push(vertices[1]);
54
+ }
55
+ if (distance1 * distance2 < 0) {
56
+ const t = distance1 / (distance1 - distance2);
57
+ const point1 = vertices[0];
58
+ const point2 = vertices[1];
59
+ clipped.push({
60
+ x: point1.x + (point2.x - point1.x) * t,
61
+ y: point1.y + (point2.y - point1.y) * t,
62
+ });
63
+ }
64
+ return clipped;
65
+ };
66
+ const getMostAntiParallelEdge = (polygon, normal) => {
67
+ let bestEdge = polygon.edges[0];
68
+ let minDot = VectorOps.dotProduct(bestEdge.normal, normal);
69
+ for (const edge of polygon.edges) {
70
+ const dot = VectorOps.dotProduct(edge.normal, normal);
71
+ if (dot < minDot) {
72
+ minDot = dot;
73
+ bestEdge = edge;
74
+ }
75
+ }
76
+ return bestEdge;
77
+ };
78
+ const getMostParallelEdge = (polygon, normal) => {
79
+ let bestEdge = polygon.edges[0];
80
+ let maxDot = VectorOps.dotProduct(bestEdge.normal, normal);
81
+ for (const edge of polygon.edges) {
82
+ const dot = VectorOps.dotProduct(edge.normal, normal);
83
+ if (dot > maxDot) {
84
+ maxDot = dot;
85
+ bestEdge = edge;
86
+ }
87
+ }
88
+ return bestEdge;
89
+ };
90
+ const dedupePoints = (points) => {
91
+ const unique = [];
92
+ for (const point of points) {
93
+ if (unique.some((entry) => Math.abs(entry.x - point.x) <= CONTACT_EPSILON &&
94
+ Math.abs(entry.y - point.y) <= CONTACT_EPSILON)) {
95
+ continue;
96
+ }
97
+ unique.push(point);
98
+ if (unique.length === MAX_CONTACT_POINTS) {
99
+ return unique;
100
+ }
101
+ }
102
+ return unique;
103
+ };
104
+ /**
105
+ * Builds up to two box-vs-box contact points by clipping the incident edge
106
+ * against the side planes of the reference edge.
107
+ *
108
+ * The returned points lie on the reference face plane, which makes them
109
+ * suitable to use as world-space contact points in a simple impulse solver.
110
+ */
111
+ export const buildContactPoints = (referencePolygon, referenceNormal, incidentPolygon) => {
112
+ const referenceEdge = getMostParallelEdge(referencePolygon, referenceNormal);
113
+ const incidentEdge = getMostAntiParallelEdge(incidentPolygon, referenceNormal);
114
+ const tangent = new Vector2(referenceEdge.point2.x - referenceEdge.point1.x, referenceEdge.point2.y - referenceEdge.point1.y);
115
+ tangent.normalize();
116
+ const minTangentOffset = VectorOps.dotProduct(referenceEdge.point1, tangent);
117
+ const maxTangentOffset = VectorOps.dotProduct(referenceEdge.point2, tangent);
118
+ let clippedPoints = [incidentEdge.point1, incidentEdge.point2];
119
+ clippedPoints = clipSegmentToLine(clippedPoints, tangent, maxTangentOffset);
120
+ if (clippedPoints.length === 0) {
121
+ return [];
122
+ }
123
+ if (clippedPoints.length === 2) {
124
+ tangent.multiplyNumber(-1);
125
+ clippedPoints = clipSegmentToLine(clippedPoints, tangent, -minTangentOffset);
126
+ if (clippedPoints.length === 0) {
127
+ return [];
128
+ }
129
+ }
130
+ const frontOffset = VectorOps.dotProduct(referenceEdge.point1, referenceNormal);
131
+ const contacts = [];
132
+ clippedPoints.forEach((point) => {
133
+ const separation = VectorOps.dotProduct(point, referenceNormal) - frontOffset;
134
+ if (separation > CONTACT_EPSILON) {
135
+ return;
136
+ }
137
+ contacts.push({
138
+ x: point.x - referenceNormal.x * separation,
139
+ y: point.y - referenceNormal.y * separation,
140
+ });
141
+ });
142
+ return dedupePoints(contacts);
143
+ };
@@ -0,0 +1,9 @@
1
+ import type { Proxy, Intersection } from '../../types';
2
+ /**
3
+ * Checks box and circle colliders for intersection.
4
+ *
5
+ * The closest point on the box is used as the world-space contact point.
6
+ * For circles inside the box, the nearest face normal is used as the
7
+ * collision direction and penetration is measured to that face.
8
+ */
9
+ export declare const checkBoxAndCircleIntersection: (arg1: Proxy, arg2: Proxy) => Intersection | false;
@@ -0,0 +1,47 @@
1
+ import { Collider } from '../../../../../../components';
2
+ import { MathOps, Vector2, VectorOps, } from '../../../../../../../engine/math-lib';
3
+ import { orientNormal, INTERSECTION_EPSILON } from '../utils';
4
+ const buildNormal = (circle, closestEdge, closestPoint) => {
5
+ let normal = new Vector2(circle.center.x - closestPoint.x, circle.center.y - closestPoint.y);
6
+ if (normal.magnitude === 0) {
7
+ normal = closestEdge.normal.clone();
8
+ }
9
+ return normal.normalize();
10
+ };
11
+ /**
12
+ * Checks box and circle colliders for intersection.
13
+ *
14
+ * The closest point on the box is used as the world-space contact point.
15
+ * For circles inside the box, the nearest face normal is used as the
16
+ * collision direction and penetration is measured to that face.
17
+ */
18
+ export const checkBoxAndCircleIntersection = (arg1, arg2) => {
19
+ const isBoxFirst = arg1.actor.getComponent(Collider).type === 'box';
20
+ const box = (isBoxFirst ? arg1.geometry : arg2.geometry);
21
+ const circle = (isBoxFirst ? arg2.geometry : arg1.geometry);
22
+ let closestEdge = box.edges[0];
23
+ let closestPoint = box.points[0];
24
+ let minDistance = Infinity;
25
+ box.edges.forEach((edge) => {
26
+ const candidate = VectorOps.getClosestPointOnEdge(circle.center, edge);
27
+ const distance = MathOps.getDistanceBetweenTwoPoints(candidate.x, circle.center.x, candidate.y, circle.center.y);
28
+ if (distance < minDistance) {
29
+ minDistance = distance;
30
+ closestEdge = edge;
31
+ closestPoint = candidate;
32
+ }
33
+ });
34
+ const isCircleInsideBox = VectorOps.isPointInPolygon(circle.center, box.edges);
35
+ if (!isCircleInsideBox &&
36
+ minDistance > circle.radius + INTERSECTION_EPSILON) {
37
+ return false;
38
+ }
39
+ const penetration = isCircleInsideBox
40
+ ? circle.radius + minDistance
41
+ : Math.max(0, circle.radius - minDistance);
42
+ return {
43
+ normal: orientNormal(buildNormal(circle, closestEdge, closestPoint), arg1.geometry.center, arg2.geometry.center),
44
+ penetration,
45
+ contactPoints: [closestPoint],
46
+ };
47
+ };
@@ -0,0 +1,12 @@
1
+ import type { Proxy, Intersection } from '../../types';
2
+ /**
3
+ * Checks circles for intersection.
4
+ *
5
+ * The manifold is straightforward:
6
+ * - the normal follows the line between circle centers
7
+ * - penetration is the overlap of the radii along that line
8
+ * - the contact point lies on the surface of the first circle
9
+ *
10
+ * When both centers are equal, the X axis is used as a deterministic fallback.
11
+ */
12
+ export declare const checkCirclesIntersection: (arg1: Proxy, arg2: Proxy) => Intersection | false;
@@ -0,0 +1,49 @@
1
+ import { Vector2 } from '../../../../../../../engine/math-lib';
2
+ import { INTERSECTION_EPSILON } from '../utils';
3
+ /**
4
+ * Checks circles for intersection.
5
+ *
6
+ * The manifold is straightforward:
7
+ * - the normal follows the line between circle centers
8
+ * - penetration is the overlap of the radii along that line
9
+ * - the contact point lies on the surface of the first circle
10
+ *
11
+ * When both centers are equal, the X axis is used as a deterministic fallback.
12
+ */
13
+ export const checkCirclesIntersection = (arg1, arg2) => {
14
+ const { radius: radius1 } = arg1.geometry;
15
+ const { radius: radius2 } = arg2.geometry;
16
+ const { x: x1, y: y1 } = arg1.geometry.center;
17
+ const { x: x2, y: y2 } = arg2.geometry.center;
18
+ const offsetX = x2 - x1;
19
+ const offsetY = y2 - y1;
20
+ const distance = Math.sqrt(offsetX ** 2 + offsetY ** 2);
21
+ if (distance > radius1 + radius2 + INTERSECTION_EPSILON) {
22
+ return false;
23
+ }
24
+ const penetration = Math.max(0, radius1 + radius2 - distance);
25
+ if (distance === 0) {
26
+ return {
27
+ normal: new Vector2(1, 0),
28
+ penetration,
29
+ contactPoints: [
30
+ {
31
+ x: x1 + radius1,
32
+ y: y1,
33
+ },
34
+ ],
35
+ };
36
+ }
37
+ const normal = new Vector2(offsetX, offsetY);
38
+ normal.normalize();
39
+ return {
40
+ normal,
41
+ penetration,
42
+ contactPoints: [
43
+ {
44
+ x: x1 + normal.x * radius1,
45
+ y: y1 + normal.y * radius1,
46
+ },
47
+ ],
48
+ };
49
+ };
@@ -1,3 +1,3 @@
1
- import type { CollisionEntry, Intersection } from '../types';
2
- export type CheckIntersectionFn = (arg1: CollisionEntry, arg2: CollisionEntry) => Intersection | false;
1
+ import type { Proxy, Intersection } from '../types';
2
+ export type CheckIntersectionFn = (arg1: Proxy, arg2: Proxy) => Intersection | false;
3
3
  export declare const intersectionCheckers: Record<string, Record<string, CheckIntersectionFn>>;