@shopify/klint 0.3.0 → 0.4.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/README.md +86 -62
- package/dist/{Klint-CsVzll4n.d.cts → Klint-DqYL-aGU.d.cts} +204 -332
- package/dist/{Klint-CsVzll4n.d.ts → Klint-DqYL-aGU.d.ts} +204 -332
- package/dist/index.cjs +872 -1184
- package/dist/index.d.cts +3 -29
- package/dist/index.d.ts +3 -29
- package/dist/index.js +868 -1184
- package/dist/plugins/index.cjs +606 -466
- package/dist/plugins/index.d.cts +440 -154
- package/dist/plugins/index.d.ts +440 -154
- package/dist/plugins/index.js +593 -466
- package/package.json +1 -1
- package/dist/chunk-3RG5ZIWI.js +0 -10
package/dist/plugins/index.cjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/plugins/index.tsx
|
|
@@ -23,8 +33,9 @@ __export(plugins_exports, {
|
|
|
23
33
|
CatmullRom: () => CatmullRom,
|
|
24
34
|
Delaunay: () => Delaunay,
|
|
25
35
|
FontParser: () => FontParser,
|
|
26
|
-
|
|
27
|
-
|
|
36
|
+
MatterPhysics: () => MatterPhysics,
|
|
37
|
+
Projector: () => Projector,
|
|
38
|
+
Sprites: () => Sprites
|
|
28
39
|
});
|
|
29
40
|
module.exports = __toCommonJS(plugins_exports);
|
|
30
41
|
|
|
@@ -49,14 +60,6 @@ var BinaryReader = class {
|
|
|
49
60
|
const num = this.readShort(data, o);
|
|
50
61
|
return num / 16384;
|
|
51
62
|
}
|
|
52
|
-
readInt(buff, p) {
|
|
53
|
-
const a = this.t.uint8;
|
|
54
|
-
a[0] = buff[p + 3];
|
|
55
|
-
a[1] = buff[p + 2];
|
|
56
|
-
a[2] = buff[p + 1];
|
|
57
|
-
a[3] = buff[p];
|
|
58
|
-
return this.t.int32[0];
|
|
59
|
-
}
|
|
60
63
|
readInt8(buff, p) {
|
|
61
64
|
const a = this.t.uint8;
|
|
62
65
|
a[0] = buff[p];
|
|
@@ -85,19 +88,11 @@ var BinaryReader = class {
|
|
|
85
88
|
a[0] = buff[p + 3];
|
|
86
89
|
return this.t.uint32[0];
|
|
87
90
|
}
|
|
88
|
-
readUint64(buff, p) {
|
|
89
|
-
return this.readUint(buff, p) * (4294967295 + 1) + this.readUint(buff, p + 4);
|
|
90
|
-
}
|
|
91
91
|
readASCII(buff, p, l) {
|
|
92
92
|
let s = "";
|
|
93
93
|
for (let i = 0; i < l; i++) s += String.fromCharCode(buff[p + i]);
|
|
94
94
|
return s;
|
|
95
95
|
}
|
|
96
|
-
readBytes(buff, p, l) {
|
|
97
|
-
const arr = [];
|
|
98
|
-
for (let i = 0; i < l; i++) arr.push(buff[p + i]);
|
|
99
|
-
return arr;
|
|
100
|
-
}
|
|
101
96
|
};
|
|
102
97
|
var bin = new BinaryReader();
|
|
103
98
|
function findTable(data, tab, foff = 0) {
|
|
@@ -105,7 +100,6 @@ function findTable(data, tab, foff = 0) {
|
|
|
105
100
|
let offset = foff + 12;
|
|
106
101
|
for (let i = 0; i < numTables; i++) {
|
|
107
102
|
const tag = bin.readASCII(data, offset, 4);
|
|
108
|
-
const checkSum = bin.readUint(data, offset + 4);
|
|
109
103
|
const toffset = bin.readUint(data, offset + 8);
|
|
110
104
|
const length = bin.readUint(data, offset + 12);
|
|
111
105
|
if (tag === tab) return [toffset, length];
|
|
@@ -116,105 +110,48 @@ function findTable(data, tab, foff = 0) {
|
|
|
116
110
|
var Tables = {
|
|
117
111
|
head: {
|
|
118
112
|
parseTab(data, offset, length) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
obj.flags = bin.readUshort(data, offset);
|
|
129
|
-
offset += 2;
|
|
130
|
-
obj.unitsPerEm = bin.readUshort(data, offset);
|
|
131
|
-
offset += 2;
|
|
132
|
-
obj.created = bin.readUint64(data, offset);
|
|
133
|
-
offset += 8;
|
|
134
|
-
obj.modified = bin.readUint64(data, offset);
|
|
135
|
-
offset += 8;
|
|
136
|
-
obj.xMin = bin.readShort(data, offset);
|
|
137
|
-
offset += 2;
|
|
138
|
-
obj.yMin = bin.readShort(data, offset);
|
|
139
|
-
offset += 2;
|
|
140
|
-
obj.xMax = bin.readShort(data, offset);
|
|
141
|
-
offset += 2;
|
|
142
|
-
obj.yMax = bin.readShort(data, offset);
|
|
143
|
-
offset += 2;
|
|
144
|
-
obj.macStyle = bin.readUshort(data, offset);
|
|
145
|
-
offset += 2;
|
|
146
|
-
obj.lowestRecPPEM = bin.readUshort(data, offset);
|
|
147
|
-
offset += 2;
|
|
148
|
-
obj.fontDirectionHint = bin.readShort(data, offset);
|
|
149
|
-
offset += 2;
|
|
150
|
-
obj.indexToLocFormat = bin.readShort(data, offset);
|
|
151
|
-
offset += 2;
|
|
152
|
-
obj.glyphDataFormat = bin.readShort(data, offset);
|
|
153
|
-
offset += 2;
|
|
154
|
-
return obj;
|
|
113
|
+
return {
|
|
114
|
+
flags: bin.readUshort(data, offset + 16),
|
|
115
|
+
unitsPerEm: bin.readUshort(data, offset + 18),
|
|
116
|
+
xMin: bin.readShort(data, offset + 36),
|
|
117
|
+
yMin: bin.readShort(data, offset + 38),
|
|
118
|
+
xMax: bin.readShort(data, offset + 40),
|
|
119
|
+
yMax: bin.readShort(data, offset + 42),
|
|
120
|
+
indexToLocFormat: bin.readShort(data, offset + 50)
|
|
121
|
+
};
|
|
155
122
|
}
|
|
156
123
|
},
|
|
157
124
|
maxp: {
|
|
158
125
|
parseTab(data, offset, length) {
|
|
159
|
-
|
|
160
|
-
const ver = bin.readUint(data, offset);
|
|
161
|
-
offset += 4;
|
|
162
|
-
obj.numGlyphs = bin.readUshort(data, offset);
|
|
163
|
-
offset += 2;
|
|
164
|
-
return obj;
|
|
126
|
+
return { numGlyphs: bin.readUshort(data, offset + 4) };
|
|
165
127
|
}
|
|
166
128
|
},
|
|
167
129
|
hhea: {
|
|
168
130
|
parseTab(data, offset, length) {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
"lineGap",
|
|
176
|
-
"advanceWidthMax",
|
|
177
|
-
"minLeftSideBearing",
|
|
178
|
-
"minRightSideBearing",
|
|
179
|
-
"xMaxExtent",
|
|
180
|
-
"caretSlopeRise",
|
|
181
|
-
"caretSlopeRun",
|
|
182
|
-
"caretOffset",
|
|
183
|
-
"res0",
|
|
184
|
-
"res1",
|
|
185
|
-
"res2",
|
|
186
|
-
"res3",
|
|
187
|
-
"metricDataFormat",
|
|
188
|
-
"numberOfHMetrics"
|
|
189
|
-
];
|
|
190
|
-
for (let i = 0; i < keys.length; i++) {
|
|
191
|
-
const key = keys[i];
|
|
192
|
-
const func = key === "advanceWidthMax" || key === "numberOfHMetrics" ? bin.readUshort : bin.readShort;
|
|
193
|
-
obj[key] = func.call(bin, data, offset + i * 2);
|
|
194
|
-
}
|
|
195
|
-
return obj;
|
|
131
|
+
return {
|
|
132
|
+
ascender: bin.readShort(data, offset + 4),
|
|
133
|
+
descender: bin.readShort(data, offset + 6),
|
|
134
|
+
lineGap: bin.readShort(data, offset + 8),
|
|
135
|
+
numberOfHMetrics: bin.readUshort(data, offset + 34)
|
|
136
|
+
};
|
|
196
137
|
}
|
|
197
138
|
},
|
|
198
139
|
hmtx: {
|
|
199
140
|
parseTab(data, offset, length, font) {
|
|
200
141
|
const aWidth = [];
|
|
201
|
-
const lsBearing = [];
|
|
202
142
|
const nG = font.maxp.numGlyphs;
|
|
203
143
|
const nH = font.hhea.numberOfHMetrics;
|
|
204
|
-
let aw = 0,
|
|
144
|
+
let aw = 0, i = 0;
|
|
205
145
|
while (i < nH) {
|
|
206
146
|
aw = bin.readUshort(data, offset + (i << 2));
|
|
207
|
-
lsb = bin.readShort(data, offset + (i << 2) + 2);
|
|
208
147
|
aWidth.push(aw);
|
|
209
|
-
lsBearing.push(lsb);
|
|
210
148
|
i++;
|
|
211
149
|
}
|
|
212
150
|
while (i < nG) {
|
|
213
151
|
aWidth.push(aw);
|
|
214
|
-
lsBearing.push(lsb);
|
|
215
152
|
i++;
|
|
216
153
|
}
|
|
217
|
-
return { aWidth
|
|
154
|
+
return { aWidth };
|
|
218
155
|
}
|
|
219
156
|
},
|
|
220
157
|
loca: {
|
|
@@ -274,9 +211,6 @@ var Tables = {
|
|
|
274
211
|
readFormat0(data, offset, map) {
|
|
275
212
|
let pleft = -1;
|
|
276
213
|
const nPairs = bin.readUshort(data, offset);
|
|
277
|
-
const searchRange = bin.readUshort(data, offset + 2);
|
|
278
|
-
const entrySelector = bin.readUshort(data, offset + 4);
|
|
279
|
-
const rangeShift = bin.readUshort(data, offset + 6);
|
|
280
214
|
offset += 8;
|
|
281
215
|
for (let j = 0; j < nPairs; j++) {
|
|
282
216
|
const left = bin.readUshort(data, offset);
|
|
@@ -333,25 +267,16 @@ var cmap = {
|
|
|
333
267
|
return obj;
|
|
334
268
|
},
|
|
335
269
|
parse0(data, offset, obj) {
|
|
336
|
-
|
|
337
|
-
const format = bin.readUshort(data, offset);
|
|
338
|
-
offset += 2;
|
|
339
|
-
const length = bin.readUshort(data, offset);
|
|
340
|
-
offset += 2;
|
|
341
|
-
const language = bin.readUshort(data, offset);
|
|
342
|
-
offset += 2;
|
|
270
|
+
offset += 6;
|
|
343
271
|
obj.map = [];
|
|
344
272
|
for (let i = 0; i < 256; i++) obj.map.push(data[offset + i]);
|
|
345
273
|
return obj;
|
|
346
274
|
},
|
|
347
275
|
parse4(data, offset, obj) {
|
|
348
276
|
const startOffset = offset;
|
|
349
|
-
const format = bin.readUshort(data, offset);
|
|
350
277
|
offset += 2;
|
|
351
278
|
const length = bin.readUshort(data, offset);
|
|
352
|
-
offset +=
|
|
353
|
-
const language = bin.readUshort(data, offset);
|
|
354
|
-
offset += 2;
|
|
279
|
+
offset += 4;
|
|
355
280
|
const segCountX2 = bin.readUshort(data, offset);
|
|
356
281
|
offset += 2;
|
|
357
282
|
const segCount = segCountX2 >>> 1;
|
|
@@ -381,13 +306,7 @@ var cmap = {
|
|
|
381
306
|
return obj;
|
|
382
307
|
},
|
|
383
308
|
parse6(data, offset, obj) {
|
|
384
|
-
|
|
385
|
-
const format = bin.readUshort(data, offset);
|
|
386
|
-
offset += 2;
|
|
387
|
-
const length = bin.readUshort(data, offset);
|
|
388
|
-
offset += 2;
|
|
389
|
-
const language = bin.readUshort(data, offset);
|
|
390
|
-
offset += 2;
|
|
309
|
+
offset += 6;
|
|
391
310
|
obj.firstCode = bin.readUshort(data, offset);
|
|
392
311
|
offset += 2;
|
|
393
312
|
obj.entryCount = bin.readUshort(data, offset);
|
|
@@ -396,12 +315,7 @@ var cmap = {
|
|
|
396
315
|
return obj;
|
|
397
316
|
},
|
|
398
317
|
parse12(data, offset, obj) {
|
|
399
|
-
|
|
400
|
-
offset += 4;
|
|
401
|
-
const length = bin.readUint(data, offset);
|
|
402
|
-
offset += 4;
|
|
403
|
-
const language = bin.readUint(data, offset);
|
|
404
|
-
offset += 4;
|
|
318
|
+
offset += 12;
|
|
405
319
|
const nGroups = bin.readUint(data, offset) * 3;
|
|
406
320
|
offset += 4;
|
|
407
321
|
obj.groups = new Uint32Array(nGroups);
|
|
@@ -445,8 +359,7 @@ var glyf = {
|
|
|
445
359
|
}
|
|
446
360
|
const instructionLength = bin.readUshort(data, off);
|
|
447
361
|
off += 2;
|
|
448
|
-
if (
|
|
449
|
-
gl.instructions = bin.readBytes(data, off, instructionLength);
|
|
362
|
+
if (off + instructionLength > data.length) return null;
|
|
450
363
|
off += instructionLength;
|
|
451
364
|
const crdnum = gl.endPts[gl.noc - 1] + 1;
|
|
452
365
|
gl.flags = [];
|
|
@@ -1591,54 +1504,26 @@ var FontParser = class {
|
|
|
1591
1504
|
},
|
|
1592
1505
|
samplePathPoints(glyphPath, sampling) {
|
|
1593
1506
|
const rawContours = this.parseContours(glyphPath);
|
|
1594
|
-
const
|
|
1595
|
-
(contour
|
|
1596
|
-
contour,
|
|
1597
|
-
originalIndex: index,
|
|
1598
|
-
length: this.calculateContourLength(contour)
|
|
1599
|
-
})
|
|
1507
|
+
const prepared = rawContours.map(
|
|
1508
|
+
(contour) => this.buildSegments(contour)
|
|
1600
1509
|
);
|
|
1601
|
-
|
|
1510
|
+
prepared.sort((a, b) => b.totalLength - a.totalLength);
|
|
1602
1511
|
const allPoints = [];
|
|
1603
|
-
for (let i = 0; i <
|
|
1604
|
-
const {
|
|
1605
|
-
const
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
for (const seg of contour.segments) {
|
|
1616
|
-
if (seg.cmd === "L") {
|
|
1617
|
-
const endX = seg.coords[0];
|
|
1618
|
-
const endY = seg.coords[1];
|
|
1619
|
-
totalLength += Math.sqrt(
|
|
1620
|
-
(endX - currentX) ** 2 + (endY - currentY) ** 2
|
|
1621
|
-
);
|
|
1622
|
-
currentX = endX;
|
|
1623
|
-
currentY = endY;
|
|
1624
|
-
} else if (seg.cmd === "Q") {
|
|
1625
|
-
const controlX = seg.coords[0];
|
|
1626
|
-
const controlY = seg.coords[1];
|
|
1627
|
-
const endX = seg.coords[2];
|
|
1628
|
-
const endY = seg.coords[3];
|
|
1629
|
-
totalLength += this.approximateQuadraticLength(
|
|
1630
|
-
currentX,
|
|
1631
|
-
currentY,
|
|
1632
|
-
controlX,
|
|
1633
|
-
controlY,
|
|
1634
|
-
endX,
|
|
1635
|
-
endY
|
|
1636
|
-
);
|
|
1637
|
-
currentX = endX;
|
|
1638
|
-
currentY = endY;
|
|
1512
|
+
for (let i = 0; i < prepared.length; i++) {
|
|
1513
|
+
const { segments, totalLength } = prepared[i];
|
|
1514
|
+
const targetPoints = Math.max(
|
|
1515
|
+
5,
|
|
1516
|
+
Math.floor(totalLength * sampling * 0.1)
|
|
1517
|
+
);
|
|
1518
|
+
for (let j = 0; j <= targetPoints; j++) {
|
|
1519
|
+
const targetLength = j / targetPoints * totalLength;
|
|
1520
|
+
const pt = this.getPointAtLengthInContour(segments, targetLength);
|
|
1521
|
+
if (pt) {
|
|
1522
|
+
allPoints.push({ x: pt.x, y: pt.y, contour: i });
|
|
1523
|
+
}
|
|
1639
1524
|
}
|
|
1640
1525
|
}
|
|
1641
|
-
return
|
|
1526
|
+
return allPoints;
|
|
1642
1527
|
},
|
|
1643
1528
|
// Parse glyph path into separate contours
|
|
1644
1529
|
parseContours(glyphPath) {
|
|
@@ -1680,8 +1565,8 @@ var FontParser = class {
|
|
|
1680
1565
|
}
|
|
1681
1566
|
return contours;
|
|
1682
1567
|
},
|
|
1683
|
-
//
|
|
1684
|
-
|
|
1568
|
+
// Build segments from a contour with lengths pre-calculated
|
|
1569
|
+
buildSegments(contour) {
|
|
1685
1570
|
const segments = [];
|
|
1686
1571
|
let currentX = contour.startX;
|
|
1687
1572
|
let currentY = contour.startY;
|
|
@@ -1690,9 +1575,7 @@ var FontParser = class {
|
|
|
1690
1575
|
if (seg.cmd === "L") {
|
|
1691
1576
|
const endX = seg.coords[0];
|
|
1692
1577
|
const endY = seg.coords[1];
|
|
1693
|
-
const length = Math.
|
|
1694
|
-
(endX - currentX) ** 2 + (endY - currentY) ** 2
|
|
1695
|
-
);
|
|
1578
|
+
const length = Math.hypot(endX - currentX, endY - currentY);
|
|
1696
1579
|
segments.push({
|
|
1697
1580
|
type: "L",
|
|
1698
1581
|
startX: currentX,
|
|
@@ -1710,14 +1593,9 @@ var FontParser = class {
|
|
|
1710
1593
|
const controlY = seg.coords[1];
|
|
1711
1594
|
const endX = seg.coords[2];
|
|
1712
1595
|
const endY = seg.coords[3];
|
|
1713
|
-
const
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
controlX,
|
|
1717
|
-
controlY,
|
|
1718
|
-
endX,
|
|
1719
|
-
endY
|
|
1720
|
-
);
|
|
1596
|
+
const chord = Math.hypot(endX - currentX, endY - currentY);
|
|
1597
|
+
const ctrl = Math.hypot(controlX - currentX, controlY - currentY) + Math.hypot(endX - controlX, endY - controlY);
|
|
1598
|
+
const length = (chord + ctrl) / 2;
|
|
1721
1599
|
segments.push({
|
|
1722
1600
|
type: "Q",
|
|
1723
1601
|
startX: currentX,
|
|
@@ -1734,26 +1612,7 @@ var FontParser = class {
|
|
|
1734
1612
|
currentY = endY;
|
|
1735
1613
|
}
|
|
1736
1614
|
}
|
|
1737
|
-
|
|
1738
|
-
const targetPoints = Math.max(
|
|
1739
|
-
5,
|
|
1740
|
-
Math.floor(totalLength * sampling * 0.1)
|
|
1741
|
-
);
|
|
1742
|
-
for (let i = 0; i <= targetPoints; i++) {
|
|
1743
|
-
const targetLength = i / targetPoints * totalLength;
|
|
1744
|
-
const pointData = this.getPointAtLengthInContour(
|
|
1745
|
-
segments,
|
|
1746
|
-
targetLength
|
|
1747
|
-
);
|
|
1748
|
-
if (pointData) {
|
|
1749
|
-
points.push({
|
|
1750
|
-
x: pointData.x,
|
|
1751
|
-
y: pointData.y,
|
|
1752
|
-
contour: contourIndex
|
|
1753
|
-
});
|
|
1754
|
-
}
|
|
1755
|
-
}
|
|
1756
|
-
return points;
|
|
1615
|
+
return { segments, totalLength };
|
|
1757
1616
|
},
|
|
1758
1617
|
// Get point at length within a single contour
|
|
1759
1618
|
getPointAtLengthInContour(segments, targetLength) {
|
|
@@ -1775,12 +1634,6 @@ var FontParser = class {
|
|
|
1775
1634
|
}
|
|
1776
1635
|
}
|
|
1777
1636
|
return null;
|
|
1778
|
-
},
|
|
1779
|
-
// Helper function to approximate quadratic curve length
|
|
1780
|
-
approximateQuadraticLength(x0, y0, x1, y1, x2, y2) {
|
|
1781
|
-
const chordLength = Math.sqrt((x2 - x0) ** 2 + (y2 - y0) ** 2);
|
|
1782
|
-
const controlLength = Math.sqrt((x1 - x0) ** 2 + (y1 - y0) ** 2) + Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
|
|
1783
|
-
return (chordLength + controlLength) / 2;
|
|
1784
1637
|
}
|
|
1785
1638
|
};
|
|
1786
1639
|
}
|
|
@@ -1948,264 +1801,6 @@ var CatmullRom = class {
|
|
|
1948
1801
|
}
|
|
1949
1802
|
};
|
|
1950
1803
|
|
|
1951
|
-
// src/plugins/Things.tsx
|
|
1952
|
-
var Things = class {
|
|
1953
|
-
/**
|
|
1954
|
-
* Configure the plugin
|
|
1955
|
-
*/
|
|
1956
|
-
static configure(config) {
|
|
1957
|
-
this.config = { ...this.config, ...config };
|
|
1958
|
-
}
|
|
1959
|
-
/**
|
|
1960
|
-
* Create a new thing
|
|
1961
|
-
*/
|
|
1962
|
-
static create(options = {}) {
|
|
1963
|
-
if (this.things.size >= (this.config.maxThings || 1e3)) {
|
|
1964
|
-
throw new Error(
|
|
1965
|
-
`Maximum number of things (${this.config.maxThings}) reached`
|
|
1966
|
-
);
|
|
1967
|
-
}
|
|
1968
|
-
const thing = {
|
|
1969
|
-
id: options.id || `thing_${this.idCounter++}`,
|
|
1970
|
-
x: options.x || 0,
|
|
1971
|
-
y: options.y || 0,
|
|
1972
|
-
width: options.width || this.config.defaultSize || 50,
|
|
1973
|
-
height: options.height || this.config.defaultSize || 50,
|
|
1974
|
-
rotation: options.rotation || 0,
|
|
1975
|
-
scale: options.scale || 1,
|
|
1976
|
-
color: options.color || this.config.defaultColor || "#ffffff",
|
|
1977
|
-
data: options.data || {}
|
|
1978
|
-
};
|
|
1979
|
-
this.things.set(thing.id, thing);
|
|
1980
|
-
return thing;
|
|
1981
|
-
}
|
|
1982
|
-
/**
|
|
1983
|
-
* Get a thing by ID
|
|
1984
|
-
*/
|
|
1985
|
-
static get(id) {
|
|
1986
|
-
return this.things.get(id);
|
|
1987
|
-
}
|
|
1988
|
-
/**
|
|
1989
|
-
* Get all things
|
|
1990
|
-
*/
|
|
1991
|
-
static getAll() {
|
|
1992
|
-
return Array.from(this.things.values());
|
|
1993
|
-
}
|
|
1994
|
-
/**
|
|
1995
|
-
* Update a thing's properties
|
|
1996
|
-
*/
|
|
1997
|
-
static update(id, updates) {
|
|
1998
|
-
const thing = this.things.get(id);
|
|
1999
|
-
if (thing) {
|
|
2000
|
-
Object.assign(thing, updates);
|
|
2001
|
-
}
|
|
2002
|
-
}
|
|
2003
|
-
/**
|
|
2004
|
-
* Remove a thing
|
|
2005
|
-
*/
|
|
2006
|
-
static remove(id) {
|
|
2007
|
-
return this.things.delete(id);
|
|
2008
|
-
}
|
|
2009
|
-
/**
|
|
2010
|
-
* Clear all things
|
|
2011
|
-
*/
|
|
2012
|
-
static clear() {
|
|
2013
|
-
this.things.clear();
|
|
2014
|
-
this.idCounter = 0;
|
|
2015
|
-
}
|
|
2016
|
-
/**
|
|
2017
|
-
* Move a thing
|
|
2018
|
-
*/
|
|
2019
|
-
static move(id, dx, dy) {
|
|
2020
|
-
const thing = this.things.get(id);
|
|
2021
|
-
if (thing) {
|
|
2022
|
-
thing.x += dx;
|
|
2023
|
-
thing.y += dy;
|
|
2024
|
-
}
|
|
2025
|
-
}
|
|
2026
|
-
/**
|
|
2027
|
-
* Rotate a thing
|
|
2028
|
-
*/
|
|
2029
|
-
static rotate(id, angle) {
|
|
2030
|
-
const thing = this.things.get(id);
|
|
2031
|
-
if (thing) {
|
|
2032
|
-
thing.rotation += angle;
|
|
2033
|
-
}
|
|
2034
|
-
}
|
|
2035
|
-
/**
|
|
2036
|
-
* Scale a thing
|
|
2037
|
-
*/
|
|
2038
|
-
static scale(id, factor) {
|
|
2039
|
-
const thing = this.things.get(id);
|
|
2040
|
-
if (thing) {
|
|
2041
|
-
thing.scale *= factor;
|
|
2042
|
-
}
|
|
2043
|
-
}
|
|
2044
|
-
/**
|
|
2045
|
-
* Find things within a radius
|
|
2046
|
-
*/
|
|
2047
|
-
static findNear(x, y, radius) {
|
|
2048
|
-
const near = [];
|
|
2049
|
-
const radiusSq = radius * radius;
|
|
2050
|
-
this.things.forEach((thing) => {
|
|
2051
|
-
const dx = thing.x - x;
|
|
2052
|
-
const dy = thing.y - y;
|
|
2053
|
-
if (dx * dx + dy * dy <= radiusSq) {
|
|
2054
|
-
near.push(thing);
|
|
2055
|
-
}
|
|
2056
|
-
});
|
|
2057
|
-
return near;
|
|
2058
|
-
}
|
|
2059
|
-
/**
|
|
2060
|
-
* Find things that overlap with a rectangle
|
|
2061
|
-
*/
|
|
2062
|
-
static findInRect(x, y, width, height) {
|
|
2063
|
-
const found = [];
|
|
2064
|
-
this.things.forEach((thing) => {
|
|
2065
|
-
const halfW = thing.width * thing.scale / 2;
|
|
2066
|
-
const halfH = thing.height * thing.scale / 2;
|
|
2067
|
-
if (thing.x + halfW >= x && thing.x - halfW <= x + width && thing.y + halfH >= y && thing.y - halfH <= y + height) {
|
|
2068
|
-
found.push(thing);
|
|
2069
|
-
}
|
|
2070
|
-
});
|
|
2071
|
-
return found;
|
|
2072
|
-
}
|
|
2073
|
-
/**
|
|
2074
|
-
* Apply a function to all things
|
|
2075
|
-
*/
|
|
2076
|
-
static forEach(fn) {
|
|
2077
|
-
this.things.forEach(fn);
|
|
2078
|
-
}
|
|
2079
|
-
/**
|
|
2080
|
-
* Map things to a new array
|
|
2081
|
-
*/
|
|
2082
|
-
static map(fn) {
|
|
2083
|
-
return Array.from(this.things.values()).map(fn);
|
|
2084
|
-
}
|
|
2085
|
-
/**
|
|
2086
|
-
* Filter things
|
|
2087
|
-
*/
|
|
2088
|
-
static filter(fn) {
|
|
2089
|
-
return Array.from(this.things.values()).filter(fn);
|
|
2090
|
-
}
|
|
2091
|
-
/**
|
|
2092
|
-
* Sort things by a property or function
|
|
2093
|
-
*/
|
|
2094
|
-
static sort(fn) {
|
|
2095
|
-
return Array.from(this.things.values()).sort(fn);
|
|
2096
|
-
}
|
|
2097
|
-
/**
|
|
2098
|
-
* Draw all things
|
|
2099
|
-
*/
|
|
2100
|
-
static draw(ctx, options) {
|
|
2101
|
-
const thingsToDraw = options?.filter ? this.filter(options.filter) : this.getAll();
|
|
2102
|
-
thingsToDraw.forEach((thing) => {
|
|
2103
|
-
if (options?.customDraw) {
|
|
2104
|
-
options.customDraw(ctx, thing);
|
|
2105
|
-
} else {
|
|
2106
|
-
this.drawThing(ctx, thing);
|
|
2107
|
-
}
|
|
2108
|
-
});
|
|
2109
|
-
}
|
|
2110
|
-
/**
|
|
2111
|
-
* Default drawing method for a thing
|
|
2112
|
-
*/
|
|
2113
|
-
static drawThing(ctx, thing) {
|
|
2114
|
-
ctx.save();
|
|
2115
|
-
ctx.translate(thing.x, thing.y);
|
|
2116
|
-
ctx.rotate(thing.rotation);
|
|
2117
|
-
ctx.scale(thing.scale, thing.scale);
|
|
2118
|
-
ctx.fillStyle = thing.color;
|
|
2119
|
-
ctx.fillRect(
|
|
2120
|
-
-thing.width / 2,
|
|
2121
|
-
-thing.height / 2,
|
|
2122
|
-
thing.width,
|
|
2123
|
-
thing.height
|
|
2124
|
-
);
|
|
2125
|
-
ctx.restore();
|
|
2126
|
-
}
|
|
2127
|
-
/**
|
|
2128
|
-
* Animate things with a simple physics update
|
|
2129
|
-
*/
|
|
2130
|
-
static animatePhysics(deltaTime, options) {
|
|
2131
|
-
const gravity = options?.gravity || 0;
|
|
2132
|
-
const friction = options?.friction || 0.99;
|
|
2133
|
-
const bounds = options?.bounds;
|
|
2134
|
-
this.things.forEach((thing) => {
|
|
2135
|
-
if (!thing.data.vx) thing.data.vx = 0;
|
|
2136
|
-
if (!thing.data.vy) thing.data.vy = 0;
|
|
2137
|
-
thing.data.vy += gravity;
|
|
2138
|
-
thing.data.vx *= friction;
|
|
2139
|
-
thing.data.vy *= friction;
|
|
2140
|
-
thing.x += thing.data.vx;
|
|
2141
|
-
thing.y += thing.data.vy;
|
|
2142
|
-
if (bounds) {
|
|
2143
|
-
const halfW = thing.width * thing.scale / 2;
|
|
2144
|
-
const halfH = thing.height * thing.scale / 2;
|
|
2145
|
-
if (thing.x - halfW < bounds.x) {
|
|
2146
|
-
thing.x = bounds.x + halfW;
|
|
2147
|
-
thing.data.vx *= -0.8;
|
|
2148
|
-
}
|
|
2149
|
-
if (thing.x + halfW > bounds.x + bounds.width) {
|
|
2150
|
-
thing.x = bounds.x + bounds.width - halfW;
|
|
2151
|
-
thing.data.vx *= -0.8;
|
|
2152
|
-
}
|
|
2153
|
-
if (thing.y - halfH < bounds.y) {
|
|
2154
|
-
thing.y = bounds.y + halfH;
|
|
2155
|
-
thing.data.vy *= -0.8;
|
|
2156
|
-
}
|
|
2157
|
-
if (thing.y + halfH > bounds.y + bounds.height) {
|
|
2158
|
-
thing.y = bounds.y + bounds.height - halfH;
|
|
2159
|
-
thing.data.vy *= -0.8;
|
|
2160
|
-
}
|
|
2161
|
-
}
|
|
2162
|
-
});
|
|
2163
|
-
}
|
|
2164
|
-
/**
|
|
2165
|
-
* Get the count of things
|
|
2166
|
-
*/
|
|
2167
|
-
static count() {
|
|
2168
|
-
return this.things.size;
|
|
2169
|
-
}
|
|
2170
|
-
/**
|
|
2171
|
-
* Check if a thing exists
|
|
2172
|
-
*/
|
|
2173
|
-
static has(id) {
|
|
2174
|
-
return this.things.has(id);
|
|
2175
|
-
}
|
|
2176
|
-
/**
|
|
2177
|
-
* Utility: Get distance between two things
|
|
2178
|
-
*/
|
|
2179
|
-
static distance(id1, id2) {
|
|
2180
|
-
const thing1 = this.things.get(id1);
|
|
2181
|
-
const thing2 = this.things.get(id2);
|
|
2182
|
-
if (!thing1 || !thing2) return Infinity;
|
|
2183
|
-
const dx = thing2.x - thing1.x;
|
|
2184
|
-
const dy = thing2.y - thing1.y;
|
|
2185
|
-
return Math.sqrt(dx * dx + dy * dy);
|
|
2186
|
-
}
|
|
2187
|
-
/**
|
|
2188
|
-
* Utility: Check collision between two things
|
|
2189
|
-
*/
|
|
2190
|
-
static collides(id1, id2) {
|
|
2191
|
-
const thing1 = this.things.get(id1);
|
|
2192
|
-
const thing2 = this.things.get(id2);
|
|
2193
|
-
if (!thing1 || !thing2) return false;
|
|
2194
|
-
const halfW1 = thing1.width * thing1.scale / 2;
|
|
2195
|
-
const halfH1 = thing1.height * thing1.scale / 2;
|
|
2196
|
-
const halfW2 = thing2.width * thing2.scale / 2;
|
|
2197
|
-
const halfH2 = thing2.height * thing2.scale / 2;
|
|
2198
|
-
return Math.abs(thing1.x - thing2.x) < halfW1 + halfW2 && Math.abs(thing1.y - thing2.y) < halfH1 + halfH2;
|
|
2199
|
-
}
|
|
2200
|
-
};
|
|
2201
|
-
Things.things = /* @__PURE__ */ new Map();
|
|
2202
|
-
Things.config = {
|
|
2203
|
-
maxThings: 1e3,
|
|
2204
|
-
defaultSize: 50,
|
|
2205
|
-
defaultColor: "#ffffff"
|
|
2206
|
-
};
|
|
2207
|
-
Things.idCounter = 0;
|
|
2208
|
-
|
|
2209
1804
|
// src/plugins/Sprites.tsx
|
|
2210
1805
|
var Sprites = class {
|
|
2211
1806
|
/**
|
|
@@ -2508,11 +2103,556 @@ var SpriteAnimation = class {
|
|
|
2508
2103
|
return this.playing;
|
|
2509
2104
|
}
|
|
2510
2105
|
};
|
|
2106
|
+
|
|
2107
|
+
// src/plugins/MatterPhysics.tsx
|
|
2108
|
+
var _MatterPhysics = class _MatterPhysics {
|
|
2109
|
+
/**
|
|
2110
|
+
* Dynamically load matter-js and initialize the engine.
|
|
2111
|
+
* Tries a local `import("matter-js")` first — if that fails (package not installed),
|
|
2112
|
+
* falls back to loading from CDN via a script tag.
|
|
2113
|
+
* @param config - Engine configuration
|
|
2114
|
+
* @returns Promise that resolves when ready
|
|
2115
|
+
*
|
|
2116
|
+
* @example
|
|
2117
|
+
* ```tsx
|
|
2118
|
+
* // In preload or setup:
|
|
2119
|
+
* await MatterPhysics.load({ gravity: { x: 0, y: 1 } });
|
|
2120
|
+
* ```
|
|
2121
|
+
*/
|
|
2122
|
+
static async load(config = {}) {
|
|
2123
|
+
if (this._loaded && this.engine) return;
|
|
2124
|
+
try {
|
|
2125
|
+
const matterModule = await import("matter-js");
|
|
2126
|
+
const Matter = matterModule.default || matterModule;
|
|
2127
|
+
this.init(Matter, config);
|
|
2128
|
+
this._loaded = true;
|
|
2129
|
+
return;
|
|
2130
|
+
} catch {
|
|
2131
|
+
}
|
|
2132
|
+
try {
|
|
2133
|
+
const Matter = await this._loadFromCDN();
|
|
2134
|
+
this.init(Matter, config);
|
|
2135
|
+
this._loaded = true;
|
|
2136
|
+
} catch (e) {
|
|
2137
|
+
throw new Error(
|
|
2138
|
+
`MatterPhysics: failed to load matter-js. Install it with "npm install matter-js" or check your network connection for CDN fallback (${this.MATTER_CDN}).`
|
|
2139
|
+
);
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
2142
|
+
/**
|
|
2143
|
+
* Load Matter.js from CDN by injecting a script tag.
|
|
2144
|
+
* Resolves with the global `Matter` object.
|
|
2145
|
+
*/
|
|
2146
|
+
static _loadFromCDN() {
|
|
2147
|
+
return new Promise((resolve, reject) => {
|
|
2148
|
+
if (typeof globalThis.Matter !== "undefined") {
|
|
2149
|
+
resolve(globalThis.Matter);
|
|
2150
|
+
return;
|
|
2151
|
+
}
|
|
2152
|
+
const script = document.createElement("script");
|
|
2153
|
+
script.src = this.MATTER_CDN;
|
|
2154
|
+
script.async = true;
|
|
2155
|
+
script.onload = () => {
|
|
2156
|
+
if (typeof globalThis.Matter !== "undefined") {
|
|
2157
|
+
resolve(globalThis.Matter);
|
|
2158
|
+
} else {
|
|
2159
|
+
reject(new Error("Matter global not found after script load"));
|
|
2160
|
+
}
|
|
2161
|
+
};
|
|
2162
|
+
script.onerror = () => {
|
|
2163
|
+
reject(new Error(`Failed to load script from ${this.MATTER_CDN}`));
|
|
2164
|
+
};
|
|
2165
|
+
document.head.appendChild(script);
|
|
2166
|
+
});
|
|
2167
|
+
}
|
|
2168
|
+
/**
|
|
2169
|
+
* Check if MatterPhysics has been loaded and initialized.
|
|
2170
|
+
*/
|
|
2171
|
+
static get isLoaded() {
|
|
2172
|
+
return this._loaded && this.engine !== null;
|
|
2173
|
+
}
|
|
2174
|
+
/**
|
|
2175
|
+
* Initialize the physics engine with a Matter.js module reference.
|
|
2176
|
+
* Prefer `load()` for dynamic import. Use this if you want to pass the module directly.
|
|
2177
|
+
* @param matterModule - The Matter.js module (import Matter from 'matter-js')
|
|
2178
|
+
* @param config - Engine configuration
|
|
2179
|
+
*/
|
|
2180
|
+
static init(matterModule, config = {}) {
|
|
2181
|
+
this.Matter = matterModule;
|
|
2182
|
+
this._loaded = true;
|
|
2183
|
+
this.engine = matterModule.Engine.create({
|
|
2184
|
+
enableSleeping: config.enableSleeping ?? false,
|
|
2185
|
+
constraintIterations: config.constraintIterations ?? 2,
|
|
2186
|
+
positionIterations: config.positionIterations ?? 6,
|
|
2187
|
+
velocityIterations: config.velocityIterations ?? 4
|
|
2188
|
+
});
|
|
2189
|
+
this.world = this.engine.world;
|
|
2190
|
+
if (config.gravity) {
|
|
2191
|
+
this.world.gravity.x = config.gravity.x;
|
|
2192
|
+
this.world.gravity.y = config.gravity.y;
|
|
2193
|
+
if (config.gravity.scale !== void 0) {
|
|
2194
|
+
this.world.gravity.scale = config.gravity.scale;
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
this.bodies.clear();
|
|
2198
|
+
this.constraints.clear();
|
|
2199
|
+
this.idCounter = 0;
|
|
2200
|
+
this.constraintIdCounter = 0;
|
|
2201
|
+
}
|
|
2202
|
+
static ensureInit() {
|
|
2203
|
+
if (!this.Matter || !this.engine) {
|
|
2204
|
+
throw new Error(
|
|
2205
|
+
"MatterPhysics not initialized. Call MatterPhysics.init(Matter) first."
|
|
2206
|
+
);
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
static generateId(prefix = "body") {
|
|
2210
|
+
return `${prefix}_${this.idCounter++}`;
|
|
2211
|
+
}
|
|
2212
|
+
/**
|
|
2213
|
+
* Add a rectangle body.
|
|
2214
|
+
* @param x - Center X
|
|
2215
|
+
* @param y - Center Y
|
|
2216
|
+
* @param width - Width
|
|
2217
|
+
* @param height - Height
|
|
2218
|
+
* @param options - Body options
|
|
2219
|
+
* @returns Body reference with id
|
|
2220
|
+
*/
|
|
2221
|
+
static addRect(x, y, width, height, options = {}) {
|
|
2222
|
+
this.ensureInit();
|
|
2223
|
+
const id = options.id || this.generateId("rect");
|
|
2224
|
+
const body = this.Matter.Bodies.rectangle(x, y, width, height, options);
|
|
2225
|
+
this.Matter.Composite.add(this.world, body);
|
|
2226
|
+
const ref = { id, body, label: options.label };
|
|
2227
|
+
this.bodies.set(id, ref);
|
|
2228
|
+
return ref;
|
|
2229
|
+
}
|
|
2230
|
+
/**
|
|
2231
|
+
* Add a circle body.
|
|
2232
|
+
* @param x - Center X
|
|
2233
|
+
* @param y - Center Y
|
|
2234
|
+
* @param radius - Radius
|
|
2235
|
+
* @param options - Body options
|
|
2236
|
+
* @returns Body reference with id
|
|
2237
|
+
*/
|
|
2238
|
+
static addCircle(x, y, radius, options = {}) {
|
|
2239
|
+
this.ensureInit();
|
|
2240
|
+
const id = options.id || this.generateId("circle");
|
|
2241
|
+
const body = this.Matter.Bodies.circle(x, y, radius, options);
|
|
2242
|
+
this.Matter.Composite.add(this.world, body);
|
|
2243
|
+
const ref = { id, body, label: options.label };
|
|
2244
|
+
this.bodies.set(id, ref);
|
|
2245
|
+
return ref;
|
|
2246
|
+
}
|
|
2247
|
+
/**
|
|
2248
|
+
* Add a polygon body.
|
|
2249
|
+
* @param x - Center X
|
|
2250
|
+
* @param y - Center Y
|
|
2251
|
+
* @param sides - Number of sides
|
|
2252
|
+
* @param radius - Radius
|
|
2253
|
+
* @param options - Body options
|
|
2254
|
+
* @returns Body reference with id
|
|
2255
|
+
*/
|
|
2256
|
+
static addPolygon(x, y, sides, radius, options = {}) {
|
|
2257
|
+
this.ensureInit();
|
|
2258
|
+
const id = options.id || this.generateId("polygon");
|
|
2259
|
+
const body = this.Matter.Bodies.polygon(x, y, sides, radius, options);
|
|
2260
|
+
this.Matter.Composite.add(this.world, body);
|
|
2261
|
+
const ref = { id, body, label: options.label };
|
|
2262
|
+
this.bodies.set(id, ref);
|
|
2263
|
+
return ref;
|
|
2264
|
+
}
|
|
2265
|
+
/**
|
|
2266
|
+
* Add a body from custom vertices.
|
|
2267
|
+
* @param x - Center X
|
|
2268
|
+
* @param y - Center Y
|
|
2269
|
+
* @param vertices - Array of {x, y} points
|
|
2270
|
+
* @param options - Body options
|
|
2271
|
+
* @returns Body reference with id
|
|
2272
|
+
*/
|
|
2273
|
+
static addFromVertices(x, y, vertices, options = {}) {
|
|
2274
|
+
this.ensureInit();
|
|
2275
|
+
const id = options.id || this.generateId("custom");
|
|
2276
|
+
const body = this.Matter.Bodies.fromVertices(x, y, vertices, options);
|
|
2277
|
+
this.Matter.Composite.add(this.world, body);
|
|
2278
|
+
const ref = { id, body, label: options.label };
|
|
2279
|
+
this.bodies.set(id, ref);
|
|
2280
|
+
return ref;
|
|
2281
|
+
}
|
|
2282
|
+
/**
|
|
2283
|
+
* Add a constraint between two bodies.
|
|
2284
|
+
* @param bodyIdA - First body ID
|
|
2285
|
+
* @param bodyIdB - Second body ID
|
|
2286
|
+
* @param options - Constraint options
|
|
2287
|
+
* @returns Constraint ID
|
|
2288
|
+
*/
|
|
2289
|
+
static addConstraint(bodyIdA, bodyIdB, options = {}) {
|
|
2290
|
+
this.ensureInit();
|
|
2291
|
+
const refA = this.bodies.get(bodyIdA);
|
|
2292
|
+
const refB = this.bodies.get(bodyIdB);
|
|
2293
|
+
if (!refA || !refB) {
|
|
2294
|
+
throw new Error(`Body not found: ${!refA ? bodyIdA : bodyIdB}`);
|
|
2295
|
+
}
|
|
2296
|
+
const id = options.id || `constraint_${this.constraintIdCounter++}`;
|
|
2297
|
+
const constraint = this.Matter.Constraint.create({
|
|
2298
|
+
bodyA: refA.body,
|
|
2299
|
+
bodyB: refB.body,
|
|
2300
|
+
...options
|
|
2301
|
+
});
|
|
2302
|
+
this.Matter.Composite.add(this.world, constraint);
|
|
2303
|
+
this.constraints.set(id, constraint);
|
|
2304
|
+
return id;
|
|
2305
|
+
}
|
|
2306
|
+
/**
|
|
2307
|
+
* Add a constraint anchored to a world point.
|
|
2308
|
+
* @param bodyId - Body ID
|
|
2309
|
+
* @param worldPoint - World anchor point {x, y}
|
|
2310
|
+
* @param options - Constraint options
|
|
2311
|
+
* @returns Constraint ID
|
|
2312
|
+
*/
|
|
2313
|
+
static addWorldConstraint(bodyId, worldPoint, options = {}) {
|
|
2314
|
+
this.ensureInit();
|
|
2315
|
+
const ref = this.bodies.get(bodyId);
|
|
2316
|
+
if (!ref) throw new Error(`Body not found: ${bodyId}`);
|
|
2317
|
+
const id = options.id || `constraint_${this.constraintIdCounter++}`;
|
|
2318
|
+
const constraint = this.Matter.Constraint.create({
|
|
2319
|
+
bodyA: ref.body,
|
|
2320
|
+
pointB: worldPoint,
|
|
2321
|
+
...options
|
|
2322
|
+
});
|
|
2323
|
+
this.Matter.Composite.add(this.world, constraint);
|
|
2324
|
+
this.constraints.set(id, constraint);
|
|
2325
|
+
return id;
|
|
2326
|
+
}
|
|
2327
|
+
/**
|
|
2328
|
+
* Apply force to a body.
|
|
2329
|
+
* @param bodyId - Body ID
|
|
2330
|
+
* @param force - Force vector {x, y}
|
|
2331
|
+
*/
|
|
2332
|
+
static applyForce(bodyId, force) {
|
|
2333
|
+
this.ensureInit();
|
|
2334
|
+
const ref = this.bodies.get(bodyId);
|
|
2335
|
+
if (!ref) return;
|
|
2336
|
+
this.Matter.Body.applyForce(ref.body, ref.body.position, force);
|
|
2337
|
+
}
|
|
2338
|
+
/**
|
|
2339
|
+
* Set velocity of a body.
|
|
2340
|
+
* @param bodyId - Body ID
|
|
2341
|
+
* @param velocity - Velocity vector {x, y}
|
|
2342
|
+
*/
|
|
2343
|
+
static setVelocity(bodyId, velocity) {
|
|
2344
|
+
this.ensureInit();
|
|
2345
|
+
const ref = this.bodies.get(bodyId);
|
|
2346
|
+
if (!ref) return;
|
|
2347
|
+
this.Matter.Body.setVelocity(ref.body, velocity);
|
|
2348
|
+
}
|
|
2349
|
+
/**
|
|
2350
|
+
* Set position of a body.
|
|
2351
|
+
* @param bodyId - Body ID
|
|
2352
|
+
* @param position - Position {x, y}
|
|
2353
|
+
*/
|
|
2354
|
+
static setPosition(bodyId, position) {
|
|
2355
|
+
this.ensureInit();
|
|
2356
|
+
const ref = this.bodies.get(bodyId);
|
|
2357
|
+
if (!ref) return;
|
|
2358
|
+
this.Matter.Body.setPosition(ref.body, position);
|
|
2359
|
+
}
|
|
2360
|
+
/**
|
|
2361
|
+
* Set gravity.
|
|
2362
|
+
* @param x - Horizontal gravity
|
|
2363
|
+
* @param y - Vertical gravity
|
|
2364
|
+
*/
|
|
2365
|
+
static setGravity(x, y) {
|
|
2366
|
+
this.ensureInit();
|
|
2367
|
+
this.world.gravity.x = x;
|
|
2368
|
+
this.world.gravity.y = y;
|
|
2369
|
+
}
|
|
2370
|
+
/**
|
|
2371
|
+
* Get a body reference by ID.
|
|
2372
|
+
*/
|
|
2373
|
+
static getBody(id) {
|
|
2374
|
+
return this.bodies.get(id);
|
|
2375
|
+
}
|
|
2376
|
+
/**
|
|
2377
|
+
* Get all body references.
|
|
2378
|
+
*/
|
|
2379
|
+
static getAllBodies() {
|
|
2380
|
+
return Array.from(this.bodies.values());
|
|
2381
|
+
}
|
|
2382
|
+
/**
|
|
2383
|
+
* Remove a body.
|
|
2384
|
+
* @param id - Body ID
|
|
2385
|
+
*/
|
|
2386
|
+
static removeBody(id) {
|
|
2387
|
+
this.ensureInit();
|
|
2388
|
+
const ref = this.bodies.get(id);
|
|
2389
|
+
if (!ref) return false;
|
|
2390
|
+
this.Matter.Composite.remove(this.world, ref.body);
|
|
2391
|
+
return this.bodies.delete(id);
|
|
2392
|
+
}
|
|
2393
|
+
/**
|
|
2394
|
+
* Remove a constraint.
|
|
2395
|
+
* @param id - Constraint ID
|
|
2396
|
+
*/
|
|
2397
|
+
static removeConstraint(id) {
|
|
2398
|
+
this.ensureInit();
|
|
2399
|
+
const constraint = this.constraints.get(id);
|
|
2400
|
+
if (!constraint) return false;
|
|
2401
|
+
this.Matter.Composite.remove(this.world, constraint);
|
|
2402
|
+
return this.constraints.delete(id);
|
|
2403
|
+
}
|
|
2404
|
+
/**
|
|
2405
|
+
* Step the physics engine.
|
|
2406
|
+
* @param deltaTime - Time step in milliseconds
|
|
2407
|
+
*/
|
|
2408
|
+
static update(deltaTime) {
|
|
2409
|
+
this.ensureInit();
|
|
2410
|
+
this.Matter.Engine.update(this.engine, deltaTime);
|
|
2411
|
+
}
|
|
2412
|
+
/**
|
|
2413
|
+
* Debug draw all bodies and constraints using Klint drawing primitives.
|
|
2414
|
+
* @param ctx - Klint context
|
|
2415
|
+
* @param options - Drawing options
|
|
2416
|
+
*/
|
|
2417
|
+
static draw(ctx, options = {}) {
|
|
2418
|
+
const {
|
|
2419
|
+
showBodies = true,
|
|
2420
|
+
showConstraints = true,
|
|
2421
|
+
showBounds = false,
|
|
2422
|
+
bodyStroke = "#ffffff",
|
|
2423
|
+
bodyFill = "transparent",
|
|
2424
|
+
staticFill = "#666666",
|
|
2425
|
+
constraintStroke = "#44ff44",
|
|
2426
|
+
lineWidth = 1
|
|
2427
|
+
} = options;
|
|
2428
|
+
ctx.save();
|
|
2429
|
+
ctx.strokeWidth(lineWidth);
|
|
2430
|
+
if (showBodies) {
|
|
2431
|
+
this.bodies.forEach((ref) => {
|
|
2432
|
+
const body = ref.body;
|
|
2433
|
+
const vertices = body.vertices;
|
|
2434
|
+
ctx.fillStyle = body.isStatic ? staticFill : bodyFill;
|
|
2435
|
+
ctx.strokeStyle = bodyStroke;
|
|
2436
|
+
ctx.beginShape();
|
|
2437
|
+
for (let i = 0; i < vertices.length; i++) {
|
|
2438
|
+
ctx.vertex(vertices[i].x, vertices[i].y);
|
|
2439
|
+
}
|
|
2440
|
+
ctx.endShape(true);
|
|
2441
|
+
if (showBounds) {
|
|
2442
|
+
const { min, max } = body.bounds;
|
|
2443
|
+
ctx.noFill();
|
|
2444
|
+
ctx.strokeStyle = "#ff444444";
|
|
2445
|
+
ctx.setRectOrigin("corner");
|
|
2446
|
+
ctx.rectangle(min.x, min.y, max.x - min.x, max.y - min.y);
|
|
2447
|
+
}
|
|
2448
|
+
});
|
|
2449
|
+
}
|
|
2450
|
+
if (showConstraints) {
|
|
2451
|
+
ctx.strokeStyle = constraintStroke;
|
|
2452
|
+
this.constraints.forEach((constraint) => {
|
|
2453
|
+
const { bodyA, bodyB, pointA, pointB } = constraint;
|
|
2454
|
+
const startX = bodyA ? bodyA.position.x + (pointA?.x || 0) : pointA?.x || 0;
|
|
2455
|
+
const startY = bodyA ? bodyA.position.y + (pointA?.y || 0) : pointA?.y || 0;
|
|
2456
|
+
const endX = bodyB ? bodyB.position.x + (pointB?.x || 0) : pointB?.x || 0;
|
|
2457
|
+
const endY = bodyB ? bodyB.position.y + (pointB?.y || 0) : pointB?.y || 0;
|
|
2458
|
+
ctx.line(startX, startY, endX, endY);
|
|
2459
|
+
});
|
|
2460
|
+
}
|
|
2461
|
+
ctx.restore();
|
|
2462
|
+
}
|
|
2463
|
+
/**
|
|
2464
|
+
* Iterate over all bodies with their position and angle.
|
|
2465
|
+
* Convenience method for syncing physics bodies to visual objects.
|
|
2466
|
+
* @param fn - Callback receiving body data
|
|
2467
|
+
*/
|
|
2468
|
+
static forEach(fn) {
|
|
2469
|
+
this.bodies.forEach((ref) => {
|
|
2470
|
+
fn({
|
|
2471
|
+
id: ref.id,
|
|
2472
|
+
x: ref.body.position.x,
|
|
2473
|
+
y: ref.body.position.y,
|
|
2474
|
+
angle: ref.body.angle,
|
|
2475
|
+
velocity: ref.body.velocity,
|
|
2476
|
+
body: ref.body,
|
|
2477
|
+
label: ref.label
|
|
2478
|
+
});
|
|
2479
|
+
});
|
|
2480
|
+
}
|
|
2481
|
+
/**
|
|
2482
|
+
* Register a collision callback.
|
|
2483
|
+
* @param event - Event type: 'collisionStart', 'collisionActive', 'collisionEnd'
|
|
2484
|
+
* @param callback - Callback receiving collision pairs
|
|
2485
|
+
*/
|
|
2486
|
+
static onCollision(event, callback) {
|
|
2487
|
+
this.ensureInit();
|
|
2488
|
+
this.Matter.Events.on(this.engine, event, (e) => {
|
|
2489
|
+
callback(e.pairs);
|
|
2490
|
+
});
|
|
2491
|
+
}
|
|
2492
|
+
/**
|
|
2493
|
+
* Get the raw Matter.js engine for advanced usage.
|
|
2494
|
+
*/
|
|
2495
|
+
static getEngine() {
|
|
2496
|
+
return this.engine;
|
|
2497
|
+
}
|
|
2498
|
+
/**
|
|
2499
|
+
* Get the raw Matter.js world for advanced usage.
|
|
2500
|
+
*/
|
|
2501
|
+
static getWorld() {
|
|
2502
|
+
return this.world;
|
|
2503
|
+
}
|
|
2504
|
+
/**
|
|
2505
|
+
* Clear all bodies, constraints, and reset the engine.
|
|
2506
|
+
*/
|
|
2507
|
+
static clear() {
|
|
2508
|
+
if (this.world) {
|
|
2509
|
+
this.Matter?.Composite.clear(this.world, false);
|
|
2510
|
+
}
|
|
2511
|
+
this.bodies.clear();
|
|
2512
|
+
this.constraints.clear();
|
|
2513
|
+
this.idCounter = 0;
|
|
2514
|
+
this.constraintIdCounter = 0;
|
|
2515
|
+
}
|
|
2516
|
+
/**
|
|
2517
|
+
* Destroy the engine entirely.
|
|
2518
|
+
*/
|
|
2519
|
+
static destroy() {
|
|
2520
|
+
this.clear();
|
|
2521
|
+
if (this.engine) {
|
|
2522
|
+
this.Matter?.Engine.clear(this.engine);
|
|
2523
|
+
}
|
|
2524
|
+
this.engine = null;
|
|
2525
|
+
this.world = null;
|
|
2526
|
+
this.Matter = null;
|
|
2527
|
+
this._loaded = false;
|
|
2528
|
+
}
|
|
2529
|
+
};
|
|
2530
|
+
_MatterPhysics.Matter = null;
|
|
2531
|
+
_MatterPhysics.engine = null;
|
|
2532
|
+
_MatterPhysics.world = null;
|
|
2533
|
+
_MatterPhysics.bodies = /* @__PURE__ */ new Map();
|
|
2534
|
+
_MatterPhysics.constraints = /* @__PURE__ */ new Map();
|
|
2535
|
+
_MatterPhysics.idCounter = 0;
|
|
2536
|
+
_MatterPhysics.constraintIdCounter = 0;
|
|
2537
|
+
_MatterPhysics._loaded = false;
|
|
2538
|
+
/** Matter.js version loaded from CDN when not installed locally */
|
|
2539
|
+
_MatterPhysics.MATTER_VERSION = "0.20.0";
|
|
2540
|
+
/** CDN URL template — version is interpolated */
|
|
2541
|
+
_MatterPhysics.MATTER_CDN = `https://cdnjs.cloudflare.com/ajax/libs/matter-js/${_MatterPhysics.MATTER_VERSION}/matter.min.js`;
|
|
2542
|
+
var MatterPhysics = _MatterPhysics;
|
|
2543
|
+
|
|
2544
|
+
// src/plugins/Projector.tsx
|
|
2545
|
+
var RAD_TO_DEG = 180 / Math.PI;
|
|
2546
|
+
var Projector = class {
|
|
2547
|
+
constructor(options) {
|
|
2548
|
+
this.perspective = options?.perspective ?? 2;
|
|
2549
|
+
this.radius = options?.radius ?? 1;
|
|
2550
|
+
}
|
|
2551
|
+
/**
|
|
2552
|
+
* Build a DOMMatrix from a transform callback.
|
|
2553
|
+
* The callback receives a Transform3D object — call rotateX, rotateY, etc.
|
|
2554
|
+
* in the order you want them applied.
|
|
2555
|
+
*/
|
|
2556
|
+
buildMatrix(transforms) {
|
|
2557
|
+
const m = new DOMMatrix();
|
|
2558
|
+
if (!transforms) return m;
|
|
2559
|
+
transforms({
|
|
2560
|
+
rotateX: (r) => m.rotateAxisAngleSelf(1, 0, 0, r * RAD_TO_DEG),
|
|
2561
|
+
rotateY: (r) => m.rotateAxisAngleSelf(0, 1, 0, r * RAD_TO_DEG),
|
|
2562
|
+
rotateZ: (r) => m.rotateAxisAngleSelf(0, 0, 1, r * RAD_TO_DEG),
|
|
2563
|
+
translate: (x, y, z) => m.translateSelf(x, y, z),
|
|
2564
|
+
scale: (x, y, z) => m.scaleSelf(x, y ?? x, z ?? x)
|
|
2565
|
+
});
|
|
2566
|
+
return m;
|
|
2567
|
+
}
|
|
2568
|
+
/**
|
|
2569
|
+
* Apply perspective projection to a transformed 3D point.
|
|
2570
|
+
* Returns 2D coordinates + depth + scale factor.
|
|
2571
|
+
*/
|
|
2572
|
+
projectPoint(m, point) {
|
|
2573
|
+
const p = m.transformPoint(new DOMPoint(point.x, point.y, point.z, 1));
|
|
2574
|
+
const d = this.perspective;
|
|
2575
|
+
const f = d === 0 ? 1 : 1 / (d - p.z);
|
|
2576
|
+
const r = this.radius;
|
|
2577
|
+
return {
|
|
2578
|
+
x: p.x * f * r,
|
|
2579
|
+
y: p.y * f * r,
|
|
2580
|
+
z: p.z,
|
|
2581
|
+
scale: Math.abs(f)
|
|
2582
|
+
};
|
|
2583
|
+
}
|
|
2584
|
+
/**
|
|
2585
|
+
* Project a single 3D point.
|
|
2586
|
+
*
|
|
2587
|
+
* @param point - The 3D point to project.
|
|
2588
|
+
* @param transforms - Optional callback to apply 3D transforms before projection.
|
|
2589
|
+
* @returns Projected 2D point with depth and scale info.
|
|
2590
|
+
*
|
|
2591
|
+
* @example
|
|
2592
|
+
* ```tsx
|
|
2593
|
+
* const p = projector.project({ x: 0, y: 1, z: 0 }, (t) => {
|
|
2594
|
+
* t.rotateY(angle);
|
|
2595
|
+
* });
|
|
2596
|
+
* K.circle(p.x, p.y, 8 * p.scale);
|
|
2597
|
+
* ```
|
|
2598
|
+
*/
|
|
2599
|
+
project(point, transforms) {
|
|
2600
|
+
return this.projectPoint(this.buildMatrix(transforms), point);
|
|
2601
|
+
}
|
|
2602
|
+
/**
|
|
2603
|
+
* Project an array of 3D points with the same transform.
|
|
2604
|
+
* The transform matrix is built once and reused for all points.
|
|
2605
|
+
*
|
|
2606
|
+
* @param points - Array of 3D points.
|
|
2607
|
+
* @param transforms - Optional transform callback (applied identically to all points).
|
|
2608
|
+
* @returns Array of projected points (same order as input).
|
|
2609
|
+
*
|
|
2610
|
+
* @example
|
|
2611
|
+
* ```tsx
|
|
2612
|
+
* const projected = projector.projectAll(cubeVertices, (t) => {
|
|
2613
|
+
* t.rotateX(K.time);
|
|
2614
|
+
* t.rotateY(K.time * 0.7);
|
|
2615
|
+
* });
|
|
2616
|
+
* for (const p of projected) {
|
|
2617
|
+
* K.circle(p.x, p.y, 6 * p.scale);
|
|
2618
|
+
* }
|
|
2619
|
+
* ```
|
|
2620
|
+
*/
|
|
2621
|
+
projectAll(points, transforms) {
|
|
2622
|
+
const m = this.buildMatrix(transforms);
|
|
2623
|
+
return points.map((pt) => this.projectPoint(m, pt));
|
|
2624
|
+
}
|
|
2625
|
+
/**
|
|
2626
|
+
* Project and depth-sort an array of 3D points (back-to-front).
|
|
2627
|
+
* Each result includes `index` — the original index in the input array,
|
|
2628
|
+
* so you can map back to colors, labels, etc.
|
|
2629
|
+
*
|
|
2630
|
+
* @param points - Array of 3D points.
|
|
2631
|
+
* @param transforms - Optional transform callback.
|
|
2632
|
+
* @returns Depth-sorted array of projected points with original indices.
|
|
2633
|
+
*
|
|
2634
|
+
* @example
|
|
2635
|
+
* ```tsx
|
|
2636
|
+
* const sorted = projector.projectSorted(points, (t) => {
|
|
2637
|
+
* t.rotateX(K.time);
|
|
2638
|
+
* });
|
|
2639
|
+
* for (const p of sorted) {
|
|
2640
|
+
* K.fillColor(colors[p.index]);
|
|
2641
|
+
* K.circle(p.x, p.y, 10 * p.scale);
|
|
2642
|
+
* }
|
|
2643
|
+
* ```
|
|
2644
|
+
*/
|
|
2645
|
+
projectSorted(points, transforms) {
|
|
2646
|
+
const m = this.buildMatrix(transforms);
|
|
2647
|
+
return points.map((pt, i) => ({ ...this.projectPoint(m, pt), index: i })).sort((a, b) => a.z - b.z);
|
|
2648
|
+
}
|
|
2649
|
+
};
|
|
2511
2650
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2512
2651
|
0 && (module.exports = {
|
|
2513
2652
|
CatmullRom,
|
|
2514
2653
|
Delaunay,
|
|
2515
2654
|
FontParser,
|
|
2516
|
-
|
|
2517
|
-
|
|
2655
|
+
MatterPhysics,
|
|
2656
|
+
Projector,
|
|
2657
|
+
Sprites
|
|
2518
2658
|
});
|