@melonjs/matter-adapter 1.0.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/CHANGELOG.md +17 -0
- package/LICENSE +21 -0
- package/README.md +618 -0
- package/build/index.d.ts +208 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +1173 -0
- package/build/index.js.map +7 -0
- package/package.json +74 -0
package/build/index.js
ADDED
|
@@ -0,0 +1,1173 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* melonJS physics adapter for matter-js - 1.0.0
|
|
3
|
+
* http://www.melonjs.org
|
|
4
|
+
* @melonjs/matter-adapter is licensed under the MIT License.
|
|
5
|
+
* http://www.opensource.org/licenses/mit-license
|
|
6
|
+
* @copyright (C) 2011 - 2026 Olivier Biot (AltByte Pte Ltd)
|
|
7
|
+
*/
|
|
8
|
+
var __create = Object.create;
|
|
9
|
+
var __defProp = Object.defineProperty;
|
|
10
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
11
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
12
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
13
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
14
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
15
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
16
|
+
};
|
|
17
|
+
var __copyProps = (to, from, except, desc) => {
|
|
18
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
19
|
+
for (let key of __getOwnPropNames(from))
|
|
20
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
21
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
22
|
+
}
|
|
23
|
+
return to;
|
|
24
|
+
};
|
|
25
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
26
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
27
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
28
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
29
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
30
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
31
|
+
mod
|
|
32
|
+
));
|
|
33
|
+
|
|
34
|
+
// ../../node_modules/.pnpm/poly-decomp@0.3.0/node_modules/poly-decomp/src/index.js
|
|
35
|
+
var require_src = __commonJS({
|
|
36
|
+
"../../node_modules/.pnpm/poly-decomp@0.3.0/node_modules/poly-decomp/src/index.js"(exports, module) {
|
|
37
|
+
module.exports = {
|
|
38
|
+
decomp: polygonDecomp,
|
|
39
|
+
quickDecomp: polygonQuickDecomp,
|
|
40
|
+
isSimple: polygonIsSimple,
|
|
41
|
+
removeCollinearPoints: polygonRemoveCollinearPoints,
|
|
42
|
+
removeDuplicatePoints: polygonRemoveDuplicatePoints,
|
|
43
|
+
makeCCW: polygonMakeCCW
|
|
44
|
+
};
|
|
45
|
+
function lineInt(l1, l2, precision) {
|
|
46
|
+
precision = precision || 0;
|
|
47
|
+
var i = [0, 0];
|
|
48
|
+
var a1, b1, c1, a2, b2, c2, det;
|
|
49
|
+
a1 = l1[1][1] - l1[0][1];
|
|
50
|
+
b1 = l1[0][0] - l1[1][0];
|
|
51
|
+
c1 = a1 * l1[0][0] + b1 * l1[0][1];
|
|
52
|
+
a2 = l2[1][1] - l2[0][1];
|
|
53
|
+
b2 = l2[0][0] - l2[1][0];
|
|
54
|
+
c2 = a2 * l2[0][0] + b2 * l2[0][1];
|
|
55
|
+
det = a1 * b2 - a2 * b1;
|
|
56
|
+
if (!scalar_eq(det, 0, precision)) {
|
|
57
|
+
i[0] = (b2 * c1 - b1 * c2) / det;
|
|
58
|
+
i[1] = (a1 * c2 - a2 * c1) / det;
|
|
59
|
+
}
|
|
60
|
+
return i;
|
|
61
|
+
}
|
|
62
|
+
function lineSegmentsIntersect(p1, p2, q1, q2) {
|
|
63
|
+
var dx = p2[0] - p1[0];
|
|
64
|
+
var dy = p2[1] - p1[1];
|
|
65
|
+
var da = q2[0] - q1[0];
|
|
66
|
+
var db = q2[1] - q1[1];
|
|
67
|
+
if (da * dy - db * dx === 0) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
var s = (dx * (q1[1] - p1[1]) + dy * (p1[0] - q1[0])) / (da * dy - db * dx);
|
|
71
|
+
var t = (da * (p1[1] - q1[1]) + db * (q1[0] - p1[0])) / (db * dx - da * dy);
|
|
72
|
+
return s >= 0 && s <= 1 && t >= 0 && t <= 1;
|
|
73
|
+
}
|
|
74
|
+
function triangleArea(a, b, c) {
|
|
75
|
+
return (b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1]);
|
|
76
|
+
}
|
|
77
|
+
function isLeft(a, b, c) {
|
|
78
|
+
return triangleArea(a, b, c) > 0;
|
|
79
|
+
}
|
|
80
|
+
function isLeftOn(a, b, c) {
|
|
81
|
+
return triangleArea(a, b, c) >= 0;
|
|
82
|
+
}
|
|
83
|
+
function isRight(a, b, c) {
|
|
84
|
+
return triangleArea(a, b, c) < 0;
|
|
85
|
+
}
|
|
86
|
+
function isRightOn(a, b, c) {
|
|
87
|
+
return triangleArea(a, b, c) <= 0;
|
|
88
|
+
}
|
|
89
|
+
var tmpPoint1 = [];
|
|
90
|
+
var tmpPoint2 = [];
|
|
91
|
+
function collinear(a, b, c, thresholdAngle) {
|
|
92
|
+
if (!thresholdAngle) {
|
|
93
|
+
return triangleArea(a, b, c) === 0;
|
|
94
|
+
} else {
|
|
95
|
+
var ab = tmpPoint1, bc = tmpPoint2;
|
|
96
|
+
ab[0] = b[0] - a[0];
|
|
97
|
+
ab[1] = b[1] - a[1];
|
|
98
|
+
bc[0] = c[0] - b[0];
|
|
99
|
+
bc[1] = c[1] - b[1];
|
|
100
|
+
var dot = ab[0] * bc[0] + ab[1] * bc[1], magA = Math.sqrt(ab[0] * ab[0] + ab[1] * ab[1]), magB = Math.sqrt(bc[0] * bc[0] + bc[1] * bc[1]), angle = Math.acos(dot / (magA * magB));
|
|
101
|
+
return angle < thresholdAngle;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function sqdist(a, b) {
|
|
105
|
+
var dx = b[0] - a[0];
|
|
106
|
+
var dy = b[1] - a[1];
|
|
107
|
+
return dx * dx + dy * dy;
|
|
108
|
+
}
|
|
109
|
+
function polygonAt(polygon, i) {
|
|
110
|
+
var s = polygon.length;
|
|
111
|
+
return polygon[i < 0 ? i % s + s : i % s];
|
|
112
|
+
}
|
|
113
|
+
function polygonClear(polygon) {
|
|
114
|
+
polygon.length = 0;
|
|
115
|
+
}
|
|
116
|
+
function polygonAppend(polygon, poly, from, to) {
|
|
117
|
+
for (var i = from; i < to; i++) {
|
|
118
|
+
polygon.push(poly[i]);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function polygonMakeCCW(polygon) {
|
|
122
|
+
var br = 0, v = polygon;
|
|
123
|
+
for (var i = 1; i < polygon.length; ++i) {
|
|
124
|
+
if (v[i][1] < v[br][1] || v[i][1] === v[br][1] && v[i][0] > v[br][0]) {
|
|
125
|
+
br = i;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (!isLeft(polygonAt(polygon, br - 1), polygonAt(polygon, br), polygonAt(polygon, br + 1))) {
|
|
129
|
+
polygonReverse(polygon);
|
|
130
|
+
return true;
|
|
131
|
+
} else {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function polygonReverse(polygon) {
|
|
136
|
+
var tmp = [];
|
|
137
|
+
var N = polygon.length;
|
|
138
|
+
for (var i = 0; i !== N; i++) {
|
|
139
|
+
tmp.push(polygon.pop());
|
|
140
|
+
}
|
|
141
|
+
for (var i = 0; i !== N; i++) {
|
|
142
|
+
polygon[i] = tmp[i];
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function polygonIsReflex(polygon, i) {
|
|
146
|
+
return isRight(polygonAt(polygon, i - 1), polygonAt(polygon, i), polygonAt(polygon, i + 1));
|
|
147
|
+
}
|
|
148
|
+
var tmpLine1 = [];
|
|
149
|
+
var tmpLine2 = [];
|
|
150
|
+
function polygonCanSee(polygon, a, b) {
|
|
151
|
+
var p, dist, l1 = tmpLine1, l2 = tmpLine2;
|
|
152
|
+
if (isLeftOn(polygonAt(polygon, a + 1), polygonAt(polygon, a), polygonAt(polygon, b)) && isRightOn(polygonAt(polygon, a - 1), polygonAt(polygon, a), polygonAt(polygon, b))) {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
dist = sqdist(polygonAt(polygon, a), polygonAt(polygon, b));
|
|
156
|
+
for (var i = 0; i !== polygon.length; ++i) {
|
|
157
|
+
if ((i + 1) % polygon.length === a || i === a) {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (isLeftOn(polygonAt(polygon, a), polygonAt(polygon, b), polygonAt(polygon, i + 1)) && isRightOn(polygonAt(polygon, a), polygonAt(polygon, b), polygonAt(polygon, i))) {
|
|
161
|
+
l1[0] = polygonAt(polygon, a);
|
|
162
|
+
l1[1] = polygonAt(polygon, b);
|
|
163
|
+
l2[0] = polygonAt(polygon, i);
|
|
164
|
+
l2[1] = polygonAt(polygon, i + 1);
|
|
165
|
+
p = lineInt(l1, l2);
|
|
166
|
+
if (sqdist(polygonAt(polygon, a), p) < dist) {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
function polygonCanSee2(polygon, a, b) {
|
|
174
|
+
for (var i = 0; i !== polygon.length; ++i) {
|
|
175
|
+
if (i === a || i === b || (i + 1) % polygon.length === a || (i + 1) % polygon.length === b) {
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (lineSegmentsIntersect(polygonAt(polygon, a), polygonAt(polygon, b), polygonAt(polygon, i), polygonAt(polygon, i + 1))) {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
function polygonCopy(polygon, i, j, targetPoly) {
|
|
185
|
+
var p = targetPoly || [];
|
|
186
|
+
polygonClear(p);
|
|
187
|
+
if (i < j) {
|
|
188
|
+
for (var k = i; k <= j; k++) {
|
|
189
|
+
p.push(polygon[k]);
|
|
190
|
+
}
|
|
191
|
+
} else {
|
|
192
|
+
for (var k = 0; k <= j; k++) {
|
|
193
|
+
p.push(polygon[k]);
|
|
194
|
+
}
|
|
195
|
+
for (var k = i; k < polygon.length; k++) {
|
|
196
|
+
p.push(polygon[k]);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return p;
|
|
200
|
+
}
|
|
201
|
+
function polygonGetCutEdges(polygon) {
|
|
202
|
+
var min = [], tmp1 = [], tmp2 = [], tmpPoly = [];
|
|
203
|
+
var nDiags = Number.MAX_VALUE;
|
|
204
|
+
for (var i = 0; i < polygon.length; ++i) {
|
|
205
|
+
if (polygonIsReflex(polygon, i)) {
|
|
206
|
+
for (var j = 0; j < polygon.length; ++j) {
|
|
207
|
+
if (polygonCanSee(polygon, i, j)) {
|
|
208
|
+
tmp1 = polygonGetCutEdges(polygonCopy(polygon, i, j, tmpPoly));
|
|
209
|
+
tmp2 = polygonGetCutEdges(polygonCopy(polygon, j, i, tmpPoly));
|
|
210
|
+
for (var k = 0; k < tmp2.length; k++) {
|
|
211
|
+
tmp1.push(tmp2[k]);
|
|
212
|
+
}
|
|
213
|
+
if (tmp1.length < nDiags) {
|
|
214
|
+
min = tmp1;
|
|
215
|
+
nDiags = tmp1.length;
|
|
216
|
+
min.push([polygonAt(polygon, i), polygonAt(polygon, j)]);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return min;
|
|
223
|
+
}
|
|
224
|
+
function polygonDecomp(polygon) {
|
|
225
|
+
var edges = polygonGetCutEdges(polygon);
|
|
226
|
+
if (edges.length > 0) {
|
|
227
|
+
return polygonSlice(polygon, edges);
|
|
228
|
+
} else {
|
|
229
|
+
return [polygon];
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function polygonSlice(polygon, cutEdges) {
|
|
233
|
+
if (cutEdges.length === 0) {
|
|
234
|
+
return [polygon];
|
|
235
|
+
}
|
|
236
|
+
if (cutEdges instanceof Array && cutEdges.length && cutEdges[0] instanceof Array && cutEdges[0].length === 2 && cutEdges[0][0] instanceof Array) {
|
|
237
|
+
var polys = [polygon];
|
|
238
|
+
for (var i = 0; i < cutEdges.length; i++) {
|
|
239
|
+
var cutEdge = cutEdges[i];
|
|
240
|
+
for (var j = 0; j < polys.length; j++) {
|
|
241
|
+
var poly = polys[j];
|
|
242
|
+
var result = polygonSlice(poly, cutEdge);
|
|
243
|
+
if (result) {
|
|
244
|
+
polys.splice(j, 1);
|
|
245
|
+
polys.push(result[0], result[1]);
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return polys;
|
|
251
|
+
} else {
|
|
252
|
+
var cutEdge = cutEdges;
|
|
253
|
+
var i = polygon.indexOf(cutEdge[0]);
|
|
254
|
+
var j = polygon.indexOf(cutEdge[1]);
|
|
255
|
+
if (i !== -1 && j !== -1) {
|
|
256
|
+
return [
|
|
257
|
+
polygonCopy(polygon, i, j),
|
|
258
|
+
polygonCopy(polygon, j, i)
|
|
259
|
+
];
|
|
260
|
+
} else {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
function polygonIsSimple(polygon) {
|
|
266
|
+
var path = polygon, i;
|
|
267
|
+
for (i = 0; i < path.length - 1; i++) {
|
|
268
|
+
for (var j = 0; j < i - 1; j++) {
|
|
269
|
+
if (lineSegmentsIntersect(path[i], path[i + 1], path[j], path[j + 1])) {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
for (i = 1; i < path.length - 2; i++) {
|
|
275
|
+
if (lineSegmentsIntersect(path[0], path[path.length - 1], path[i], path[i + 1])) {
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
function getIntersectionPoint(p1, p2, q1, q2, delta) {
|
|
282
|
+
delta = delta || 0;
|
|
283
|
+
var a1 = p2[1] - p1[1];
|
|
284
|
+
var b1 = p1[0] - p2[0];
|
|
285
|
+
var c1 = a1 * p1[0] + b1 * p1[1];
|
|
286
|
+
var a2 = q2[1] - q1[1];
|
|
287
|
+
var b2 = q1[0] - q2[0];
|
|
288
|
+
var c2 = a2 * q1[0] + b2 * q1[1];
|
|
289
|
+
var det = a1 * b2 - a2 * b1;
|
|
290
|
+
if (!scalar_eq(det, 0, delta)) {
|
|
291
|
+
return [(b2 * c1 - b1 * c2) / det, (a1 * c2 - a2 * c1) / det];
|
|
292
|
+
} else {
|
|
293
|
+
return [0, 0];
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
function polygonQuickDecomp(polygon, result, reflexVertices, steinerPoints, delta, maxlevel, level) {
|
|
297
|
+
maxlevel = maxlevel || 100;
|
|
298
|
+
level = level || 0;
|
|
299
|
+
delta = delta || 25;
|
|
300
|
+
result = typeof result !== "undefined" ? result : [];
|
|
301
|
+
reflexVertices = reflexVertices || [];
|
|
302
|
+
steinerPoints = steinerPoints || [];
|
|
303
|
+
var upperInt = [0, 0], lowerInt = [0, 0], p = [0, 0];
|
|
304
|
+
var upperDist = 0, lowerDist = 0, d = 0, closestDist = 0;
|
|
305
|
+
var upperIndex = 0, lowerIndex = 0, closestIndex = 0;
|
|
306
|
+
var lowerPoly = [], upperPoly = [];
|
|
307
|
+
var poly = polygon, v = polygon;
|
|
308
|
+
if (v.length < 3) {
|
|
309
|
+
return result;
|
|
310
|
+
}
|
|
311
|
+
level++;
|
|
312
|
+
if (level > maxlevel) {
|
|
313
|
+
console.warn("quickDecomp: max level (" + maxlevel + ") reached.");
|
|
314
|
+
return result;
|
|
315
|
+
}
|
|
316
|
+
for (var i = 0; i < polygon.length; ++i) {
|
|
317
|
+
if (polygonIsReflex(poly, i)) {
|
|
318
|
+
reflexVertices.push(poly[i]);
|
|
319
|
+
upperDist = lowerDist = Number.MAX_VALUE;
|
|
320
|
+
for (var j = 0; j < polygon.length; ++j) {
|
|
321
|
+
if (isLeft(polygonAt(poly, i - 1), polygonAt(poly, i), polygonAt(poly, j)) && isRightOn(polygonAt(poly, i - 1), polygonAt(poly, i), polygonAt(poly, j - 1))) {
|
|
322
|
+
p = getIntersectionPoint(polygonAt(poly, i - 1), polygonAt(poly, i), polygonAt(poly, j), polygonAt(poly, j - 1));
|
|
323
|
+
if (isRight(polygonAt(poly, i + 1), polygonAt(poly, i), p)) {
|
|
324
|
+
d = sqdist(poly[i], p);
|
|
325
|
+
if (d < lowerDist) {
|
|
326
|
+
lowerDist = d;
|
|
327
|
+
lowerInt = p;
|
|
328
|
+
lowerIndex = j;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
if (isLeft(polygonAt(poly, i + 1), polygonAt(poly, i), polygonAt(poly, j + 1)) && isRightOn(polygonAt(poly, i + 1), polygonAt(poly, i), polygonAt(poly, j))) {
|
|
333
|
+
p = getIntersectionPoint(polygonAt(poly, i + 1), polygonAt(poly, i), polygonAt(poly, j), polygonAt(poly, j + 1));
|
|
334
|
+
if (isLeft(polygonAt(poly, i - 1), polygonAt(poly, i), p)) {
|
|
335
|
+
d = sqdist(poly[i], p);
|
|
336
|
+
if (d < upperDist) {
|
|
337
|
+
upperDist = d;
|
|
338
|
+
upperInt = p;
|
|
339
|
+
upperIndex = j;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
if (lowerIndex === (upperIndex + 1) % polygon.length) {
|
|
345
|
+
p[0] = (lowerInt[0] + upperInt[0]) / 2;
|
|
346
|
+
p[1] = (lowerInt[1] + upperInt[1]) / 2;
|
|
347
|
+
steinerPoints.push(p);
|
|
348
|
+
if (i < upperIndex) {
|
|
349
|
+
polygonAppend(lowerPoly, poly, i, upperIndex + 1);
|
|
350
|
+
lowerPoly.push(p);
|
|
351
|
+
upperPoly.push(p);
|
|
352
|
+
if (lowerIndex !== 0) {
|
|
353
|
+
polygonAppend(upperPoly, poly, lowerIndex, poly.length);
|
|
354
|
+
}
|
|
355
|
+
polygonAppend(upperPoly, poly, 0, i + 1);
|
|
356
|
+
} else {
|
|
357
|
+
if (i !== 0) {
|
|
358
|
+
polygonAppend(lowerPoly, poly, i, poly.length);
|
|
359
|
+
}
|
|
360
|
+
polygonAppend(lowerPoly, poly, 0, upperIndex + 1);
|
|
361
|
+
lowerPoly.push(p);
|
|
362
|
+
upperPoly.push(p);
|
|
363
|
+
polygonAppend(upperPoly, poly, lowerIndex, i + 1);
|
|
364
|
+
}
|
|
365
|
+
} else {
|
|
366
|
+
if (lowerIndex > upperIndex) {
|
|
367
|
+
upperIndex += polygon.length;
|
|
368
|
+
}
|
|
369
|
+
closestDist = Number.MAX_VALUE;
|
|
370
|
+
if (upperIndex < lowerIndex) {
|
|
371
|
+
return result;
|
|
372
|
+
}
|
|
373
|
+
for (var j = lowerIndex; j <= upperIndex; ++j) {
|
|
374
|
+
if (isLeftOn(polygonAt(poly, i - 1), polygonAt(poly, i), polygonAt(poly, j)) && isRightOn(polygonAt(poly, i + 1), polygonAt(poly, i), polygonAt(poly, j))) {
|
|
375
|
+
d = sqdist(polygonAt(poly, i), polygonAt(poly, j));
|
|
376
|
+
if (d < closestDist && polygonCanSee2(poly, i, j)) {
|
|
377
|
+
closestDist = d;
|
|
378
|
+
closestIndex = j % polygon.length;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
if (i < closestIndex) {
|
|
383
|
+
polygonAppend(lowerPoly, poly, i, closestIndex + 1);
|
|
384
|
+
if (closestIndex !== 0) {
|
|
385
|
+
polygonAppend(upperPoly, poly, closestIndex, v.length);
|
|
386
|
+
}
|
|
387
|
+
polygonAppend(upperPoly, poly, 0, i + 1);
|
|
388
|
+
} else {
|
|
389
|
+
if (i !== 0) {
|
|
390
|
+
polygonAppend(lowerPoly, poly, i, v.length);
|
|
391
|
+
}
|
|
392
|
+
polygonAppend(lowerPoly, poly, 0, closestIndex + 1);
|
|
393
|
+
polygonAppend(upperPoly, poly, closestIndex, i + 1);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
if (lowerPoly.length < upperPoly.length) {
|
|
397
|
+
polygonQuickDecomp(lowerPoly, result, reflexVertices, steinerPoints, delta, maxlevel, level);
|
|
398
|
+
polygonQuickDecomp(upperPoly, result, reflexVertices, steinerPoints, delta, maxlevel, level);
|
|
399
|
+
} else {
|
|
400
|
+
polygonQuickDecomp(upperPoly, result, reflexVertices, steinerPoints, delta, maxlevel, level);
|
|
401
|
+
polygonQuickDecomp(lowerPoly, result, reflexVertices, steinerPoints, delta, maxlevel, level);
|
|
402
|
+
}
|
|
403
|
+
return result;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
result.push(polygon);
|
|
407
|
+
return result;
|
|
408
|
+
}
|
|
409
|
+
function polygonRemoveCollinearPoints(polygon, precision) {
|
|
410
|
+
var num = 0;
|
|
411
|
+
for (var i = polygon.length - 1; polygon.length > 3 && i >= 0; --i) {
|
|
412
|
+
if (collinear(polygonAt(polygon, i - 1), polygonAt(polygon, i), polygonAt(polygon, i + 1), precision)) {
|
|
413
|
+
polygon.splice(i % polygon.length, 1);
|
|
414
|
+
num++;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return num;
|
|
418
|
+
}
|
|
419
|
+
function polygonRemoveDuplicatePoints(polygon, precision) {
|
|
420
|
+
for (var i = polygon.length - 1; i >= 1; --i) {
|
|
421
|
+
var pi = polygon[i];
|
|
422
|
+
for (var j = i - 1; j >= 0; --j) {
|
|
423
|
+
if (points_eq(pi, polygon[j], precision)) {
|
|
424
|
+
polygon.splice(i, 1);
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
function scalar_eq(a, b, precision) {
|
|
431
|
+
precision = precision || 0;
|
|
432
|
+
return Math.abs(a - b) <= precision;
|
|
433
|
+
}
|
|
434
|
+
function points_eq(a, b, precision) {
|
|
435
|
+
return scalar_eq(a[0], b[0], precision) && scalar_eq(a[1], b[1], precision);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
// src/index.ts
|
|
441
|
+
var decomp = __toESM(require_src(), 1);
|
|
442
|
+
import * as Matter from "matter-js";
|
|
443
|
+
import {
|
|
444
|
+
Ellipse,
|
|
445
|
+
version as melonjsVersion,
|
|
446
|
+
Polygon,
|
|
447
|
+
Rect,
|
|
448
|
+
state,
|
|
449
|
+
utils,
|
|
450
|
+
Vector2d
|
|
451
|
+
} from "melonjs";
|
|
452
|
+
Matter.Common.setDecomp(decomp);
|
|
453
|
+
var REQUIRED_MELONJS_VERSION = "19.5.0";
|
|
454
|
+
var MatterAdapter = class {
|
|
455
|
+
physicLabel = "matter";
|
|
456
|
+
name = "@melonjs/matter-adapter";
|
|
457
|
+
version = "1.0.0";
|
|
458
|
+
url = "https://www.npmjs.com/package/@melonjs/matter-adapter";
|
|
459
|
+
capabilities = {
|
|
460
|
+
constraints: true,
|
|
461
|
+
continuousCollisionDetection: true,
|
|
462
|
+
sleepingBodies: true,
|
|
463
|
+
raycasts: true,
|
|
464
|
+
velocityLimit: true,
|
|
465
|
+
isGrounded: true
|
|
466
|
+
};
|
|
467
|
+
gravity;
|
|
468
|
+
/**
|
|
469
|
+
* Raw matter-js namespace — escape hatch for matter-specific features
|
|
470
|
+
* the portable `PhysicsAdapter` interface doesn't cover (constraints,
|
|
471
|
+
* compound bodies, events on the Matter engine, queries, etc.).
|
|
472
|
+
*
|
|
473
|
+
* Saves you from adding a transitive `import * as Matter from "matter-js"`
|
|
474
|
+
* just to reach the factories you need. The Matter modules are accessed
|
|
475
|
+
* by the same names matter's own docs use, so examples from the
|
|
476
|
+
* matter-js docs copy-paste without renaming:
|
|
477
|
+
*
|
|
478
|
+
* ```ts
|
|
479
|
+
* const spring = adapter.matter.Constraint.create({
|
|
480
|
+
* bodyA: a.body, bodyB: b.body, stiffness: 0.04, length: 80,
|
|
481
|
+
* });
|
|
482
|
+
* adapter.matter.Composite.add(adapter.engine.world, spring);
|
|
483
|
+
* ```
|
|
484
|
+
*
|
|
485
|
+
* Game code that touches `adapter.matter.*` is matter-only — it will
|
|
486
|
+
* not work under any other physics adapter. Use the
|
|
487
|
+
* `PhysicsAdapter` methods for anything that should stay portable.
|
|
488
|
+
* @see {@link https://brm.io/matter-js/docs/ Official matter-js documentation}
|
|
489
|
+
* for the full module reference (`Matter.Constraint`, `Matter.Composite`,
|
|
490
|
+
* `Matter.Bodies`, `Matter.Events`, `Matter.Query`, `Matter.Vector`, …).
|
|
491
|
+
*/
|
|
492
|
+
matter = Matter;
|
|
493
|
+
/** the underlying Matter engine; exposed for advanced use cases. */
|
|
494
|
+
engine;
|
|
495
|
+
/** back-reference to the owning melonJS world (set in {@link init}). */
|
|
496
|
+
world;
|
|
497
|
+
/** renderable → its matter-js body */
|
|
498
|
+
bodyMap = /* @__PURE__ */ new Map();
|
|
499
|
+
/** matter-js body → its renderable (for collision / sync) */
|
|
500
|
+
renderableMap = /* @__PURE__ */ new Map();
|
|
501
|
+
/** per-body velocity cap (Matter has no native equivalent) */
|
|
502
|
+
velocityLimits = /* @__PURE__ */ new Map();
|
|
503
|
+
/** per-body bodyDef hash kept for shape introspection / removeBody */
|
|
504
|
+
defMap = /* @__PURE__ */ new Map();
|
|
505
|
+
/**
|
|
506
|
+
* Per-body gravity multiplier. matter-js 0.20 doesn't honor a
|
|
507
|
+
* body-level gravityScale, so we emulate it by applying a counter-
|
|
508
|
+
* force each step (see {@link init} `beforeUpdate`). Only stored for
|
|
509
|
+
* bodies with scale ≠ 1; the default-1 case is the hot path and we
|
|
510
|
+
* skip the map lookup entirely for it.
|
|
511
|
+
*/
|
|
512
|
+
bodyGravityScale = /* @__PURE__ */ new Map();
|
|
513
|
+
/**
|
|
514
|
+
* Offset between `renderable.pos` (top-left in melonJS convention)
|
|
515
|
+
* and `Matter.Body.position` (centroid in Matter's convention). Stored
|
|
516
|
+
* at addBody time so syncFromPhysics can place the sprite correctly.
|
|
517
|
+
*/
|
|
518
|
+
posOffsets = /* @__PURE__ */ new Map();
|
|
519
|
+
matterOptions;
|
|
520
|
+
subSteps;
|
|
521
|
+
constructor(options = {}) {
|
|
522
|
+
if (utils.checkVersion(REQUIRED_MELONJS_VERSION, melonjsVersion) > 0) {
|
|
523
|
+
throw new Error(
|
|
524
|
+
`@melonjs/matter-adapter requires melonJS >= ${REQUIRED_MELONJS_VERSION}, but the loaded melonJS is ${melonjsVersion}.`
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
this.matterOptions = options.matterEngineOptions;
|
|
528
|
+
this.subSteps = Math.max(1, Math.floor(options.subSteps ?? 1));
|
|
529
|
+
const g = options.gravity ?? { x: 0, y: 1 };
|
|
530
|
+
this.gravity = new Vector2d(g.x, g.y);
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Stored references to the listeners registered in {@link init}, so
|
|
534
|
+
* {@link destroy} can `Matter.Events.off` them. Without these stored
|
|
535
|
+
* refs, a destroy/re-init cycle would leak the old listeners and
|
|
536
|
+
* dispatch every event twice on the second cycle.
|
|
537
|
+
*/
|
|
538
|
+
_matterListeners = [];
|
|
539
|
+
init(world) {
|
|
540
|
+
this.world = world;
|
|
541
|
+
this.engine = Matter.Engine.create(this.matterOptions);
|
|
542
|
+
this.engine.gravity.x = this.gravity.x;
|
|
543
|
+
this.engine.gravity.y = this.gravity.y;
|
|
544
|
+
const on = (name, fn) => {
|
|
545
|
+
Matter.Events.on(
|
|
546
|
+
this.engine,
|
|
547
|
+
name,
|
|
548
|
+
fn
|
|
549
|
+
);
|
|
550
|
+
this._matterListeners.push({ name, fn });
|
|
551
|
+
};
|
|
552
|
+
on("beforeUpdate", () => {
|
|
553
|
+
if (this.bodyGravityScale.size === 0) return;
|
|
554
|
+
const gx = this.engine.gravity.x;
|
|
555
|
+
const gy = this.engine.gravity.y;
|
|
556
|
+
const gs = this.engine.gravity.scale ?? 1e-3;
|
|
557
|
+
for (const [body, scale] of this.bodyGravityScale) {
|
|
558
|
+
if (body.isStatic || body.isSleeping) continue;
|
|
559
|
+
const k = (scale - 1) * body.mass * gs;
|
|
560
|
+
body.force.x += k * gx;
|
|
561
|
+
body.force.y += k * gy;
|
|
562
|
+
}
|
|
563
|
+
});
|
|
564
|
+
on("afterUpdate", () => {
|
|
565
|
+
this._clampVelocities();
|
|
566
|
+
this.syncFromPhysics();
|
|
567
|
+
});
|
|
568
|
+
on("collisionStart", (e) => {
|
|
569
|
+
this._dispatchCollisions(e.pairs, "start");
|
|
570
|
+
});
|
|
571
|
+
on("collisionActive", (e) => {
|
|
572
|
+
this._dispatchCollisions(e.pairs, "active");
|
|
573
|
+
});
|
|
574
|
+
on("collisionEnd", (e) => {
|
|
575
|
+
this._dispatchCollisions(e.pairs, "end");
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
destroy() {
|
|
579
|
+
for (const { name, fn } of this._matterListeners) {
|
|
580
|
+
Matter.Events.off(
|
|
581
|
+
this.engine,
|
|
582
|
+
name,
|
|
583
|
+
fn
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
this._matterListeners = [];
|
|
587
|
+
Matter.Composite.clear(this.engine.world, false, true);
|
|
588
|
+
Matter.Engine.clear(this.engine);
|
|
589
|
+
this.bodyMap.clear();
|
|
590
|
+
this.renderableMap.clear();
|
|
591
|
+
this.velocityLimits.clear();
|
|
592
|
+
this.defMap.clear();
|
|
593
|
+
this.bodyGravityScale.clear();
|
|
594
|
+
this.posOffsets.clear();
|
|
595
|
+
}
|
|
596
|
+
step(dt) {
|
|
597
|
+
if (state.isPaused()) {
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
this.engine.gravity.x = this.gravity.x;
|
|
601
|
+
this.engine.gravity.y = this.gravity.y;
|
|
602
|
+
if (this.subSteps === 1) {
|
|
603
|
+
Matter.Engine.update(this.engine, dt);
|
|
604
|
+
} else {
|
|
605
|
+
const sub = dt / this.subSteps;
|
|
606
|
+
for (let i = 0; i < this.subSteps; i++) {
|
|
607
|
+
Matter.Engine.update(this.engine, sub);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
syncFromPhysics() {
|
|
612
|
+
for (const [body, renderable] of this.renderableMap) {
|
|
613
|
+
if (!renderable.pos) {
|
|
614
|
+
continue;
|
|
615
|
+
}
|
|
616
|
+
const off = this.posOffsets.get(renderable);
|
|
617
|
+
if (off) {
|
|
618
|
+
renderable.pos.x = body.position.x + off.x;
|
|
619
|
+
renderable.pos.y = body.position.y + off.y;
|
|
620
|
+
} else {
|
|
621
|
+
renderable.pos.x = body.position.x;
|
|
622
|
+
renderable.pos.y = body.position.y;
|
|
623
|
+
}
|
|
624
|
+
const t = renderable.currentTransform;
|
|
625
|
+
const off2 = this.posOffsets.get(renderable);
|
|
626
|
+
const cx = off2 ? -off2.x : 0;
|
|
627
|
+
const cy = off2 ? -off2.y : 0;
|
|
628
|
+
t.identity();
|
|
629
|
+
if (cx !== 0 || cy !== 0) {
|
|
630
|
+
t.translate(cx, cy);
|
|
631
|
+
t.rotate(body.angle);
|
|
632
|
+
t.translate(-cx, -cy);
|
|
633
|
+
} else {
|
|
634
|
+
t.rotate(body.angle);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
addBody(renderable, def) {
|
|
639
|
+
const baseX = renderable.pos.x;
|
|
640
|
+
const baseY = renderable.pos.y;
|
|
641
|
+
const parts = def.shapes.map((s) => this._shapeToMatter(s, baseX, baseY));
|
|
642
|
+
let body;
|
|
643
|
+
if (parts.length === 1) {
|
|
644
|
+
body = parts[0];
|
|
645
|
+
} else {
|
|
646
|
+
body = Matter.Body.create({ parts });
|
|
647
|
+
}
|
|
648
|
+
if (def.type === "static") {
|
|
649
|
+
Matter.Body.setStatic(body, true);
|
|
650
|
+
}
|
|
651
|
+
if (typeof def.density === "number") {
|
|
652
|
+
Matter.Body.setDensity(body, def.density);
|
|
653
|
+
}
|
|
654
|
+
if (def.frictionAir !== void 0) {
|
|
655
|
+
body.frictionAir = typeof def.frictionAir === "number" ? def.frictionAir : (def.frictionAir.x + def.frictionAir.y) / 2;
|
|
656
|
+
}
|
|
657
|
+
if (typeof def.restitution === "number") {
|
|
658
|
+
body.restitution = def.restitution;
|
|
659
|
+
}
|
|
660
|
+
if (typeof def.friction === "number") {
|
|
661
|
+
body.friction = def.friction;
|
|
662
|
+
}
|
|
663
|
+
if (typeof def.gravityScale === "number" && def.gravityScale !== 1) {
|
|
664
|
+
this.bodyGravityScale.set(body, def.gravityScale);
|
|
665
|
+
}
|
|
666
|
+
if (def.maxVelocity) {
|
|
667
|
+
this.velocityLimits.set(renderable, {
|
|
668
|
+
x: def.maxVelocity.x,
|
|
669
|
+
y: def.maxVelocity.y
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
Object.defineProperties(body, {
|
|
673
|
+
collisionType: {
|
|
674
|
+
get() {
|
|
675
|
+
return this.collisionFilter.category;
|
|
676
|
+
},
|
|
677
|
+
set(v) {
|
|
678
|
+
this.collisionFilter.category = v;
|
|
679
|
+
},
|
|
680
|
+
configurable: true,
|
|
681
|
+
enumerable: true
|
|
682
|
+
},
|
|
683
|
+
collisionMask: {
|
|
684
|
+
get() {
|
|
685
|
+
return this.collisionFilter.mask;
|
|
686
|
+
},
|
|
687
|
+
set(v) {
|
|
688
|
+
this.collisionFilter.mask = v;
|
|
689
|
+
},
|
|
690
|
+
configurable: true,
|
|
691
|
+
enumerable: true
|
|
692
|
+
}
|
|
693
|
+
});
|
|
694
|
+
if (typeof def.collisionType === "number") {
|
|
695
|
+
body.collisionFilter.category = def.collisionType;
|
|
696
|
+
}
|
|
697
|
+
if (typeof def.collisionMask === "number") {
|
|
698
|
+
body.collisionFilter.mask = def.collisionMask;
|
|
699
|
+
}
|
|
700
|
+
if (def.isSensor) {
|
|
701
|
+
body.isSensor = true;
|
|
702
|
+
}
|
|
703
|
+
if (def.fixedRotation !== false) {
|
|
704
|
+
Matter.Body.setInertia(body, Number.POSITIVE_INFINITY);
|
|
705
|
+
}
|
|
706
|
+
Matter.Composite.add(this.engine.world, body);
|
|
707
|
+
this.bodyMap.set(renderable, body);
|
|
708
|
+
this.renderableMap.set(body, renderable);
|
|
709
|
+
this.defMap.set(renderable, def);
|
|
710
|
+
this.posOffsets.set(renderable, {
|
|
711
|
+
x: baseX - body.position.x,
|
|
712
|
+
y: baseY - body.position.y
|
|
713
|
+
});
|
|
714
|
+
const helpers = {
|
|
715
|
+
setVelocity(x, y) {
|
|
716
|
+
Matter.Body.setVelocity(body, { x, y });
|
|
717
|
+
},
|
|
718
|
+
getVelocity(out) {
|
|
719
|
+
return (out ?? new Vector2d()).set(body.velocity.x, body.velocity.y);
|
|
720
|
+
},
|
|
721
|
+
applyForce(x, y, pointX, pointY) {
|
|
722
|
+
const point = typeof pointX === "number" && typeof pointY === "number" ? { x: pointX, y: pointY } : body.position;
|
|
723
|
+
Matter.Body.applyForce(body, point, { x, y });
|
|
724
|
+
},
|
|
725
|
+
applyImpulse(x, y) {
|
|
726
|
+
const invMass = body.mass > 0 ? 1 / body.mass : 0;
|
|
727
|
+
Matter.Body.setVelocity(body, {
|
|
728
|
+
x: body.velocity.x + x * invMass,
|
|
729
|
+
y: body.velocity.y + y * invMass
|
|
730
|
+
});
|
|
731
|
+
},
|
|
732
|
+
setSensor(isSensor = true) {
|
|
733
|
+
body.isSensor = isSensor;
|
|
734
|
+
},
|
|
735
|
+
setStatic(isStatic = true) {
|
|
736
|
+
Matter.Body.setStatic(body, isStatic);
|
|
737
|
+
},
|
|
738
|
+
setCollisionMask(mask) {
|
|
739
|
+
body.collisionFilter.mask = mask;
|
|
740
|
+
},
|
|
741
|
+
setCollisionType(type) {
|
|
742
|
+
body.collisionFilter.category = type;
|
|
743
|
+
},
|
|
744
|
+
setMass: (m) => {
|
|
745
|
+
Matter.Body.setMass(body, m);
|
|
746
|
+
},
|
|
747
|
+
setBounce(r) {
|
|
748
|
+
body.restitution = r;
|
|
749
|
+
},
|
|
750
|
+
setGravityScale: (scale) => {
|
|
751
|
+
if (scale === 1) {
|
|
752
|
+
this.bodyGravityScale.delete(body);
|
|
753
|
+
} else {
|
|
754
|
+
this.bodyGravityScale.set(body, scale);
|
|
755
|
+
}
|
|
756
|
+
},
|
|
757
|
+
setAngularVelocity(omega) {
|
|
758
|
+
Matter.Body.setAngularVelocity(body, omega);
|
|
759
|
+
},
|
|
760
|
+
getAngularVelocity() {
|
|
761
|
+
return body.angularVelocity;
|
|
762
|
+
},
|
|
763
|
+
setAngle(rad) {
|
|
764
|
+
Matter.Body.setAngle(body, rad);
|
|
765
|
+
},
|
|
766
|
+
getAngle() {
|
|
767
|
+
return body.angle;
|
|
768
|
+
},
|
|
769
|
+
applyTorque(t) {
|
|
770
|
+
body.torque += t;
|
|
771
|
+
}
|
|
772
|
+
};
|
|
773
|
+
Object.assign(body, helpers);
|
|
774
|
+
const adapterBody = body;
|
|
775
|
+
renderable.body = adapterBody;
|
|
776
|
+
return adapterBody;
|
|
777
|
+
}
|
|
778
|
+
removeBody(renderable) {
|
|
779
|
+
const body = this.bodyMap.get(renderable);
|
|
780
|
+
if (body) {
|
|
781
|
+
Matter.Composite.remove(this.engine.world, body);
|
|
782
|
+
this.bodyMap.delete(renderable);
|
|
783
|
+
this.renderableMap.delete(body);
|
|
784
|
+
this.velocityLimits.delete(renderable);
|
|
785
|
+
this.defMap.delete(renderable);
|
|
786
|
+
this.posOffsets.delete(renderable);
|
|
787
|
+
this.bodyGravityScale.delete(body);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
updateShape(renderable, shapes) {
|
|
791
|
+
const oldDef = this.defMap.get(renderable);
|
|
792
|
+
if (!oldDef) {
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
const oldBody = this.bodyMap.get(renderable);
|
|
796
|
+
const savedVel = oldBody ? { x: oldBody.velocity.x, y: oldBody.velocity.y } : void 0;
|
|
797
|
+
const savedAngVel = oldBody?.angularVelocity ?? 0;
|
|
798
|
+
this.removeBody(renderable);
|
|
799
|
+
const newBody = this.addBody(renderable, { ...oldDef, shapes });
|
|
800
|
+
if (savedVel) {
|
|
801
|
+
Matter.Body.setVelocity(newBody, savedVel);
|
|
802
|
+
Matter.Body.setAngularVelocity(newBody, savedAngVel);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
getVelocity(renderable, out) {
|
|
806
|
+
const body = this.bodyMap.get(renderable);
|
|
807
|
+
const target = out ?? new Vector2d();
|
|
808
|
+
if (body) {
|
|
809
|
+
return target.set(body.velocity.x, body.velocity.y);
|
|
810
|
+
}
|
|
811
|
+
return target.set(0, 0);
|
|
812
|
+
}
|
|
813
|
+
setVelocity(renderable, v) {
|
|
814
|
+
const body = this.bodyMap.get(renderable);
|
|
815
|
+
if (body) {
|
|
816
|
+
Matter.Body.setVelocity(body, { x: v.x, y: v.y });
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
applyForce(renderable, force, point) {
|
|
820
|
+
const body = this.bodyMap.get(renderable);
|
|
821
|
+
if (!body) return;
|
|
822
|
+
Matter.Body.applyForce(body, point ?? body.position, {
|
|
823
|
+
x: force.x,
|
|
824
|
+
y: force.y
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
applyImpulse(renderable, impulse) {
|
|
828
|
+
const body = this.bodyMap.get(renderable);
|
|
829
|
+
if (!body) return;
|
|
830
|
+
const invMass = body.mass > 0 ? 1 / body.mass : 0;
|
|
831
|
+
Matter.Body.setVelocity(body, {
|
|
832
|
+
x: body.velocity.x + impulse.x * invMass,
|
|
833
|
+
y: body.velocity.y + impulse.y * invMass
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
setPosition(renderable, p) {
|
|
837
|
+
const body = this.bodyMap.get(renderable);
|
|
838
|
+
if (body) {
|
|
839
|
+
const off = this.posOffsets.get(renderable);
|
|
840
|
+
Matter.Body.setPosition(body, {
|
|
841
|
+
x: p.x - (off?.x ?? 0),
|
|
842
|
+
y: p.y - (off?.y ?? 0)
|
|
843
|
+
});
|
|
844
|
+
this.invalidateContactsFor(body);
|
|
845
|
+
}
|
|
846
|
+
renderable.pos.x = p.x;
|
|
847
|
+
renderable.pos.y = p.y;
|
|
848
|
+
}
|
|
849
|
+
invalidateContactsFor(body) {
|
|
850
|
+
const impulse = body.positionImpulse;
|
|
851
|
+
impulse.x = 0;
|
|
852
|
+
impulse.y = 0;
|
|
853
|
+
}
|
|
854
|
+
setAngle(renderable, angle) {
|
|
855
|
+
const body = this.bodyMap.get(renderable);
|
|
856
|
+
if (body) {
|
|
857
|
+
Matter.Body.setAngle(body, angle);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
getAngle(renderable) {
|
|
861
|
+
const body = this.bodyMap.get(renderable);
|
|
862
|
+
return body ? body.angle : 0;
|
|
863
|
+
}
|
|
864
|
+
setAngularVelocity(renderable, omega) {
|
|
865
|
+
const body = this.bodyMap.get(renderable);
|
|
866
|
+
if (body) {
|
|
867
|
+
Matter.Body.setAngularVelocity(body, omega);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
getAngularVelocity(renderable) {
|
|
871
|
+
const body = this.bodyMap.get(renderable);
|
|
872
|
+
return body ? body.angularVelocity : 0;
|
|
873
|
+
}
|
|
874
|
+
applyTorque(renderable, torque) {
|
|
875
|
+
const body = this.bodyMap.get(renderable);
|
|
876
|
+
if (body) {
|
|
877
|
+
body.torque += torque;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
setStatic(renderable, isStatic) {
|
|
881
|
+
const body = this.bodyMap.get(renderable);
|
|
882
|
+
if (body) {
|
|
883
|
+
Matter.Body.setStatic(body, isStatic);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
setGravityScale(renderable, scale) {
|
|
887
|
+
const body = this.bodyMap.get(renderable);
|
|
888
|
+
if (!body) return;
|
|
889
|
+
if (scale === 1) {
|
|
890
|
+
this.bodyGravityScale.delete(body);
|
|
891
|
+
} else {
|
|
892
|
+
this.bodyGravityScale.set(body, scale);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
setSensor(renderable, isSensor) {
|
|
896
|
+
const body = this.bodyMap.get(renderable);
|
|
897
|
+
if (body) {
|
|
898
|
+
body.isSensor = isSensor;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
setFrictionAir(renderable, friction) {
|
|
902
|
+
const body = this.bodyMap.get(renderable);
|
|
903
|
+
if (body) {
|
|
904
|
+
body.frictionAir = typeof friction === "number" ? friction : (friction.x + friction.y) / 2;
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
setMaxVelocity(renderable, limit) {
|
|
908
|
+
this.velocityLimits.set(renderable, { x: limit.x, y: limit.y });
|
|
909
|
+
}
|
|
910
|
+
getMaxVelocity(renderable) {
|
|
911
|
+
return this.velocityLimits.get(renderable) ?? { x: 0, y: 0 };
|
|
912
|
+
}
|
|
913
|
+
setCollisionType(renderable, type) {
|
|
914
|
+
const body = this.bodyMap.get(renderable);
|
|
915
|
+
if (body) {
|
|
916
|
+
body.collisionFilter.category = type;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
setCollisionMask(renderable, mask) {
|
|
920
|
+
const body = this.bodyMap.get(renderable);
|
|
921
|
+
if (body) {
|
|
922
|
+
body.collisionFilter.mask = mask;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
/**
|
|
926
|
+
* Adapter-side debug surface: the body's AABB in renderable-local
|
|
927
|
+
* coordinates. Matter tracks `body.bounds` in WORLD space; we
|
|
928
|
+
* subtract `renderable.pos` so the result matches melonJS's local-
|
|
929
|
+
* space convention (the debug plugin translates to the renderable
|
|
930
|
+
* origin before drawing, and would otherwise see the bounds drawn
|
|
931
|
+
* offset by the renderable's world position).
|
|
932
|
+
* @param renderable - the renderable whose body bounds to read
|
|
933
|
+
* @param out - destination `Bounds` (filled in place, also returned)
|
|
934
|
+
*/
|
|
935
|
+
getBodyAABB(renderable, out) {
|
|
936
|
+
const body = this.bodyMap.get(renderable);
|
|
937
|
+
if (!body) return void 0;
|
|
938
|
+
const b = body.bounds;
|
|
939
|
+
const rp = renderable.pos;
|
|
940
|
+
out.setMinMax(
|
|
941
|
+
b.min.x - rp.x,
|
|
942
|
+
b.min.y - rp.y,
|
|
943
|
+
b.max.x - rp.x,
|
|
944
|
+
b.max.y - rp.y
|
|
945
|
+
);
|
|
946
|
+
return out;
|
|
947
|
+
}
|
|
948
|
+
/**
|
|
949
|
+
* Adapter-side debug surface: the body's collision shapes in
|
|
950
|
+
* renderable-local coordinates. We return the original `def.shapes`
|
|
951
|
+
* array — those are the input shape definitions in local space,
|
|
952
|
+
* unchanged by matter's body transformation (rotation is baked into
|
|
953
|
+
* matter's vertices, not into our local-space defs). Read-only.
|
|
954
|
+
* @param renderable - the renderable whose body shapes to read
|
|
955
|
+
*/
|
|
956
|
+
getBodyShapes(renderable) {
|
|
957
|
+
return this.defMap.get(renderable)?.shapes ?? [];
|
|
958
|
+
}
|
|
959
|
+
isGrounded(renderable) {
|
|
960
|
+
const body = this.bodyMap.get(renderable);
|
|
961
|
+
if (!body) return false;
|
|
962
|
+
for (const pair of this.engine.pairs.list) {
|
|
963
|
+
if (!pair.isActive) continue;
|
|
964
|
+
const isA = pair.bodyA === body;
|
|
965
|
+
const isB = pair.bodyB === body;
|
|
966
|
+
if (!isA && !isB) continue;
|
|
967
|
+
const other = isA ? pair.bodyB : pair.bodyA;
|
|
968
|
+
if (other.position.y > body.position.y) {
|
|
969
|
+
return true;
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
return false;
|
|
973
|
+
}
|
|
974
|
+
raycast(from, to) {
|
|
975
|
+
const dx = to.x - from.x;
|
|
976
|
+
const dy = to.y - from.y;
|
|
977
|
+
if (Math.abs(dx) < 1e-9 && Math.abs(dy) < 1e-9) return null;
|
|
978
|
+
const bodies = Matter.Composite.allBodies(this.engine.world);
|
|
979
|
+
const collisions = Matter.Query.ray(bodies, from, to);
|
|
980
|
+
if (collisions.length === 0) return null;
|
|
981
|
+
let bestT = Infinity;
|
|
982
|
+
let bestEdgeX = 0;
|
|
983
|
+
let bestEdgeY = 0;
|
|
984
|
+
let bestBody = null;
|
|
985
|
+
for (const collision of collisions) {
|
|
986
|
+
const candidate = collision.bodyA;
|
|
987
|
+
const parts = candidate.parts;
|
|
988
|
+
const startIdx = parts.length > 1 ? 1 : 0;
|
|
989
|
+
for (let p = startIdx; p < parts.length; p++) {
|
|
990
|
+
const vertices = parts[p].vertices;
|
|
991
|
+
const vlen = vertices.length;
|
|
992
|
+
for (let i = 0; i < vlen; i++) {
|
|
993
|
+
const a = vertices[i];
|
|
994
|
+
const b = vertices[i + 1 === vlen ? 0 : i + 1];
|
|
995
|
+
const ex = b.x - a.x;
|
|
996
|
+
const ey = b.y - a.y;
|
|
997
|
+
const denom = dx * ey - dy * ex;
|
|
998
|
+
if (denom > -1e-9 && denom < 1e-9) continue;
|
|
999
|
+
const fx = a.x - from.x;
|
|
1000
|
+
const fy = a.y - from.y;
|
|
1001
|
+
const t = (fx * ey - fy * ex) / denom;
|
|
1002
|
+
if (t < 0 || t > 1 || t >= bestT) continue;
|
|
1003
|
+
const s = (fx * dy - fy * dx) / denom;
|
|
1004
|
+
if (s < 0 || s > 1) continue;
|
|
1005
|
+
bestT = t;
|
|
1006
|
+
bestEdgeX = ex;
|
|
1007
|
+
bestEdgeY = ey;
|
|
1008
|
+
bestBody = candidate;
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
if (bestBody === null) return null;
|
|
1013
|
+
const renderable = this.renderableMap.get(bestBody);
|
|
1014
|
+
if (!renderable) return null;
|
|
1015
|
+
let nx = bestEdgeY;
|
|
1016
|
+
let ny = -bestEdgeX;
|
|
1017
|
+
const nLen = Math.sqrt(nx * nx + ny * ny);
|
|
1018
|
+
if (nLen > 0) {
|
|
1019
|
+
nx /= nLen;
|
|
1020
|
+
ny /= nLen;
|
|
1021
|
+
}
|
|
1022
|
+
if (nx * dx + ny * dy > 0) {
|
|
1023
|
+
nx = -nx;
|
|
1024
|
+
ny = -ny;
|
|
1025
|
+
}
|
|
1026
|
+
return {
|
|
1027
|
+
renderable,
|
|
1028
|
+
point: new Vector2d(from.x + dx * bestT, from.y + dy * bestT),
|
|
1029
|
+
normal: new Vector2d(nx, ny),
|
|
1030
|
+
fraction: bestT
|
|
1031
|
+
};
|
|
1032
|
+
}
|
|
1033
|
+
queryAABB(rect) {
|
|
1034
|
+
const bodies = Matter.Composite.allBodies(this.engine.world);
|
|
1035
|
+
const matched = Matter.Query.region(bodies, {
|
|
1036
|
+
min: { x: rect.pos.x, y: rect.pos.y },
|
|
1037
|
+
max: { x: rect.pos.x + rect.width, y: rect.pos.y + rect.height }
|
|
1038
|
+
});
|
|
1039
|
+
const result = [];
|
|
1040
|
+
for (const b of matched) {
|
|
1041
|
+
const r = this.renderableMap.get(b);
|
|
1042
|
+
if (r) result.push(r);
|
|
1043
|
+
}
|
|
1044
|
+
return result;
|
|
1045
|
+
}
|
|
1046
|
+
// ---------------------------------------------------------------------
|
|
1047
|
+
// Internal helpers
|
|
1048
|
+
// ---------------------------------------------------------------------
|
|
1049
|
+
_clampVelocities() {
|
|
1050
|
+
for (const [renderable, limit] of this.velocityLimits) {
|
|
1051
|
+
const body = this.bodyMap.get(renderable);
|
|
1052
|
+
if (!body) continue;
|
|
1053
|
+
const vx = Math.max(-limit.x, Math.min(limit.x, body.velocity.x));
|
|
1054
|
+
const vy = Math.max(-limit.y, Math.min(limit.y, body.velocity.y));
|
|
1055
|
+
if (vx !== body.velocity.x || vy !== body.velocity.y) {
|
|
1056
|
+
Matter.Body.setVelocity(body, { x: vx, y: vy });
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
_dispatchCollisions(pairs, phase) {
|
|
1061
|
+
const methodForSide = (receiver) => {
|
|
1062
|
+
if (phase === "start") return "onCollisionStart";
|
|
1063
|
+
if (phase === "end") return "onCollisionEnd";
|
|
1064
|
+
return typeof receiver.onCollisionActive === "function" ? "onCollisionActive" : "onCollision";
|
|
1065
|
+
};
|
|
1066
|
+
for (const pair of pairs) {
|
|
1067
|
+
const rA = this.renderableMap.get(pair.bodyA);
|
|
1068
|
+
const rB = this.renderableMap.get(pair.bodyB);
|
|
1069
|
+
if (!rA || !rB) continue;
|
|
1070
|
+
const ancA = rA.ancestor;
|
|
1071
|
+
const ancB = rB.ancestor;
|
|
1072
|
+
const aDetached = ancA === null;
|
|
1073
|
+
const bDetached = ancB === null;
|
|
1074
|
+
if (phase === "end") {
|
|
1075
|
+
if (aDetached && bDetached) continue;
|
|
1076
|
+
} else if (aDetached || bDetached) {
|
|
1077
|
+
continue;
|
|
1078
|
+
}
|
|
1079
|
+
const collision = pair.collision;
|
|
1080
|
+
const depth = collision.depth;
|
|
1081
|
+
const normal = collision.normal;
|
|
1082
|
+
const responseAB = {
|
|
1083
|
+
a: rA,
|
|
1084
|
+
b: rB,
|
|
1085
|
+
normal: { x: normal.x, y: normal.y },
|
|
1086
|
+
depth,
|
|
1087
|
+
pair
|
|
1088
|
+
};
|
|
1089
|
+
const responseBA = {
|
|
1090
|
+
a: rB,
|
|
1091
|
+
b: rA,
|
|
1092
|
+
normal: { x: -normal.x, y: -normal.y },
|
|
1093
|
+
depth,
|
|
1094
|
+
pair
|
|
1095
|
+
};
|
|
1096
|
+
if (!aDetached) {
|
|
1097
|
+
const mA = methodForSide(rA);
|
|
1098
|
+
const fnA = rA[mA];
|
|
1099
|
+
if (typeof fnA === "function") {
|
|
1100
|
+
fnA.call(
|
|
1101
|
+
rA,
|
|
1102
|
+
responseAB,
|
|
1103
|
+
rB
|
|
1104
|
+
);
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
if (!bDetached) {
|
|
1108
|
+
const mB = methodForSide(rB);
|
|
1109
|
+
const fnB = rB[mB];
|
|
1110
|
+
if (typeof fnB === "function") {
|
|
1111
|
+
fnB.call(
|
|
1112
|
+
rB,
|
|
1113
|
+
responseBA,
|
|
1114
|
+
rA
|
|
1115
|
+
);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
_shapeToMatter(shape, baseX, baseY) {
|
|
1121
|
+
if (shape instanceof Rect) {
|
|
1122
|
+
const w = shape.width;
|
|
1123
|
+
const h = shape.height;
|
|
1124
|
+
return Matter.Bodies.rectangle(
|
|
1125
|
+
baseX + shape.pos.x + w / 2,
|
|
1126
|
+
baseY + shape.pos.y + h / 2,
|
|
1127
|
+
w,
|
|
1128
|
+
h
|
|
1129
|
+
);
|
|
1130
|
+
}
|
|
1131
|
+
if (shape instanceof Ellipse) {
|
|
1132
|
+
const radius = (shape.radiusV.x + shape.radiusV.y) / 2;
|
|
1133
|
+
return Matter.Bodies.circle(
|
|
1134
|
+
baseX + shape.pos.x,
|
|
1135
|
+
baseY + shape.pos.y,
|
|
1136
|
+
radius
|
|
1137
|
+
);
|
|
1138
|
+
}
|
|
1139
|
+
if (shape instanceof Polygon) {
|
|
1140
|
+
const points = shape.points.map((p) => ({
|
|
1141
|
+
x: baseX + shape.pos.x + p.x,
|
|
1142
|
+
y: baseY + shape.pos.y + p.y
|
|
1143
|
+
}));
|
|
1144
|
+
const cx = points.reduce((s, p) => s + p.x, 0) / Math.max(1, points.length);
|
|
1145
|
+
const cy = points.reduce((s, p) => s + p.y, 0) / Math.max(1, points.length);
|
|
1146
|
+
const body = Matter.Bodies.fromVertices(cx, cy, [points]);
|
|
1147
|
+
if (body) {
|
|
1148
|
+
return body;
|
|
1149
|
+
}
|
|
1150
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
1151
|
+
let minY = Number.POSITIVE_INFINITY;
|
|
1152
|
+
let maxX = Number.NEGATIVE_INFINITY;
|
|
1153
|
+
let maxY = Number.NEGATIVE_INFINITY;
|
|
1154
|
+
for (const p of points) {
|
|
1155
|
+
if (p.x < minX) minX = p.x;
|
|
1156
|
+
if (p.y < minY) minY = p.y;
|
|
1157
|
+
if (p.x > maxX) maxX = p.x;
|
|
1158
|
+
if (p.y > maxY) maxY = p.y;
|
|
1159
|
+
}
|
|
1160
|
+
const w = Math.max(1, maxX - minX);
|
|
1161
|
+
const h = Math.max(1, maxY - minY);
|
|
1162
|
+
return Matter.Bodies.rectangle(minX + w / 2, minY + h / 2, w, h);
|
|
1163
|
+
}
|
|
1164
|
+
throw new Error(
|
|
1165
|
+
`MatterAdapter: unsupported shape type ${shape.constructor.name}`
|
|
1166
|
+
);
|
|
1167
|
+
}
|
|
1168
|
+
};
|
|
1169
|
+
export {
|
|
1170
|
+
MatterAdapter,
|
|
1171
|
+
REQUIRED_MELONJS_VERSION
|
|
1172
|
+
};
|
|
1173
|
+
//# sourceMappingURL=index.js.map
|