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.
- package/build/contrib/components/collider/index.d.ts +6 -2
- package/build/contrib/components/collider/index.js +7 -2
- package/build/contrib/components/rigid-body/index.d.ts +29 -17
- package/build/contrib/components/rigid-body/index.js +64 -21
- package/build/contrib/events/index.d.ts +10 -75
- package/build/contrib/events/index.js +0 -36
- package/build/contrib/systems/physics-system/index.d.ts +0 -1
- package/build/contrib/systems/physics-system/index.js +6 -11
- package/build/contrib/systems/physics-system/subsystems/collision-broadcast/collision.d.ts +5 -3
- package/build/contrib/systems/physics-system/subsystems/collision-broadcast/collision.js +7 -5
- package/build/contrib/systems/physics-system/subsystems/collision-broadcast/index.d.ts +4 -6
- package/build/contrib/systems/physics-system/subsystems/collision-broadcast/index.js +20 -17
- package/build/contrib/systems/physics-system/subsystems/collision-detection/index.d.ts +12 -9
- package/build/contrib/systems/physics-system/subsystems/collision-detection/index.js +107 -93
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/box-box/check-boxes-intersection.d.ts +10 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/box-box/check-boxes-intersection.js +36 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/box-box/utils.d.ts +23 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/box-box/utils.js +143 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/box-circle/check-box-and-circle-intersection.d.ts +9 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/box-circle/check-box-and-circle-intersection.js +47 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/circle-circle/check-circles-intersection.d.ts +12 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/circle-circle/check-circles-intersection.js +49 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/index.d.ts +2 -2
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/index.js +3 -3
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/tests/helpers.d.ts +17 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/tests/helpers.js +66 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/utils.d.ts +4 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/utils.js +9 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/reorientation-checkers/check-collider.js +3 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/types.d.ts +14 -5
- package/build/contrib/systems/physics-system/subsystems/collision-detection/types.js +0 -3
- package/build/contrib/systems/physics-system/subsystems/constraint-solver/index.d.ts +7 -10
- package/build/contrib/systems/physics-system/subsystems/constraint-solver/index.js +117 -79
- package/build/contrib/systems/physics-system/subsystems/index.d.ts +0 -1
- package/build/contrib/systems/physics-system/subsystems/index.js +0 -1
- package/build/contrib/systems/physics-system/subsystems/physics/index.d.ts +3 -9
- package/build/contrib/systems/physics-system/subsystems/physics/index.js +57 -93
- package/build/contrib/systems/physics-system/types.d.ts +9 -0
- package/build/engine/math-lib/vector/ops.d.ts +3 -3
- package/build/engine/math-lib/vector/ops.js +8 -5
- package/build/engine/math-lib/vector/vector2.d.ts +22 -7
- package/build/engine/math-lib/vector/vector2.js +29 -5
- package/build/events/index.d.ts +2 -2
- package/build/events/index.js +1 -1
- package/package.json +1 -1
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/check-box-and-circle-intersection.d.ts +0 -16
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/check-box-and-circle-intersection.js +0 -80
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/check-boxes-intersection.d.ts +0 -6
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/check-boxes-intersection.js +0 -72
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/check-circles-intersection.d.ts +0 -11
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/check-circles-intersection.js +0 -39
- package/build/contrib/systems/physics-system/subsystems/collision-solver/index.d.ts +0 -10
- 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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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.
|
|
35
|
-
this.
|
|
36
|
-
this.
|
|
37
|
-
this.
|
|
38
|
-
|
|
39
|
-
|
|
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.
|
|
48
|
+
this.addProxy(event.actor);
|
|
49
49
|
};
|
|
50
50
|
handleActorRemove = (event) => {
|
|
51
|
-
this.
|
|
51
|
+
this.actorIdsToDelete.add(event.actor.id);
|
|
52
52
|
};
|
|
53
53
|
checkOnReorientation(actor) {
|
|
54
|
-
const
|
|
55
|
-
if (!
|
|
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 =
|
|
61
|
-
const colliderOld =
|
|
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
|
-
|
|
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
|
|
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(
|
|
99
|
+
this.addToSortedList(proxy, 'x');
|
|
99
100
|
this.axis.y.dispersionCalculator.addToSample(aabb);
|
|
100
|
-
this.addToSortedList(
|
|
101
|
-
this.
|
|
101
|
+
this.addToSortedList(proxy, 'y');
|
|
102
|
+
this.proxiesByActorId.set(actor.id, proxy);
|
|
102
103
|
}
|
|
103
|
-
|
|
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
|
|
109
|
-
const prevAABB =
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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(
|
|
116
|
+
this.updateSortedList(proxy, 'x');
|
|
116
117
|
this.axis.y.dispersionCalculator.removeFromSample(prevAABB);
|
|
117
118
|
this.axis.y.dispersionCalculator.addToSample(aabb);
|
|
118
|
-
this.updateSortedList(
|
|
119
|
+
this.updateSortedList(proxy, 'y');
|
|
119
120
|
}
|
|
120
|
-
addToSortedList(
|
|
121
|
-
const min = { value:
|
|
122
|
-
const max = { value:
|
|
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
|
-
|
|
125
|
-
|
|
125
|
+
proxy.edges ??= {};
|
|
126
|
+
proxy.edges[axis] = [min, max];
|
|
126
127
|
}
|
|
127
|
-
updateSortedList(
|
|
128
|
-
const [min, max] =
|
|
129
|
-
min.value =
|
|
130
|
-
min.
|
|
131
|
-
max.value =
|
|
132
|
-
max.
|
|
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.
|
|
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(
|
|
143
|
-
const { actor: actor1 } =
|
|
144
|
-
const { actor: actor2 } =
|
|
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(
|
|
150
|
-
const aabb1 =
|
|
151
|
-
const aabb2 =
|
|
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
|
|
159
|
-
let
|
|
164
|
+
const activeProxies = new Set();
|
|
165
|
+
let proxyPairIndex = 0;
|
|
160
166
|
for (const item of sortedList) {
|
|
161
|
-
const {
|
|
162
|
-
if (!
|
|
163
|
-
|
|
164
|
-
if (!this.testAABB(
|
|
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.
|
|
176
|
+
if (!this.testCollisionLayers(proxy, activeProxy)) {
|
|
168
177
|
return;
|
|
169
178
|
}
|
|
170
|
-
this.
|
|
171
|
-
|
|
179
|
+
this.proxyPairs[proxyPairIndex] = [proxy, activeProxy];
|
|
180
|
+
proxyPairIndex += 1;
|
|
172
181
|
});
|
|
173
|
-
|
|
182
|
+
activeProxies.add(proxy);
|
|
174
183
|
}
|
|
175
184
|
else {
|
|
176
|
-
|
|
185
|
+
activeProxies.delete(proxy);
|
|
177
186
|
}
|
|
178
187
|
}
|
|
179
|
-
if (this.
|
|
180
|
-
this.
|
|
188
|
+
if (this.proxyPairs.length > proxyPairIndex) {
|
|
189
|
+
this.proxyPairs.length = proxyPairIndex;
|
|
181
190
|
}
|
|
182
191
|
}
|
|
183
|
-
checkOnIntersection(
|
|
184
|
-
const [
|
|
185
|
-
const type1 =
|
|
186
|
-
const type2 =
|
|
187
|
-
return intersectionCheckers[type1][type2](
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
this.scene.dispatchEventImmediately(Collision, {
|
|
198
|
+
storeContact(contactIndex, actor1, actor2, intersection) {
|
|
199
|
+
this.contacts[contactIndex] ??= {
|
|
192
200
|
actor1,
|
|
193
201
|
actor2,
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
if (this.
|
|
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.
|
|
211
|
-
const
|
|
212
|
-
this.axis.x.dispersionCalculator.removeFromSample(
|
|
213
|
-
this.axis.y.dispersionCalculator.removeFromSample(
|
|
214
|
-
this.
|
|
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.
|
|
224
|
+
this.actorIdsToDelete.clear();
|
|
217
225
|
}
|
|
218
226
|
update() {
|
|
219
|
-
this.
|
|
227
|
+
this.clearDeletedProxies();
|
|
220
228
|
this.actorQuery.getActors().forEach((actor) => {
|
|
221
229
|
if (!this.checkOnReorientation(actor)) {
|
|
222
230
|
return;
|
|
223
231
|
}
|
|
224
|
-
this.
|
|
232
|
+
this.updateProxy(actor);
|
|
225
233
|
});
|
|
226
234
|
this.sweepAndPrune();
|
|
227
|
-
|
|
228
|
-
|
|
235
|
+
let contactIndex = 0;
|
|
236
|
+
this.proxyPairs.forEach((proxyPair) => {
|
|
237
|
+
const intersection = this.checkOnIntersection(proxyPair);
|
|
229
238
|
if (intersection) {
|
|
230
|
-
this.
|
|
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 {
|
|
2
|
-
export type CheckIntersectionFn = (arg1:
|
|
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>>;
|