@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.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import "../chunk-3RG5ZIWI.js";
|
|
2
|
-
|
|
3
1
|
// src/plugins/FontParser.tsx
|
|
4
2
|
var BinaryReader = class {
|
|
5
3
|
constructor() {
|
|
@@ -21,14 +19,6 @@ var BinaryReader = class {
|
|
|
21
19
|
const num = this.readShort(data, o);
|
|
22
20
|
return num / 16384;
|
|
23
21
|
}
|
|
24
|
-
readInt(buff, p) {
|
|
25
|
-
const a = this.t.uint8;
|
|
26
|
-
a[0] = buff[p + 3];
|
|
27
|
-
a[1] = buff[p + 2];
|
|
28
|
-
a[2] = buff[p + 1];
|
|
29
|
-
a[3] = buff[p];
|
|
30
|
-
return this.t.int32[0];
|
|
31
|
-
}
|
|
32
22
|
readInt8(buff, p) {
|
|
33
23
|
const a = this.t.uint8;
|
|
34
24
|
a[0] = buff[p];
|
|
@@ -57,19 +47,11 @@ var BinaryReader = class {
|
|
|
57
47
|
a[0] = buff[p + 3];
|
|
58
48
|
return this.t.uint32[0];
|
|
59
49
|
}
|
|
60
|
-
readUint64(buff, p) {
|
|
61
|
-
return this.readUint(buff, p) * (4294967295 + 1) + this.readUint(buff, p + 4);
|
|
62
|
-
}
|
|
63
50
|
readASCII(buff, p, l) {
|
|
64
51
|
let s = "";
|
|
65
52
|
for (let i = 0; i < l; i++) s += String.fromCharCode(buff[p + i]);
|
|
66
53
|
return s;
|
|
67
54
|
}
|
|
68
|
-
readBytes(buff, p, l) {
|
|
69
|
-
const arr = [];
|
|
70
|
-
for (let i = 0; i < l; i++) arr.push(buff[p + i]);
|
|
71
|
-
return arr;
|
|
72
|
-
}
|
|
73
55
|
};
|
|
74
56
|
var bin = new BinaryReader();
|
|
75
57
|
function findTable(data, tab, foff = 0) {
|
|
@@ -77,7 +59,6 @@ function findTable(data, tab, foff = 0) {
|
|
|
77
59
|
let offset = foff + 12;
|
|
78
60
|
for (let i = 0; i < numTables; i++) {
|
|
79
61
|
const tag = bin.readASCII(data, offset, 4);
|
|
80
|
-
const checkSum = bin.readUint(data, offset + 4);
|
|
81
62
|
const toffset = bin.readUint(data, offset + 8);
|
|
82
63
|
const length = bin.readUint(data, offset + 12);
|
|
83
64
|
if (tag === tab) return [toffset, length];
|
|
@@ -88,105 +69,48 @@ function findTable(data, tab, foff = 0) {
|
|
|
88
69
|
var Tables = {
|
|
89
70
|
head: {
|
|
90
71
|
parseTab(data, offset, length) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
obj.flags = bin.readUshort(data, offset);
|
|
101
|
-
offset += 2;
|
|
102
|
-
obj.unitsPerEm = bin.readUshort(data, offset);
|
|
103
|
-
offset += 2;
|
|
104
|
-
obj.created = bin.readUint64(data, offset);
|
|
105
|
-
offset += 8;
|
|
106
|
-
obj.modified = bin.readUint64(data, offset);
|
|
107
|
-
offset += 8;
|
|
108
|
-
obj.xMin = bin.readShort(data, offset);
|
|
109
|
-
offset += 2;
|
|
110
|
-
obj.yMin = bin.readShort(data, offset);
|
|
111
|
-
offset += 2;
|
|
112
|
-
obj.xMax = bin.readShort(data, offset);
|
|
113
|
-
offset += 2;
|
|
114
|
-
obj.yMax = bin.readShort(data, offset);
|
|
115
|
-
offset += 2;
|
|
116
|
-
obj.macStyle = bin.readUshort(data, offset);
|
|
117
|
-
offset += 2;
|
|
118
|
-
obj.lowestRecPPEM = bin.readUshort(data, offset);
|
|
119
|
-
offset += 2;
|
|
120
|
-
obj.fontDirectionHint = bin.readShort(data, offset);
|
|
121
|
-
offset += 2;
|
|
122
|
-
obj.indexToLocFormat = bin.readShort(data, offset);
|
|
123
|
-
offset += 2;
|
|
124
|
-
obj.glyphDataFormat = bin.readShort(data, offset);
|
|
125
|
-
offset += 2;
|
|
126
|
-
return obj;
|
|
72
|
+
return {
|
|
73
|
+
flags: bin.readUshort(data, offset + 16),
|
|
74
|
+
unitsPerEm: bin.readUshort(data, offset + 18),
|
|
75
|
+
xMin: bin.readShort(data, offset + 36),
|
|
76
|
+
yMin: bin.readShort(data, offset + 38),
|
|
77
|
+
xMax: bin.readShort(data, offset + 40),
|
|
78
|
+
yMax: bin.readShort(data, offset + 42),
|
|
79
|
+
indexToLocFormat: bin.readShort(data, offset + 50)
|
|
80
|
+
};
|
|
127
81
|
}
|
|
128
82
|
},
|
|
129
83
|
maxp: {
|
|
130
84
|
parseTab(data, offset, length) {
|
|
131
|
-
|
|
132
|
-
const ver = bin.readUint(data, offset);
|
|
133
|
-
offset += 4;
|
|
134
|
-
obj.numGlyphs = bin.readUshort(data, offset);
|
|
135
|
-
offset += 2;
|
|
136
|
-
return obj;
|
|
85
|
+
return { numGlyphs: bin.readUshort(data, offset + 4) };
|
|
137
86
|
}
|
|
138
87
|
},
|
|
139
88
|
hhea: {
|
|
140
89
|
parseTab(data, offset, length) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
"lineGap",
|
|
148
|
-
"advanceWidthMax",
|
|
149
|
-
"minLeftSideBearing",
|
|
150
|
-
"minRightSideBearing",
|
|
151
|
-
"xMaxExtent",
|
|
152
|
-
"caretSlopeRise",
|
|
153
|
-
"caretSlopeRun",
|
|
154
|
-
"caretOffset",
|
|
155
|
-
"res0",
|
|
156
|
-
"res1",
|
|
157
|
-
"res2",
|
|
158
|
-
"res3",
|
|
159
|
-
"metricDataFormat",
|
|
160
|
-
"numberOfHMetrics"
|
|
161
|
-
];
|
|
162
|
-
for (let i = 0; i < keys.length; i++) {
|
|
163
|
-
const key = keys[i];
|
|
164
|
-
const func = key === "advanceWidthMax" || key === "numberOfHMetrics" ? bin.readUshort : bin.readShort;
|
|
165
|
-
obj[key] = func.call(bin, data, offset + i * 2);
|
|
166
|
-
}
|
|
167
|
-
return obj;
|
|
90
|
+
return {
|
|
91
|
+
ascender: bin.readShort(data, offset + 4),
|
|
92
|
+
descender: bin.readShort(data, offset + 6),
|
|
93
|
+
lineGap: bin.readShort(data, offset + 8),
|
|
94
|
+
numberOfHMetrics: bin.readUshort(data, offset + 34)
|
|
95
|
+
};
|
|
168
96
|
}
|
|
169
97
|
},
|
|
170
98
|
hmtx: {
|
|
171
99
|
parseTab(data, offset, length, font) {
|
|
172
100
|
const aWidth = [];
|
|
173
|
-
const lsBearing = [];
|
|
174
101
|
const nG = font.maxp.numGlyphs;
|
|
175
102
|
const nH = font.hhea.numberOfHMetrics;
|
|
176
|
-
let aw = 0,
|
|
103
|
+
let aw = 0, i = 0;
|
|
177
104
|
while (i < nH) {
|
|
178
105
|
aw = bin.readUshort(data, offset + (i << 2));
|
|
179
|
-
lsb = bin.readShort(data, offset + (i << 2) + 2);
|
|
180
106
|
aWidth.push(aw);
|
|
181
|
-
lsBearing.push(lsb);
|
|
182
107
|
i++;
|
|
183
108
|
}
|
|
184
109
|
while (i < nG) {
|
|
185
110
|
aWidth.push(aw);
|
|
186
|
-
lsBearing.push(lsb);
|
|
187
111
|
i++;
|
|
188
112
|
}
|
|
189
|
-
return { aWidth
|
|
113
|
+
return { aWidth };
|
|
190
114
|
}
|
|
191
115
|
},
|
|
192
116
|
loca: {
|
|
@@ -246,9 +170,6 @@ var Tables = {
|
|
|
246
170
|
readFormat0(data, offset, map) {
|
|
247
171
|
let pleft = -1;
|
|
248
172
|
const nPairs = bin.readUshort(data, offset);
|
|
249
|
-
const searchRange = bin.readUshort(data, offset + 2);
|
|
250
|
-
const entrySelector = bin.readUshort(data, offset + 4);
|
|
251
|
-
const rangeShift = bin.readUshort(data, offset + 6);
|
|
252
173
|
offset += 8;
|
|
253
174
|
for (let j = 0; j < nPairs; j++) {
|
|
254
175
|
const left = bin.readUshort(data, offset);
|
|
@@ -305,25 +226,16 @@ var cmap = {
|
|
|
305
226
|
return obj;
|
|
306
227
|
},
|
|
307
228
|
parse0(data, offset, obj) {
|
|
308
|
-
|
|
309
|
-
const format = bin.readUshort(data, offset);
|
|
310
|
-
offset += 2;
|
|
311
|
-
const length = bin.readUshort(data, offset);
|
|
312
|
-
offset += 2;
|
|
313
|
-
const language = bin.readUshort(data, offset);
|
|
314
|
-
offset += 2;
|
|
229
|
+
offset += 6;
|
|
315
230
|
obj.map = [];
|
|
316
231
|
for (let i = 0; i < 256; i++) obj.map.push(data[offset + i]);
|
|
317
232
|
return obj;
|
|
318
233
|
},
|
|
319
234
|
parse4(data, offset, obj) {
|
|
320
235
|
const startOffset = offset;
|
|
321
|
-
const format = bin.readUshort(data, offset);
|
|
322
236
|
offset += 2;
|
|
323
237
|
const length = bin.readUshort(data, offset);
|
|
324
|
-
offset +=
|
|
325
|
-
const language = bin.readUshort(data, offset);
|
|
326
|
-
offset += 2;
|
|
238
|
+
offset += 4;
|
|
327
239
|
const segCountX2 = bin.readUshort(data, offset);
|
|
328
240
|
offset += 2;
|
|
329
241
|
const segCount = segCountX2 >>> 1;
|
|
@@ -353,13 +265,7 @@ var cmap = {
|
|
|
353
265
|
return obj;
|
|
354
266
|
},
|
|
355
267
|
parse6(data, offset, obj) {
|
|
356
|
-
|
|
357
|
-
const format = bin.readUshort(data, offset);
|
|
358
|
-
offset += 2;
|
|
359
|
-
const length = bin.readUshort(data, offset);
|
|
360
|
-
offset += 2;
|
|
361
|
-
const language = bin.readUshort(data, offset);
|
|
362
|
-
offset += 2;
|
|
268
|
+
offset += 6;
|
|
363
269
|
obj.firstCode = bin.readUshort(data, offset);
|
|
364
270
|
offset += 2;
|
|
365
271
|
obj.entryCount = bin.readUshort(data, offset);
|
|
@@ -368,12 +274,7 @@ var cmap = {
|
|
|
368
274
|
return obj;
|
|
369
275
|
},
|
|
370
276
|
parse12(data, offset, obj) {
|
|
371
|
-
|
|
372
|
-
offset += 4;
|
|
373
|
-
const length = bin.readUint(data, offset);
|
|
374
|
-
offset += 4;
|
|
375
|
-
const language = bin.readUint(data, offset);
|
|
376
|
-
offset += 4;
|
|
277
|
+
offset += 12;
|
|
377
278
|
const nGroups = bin.readUint(data, offset) * 3;
|
|
378
279
|
offset += 4;
|
|
379
280
|
obj.groups = new Uint32Array(nGroups);
|
|
@@ -417,8 +318,7 @@ var glyf = {
|
|
|
417
318
|
}
|
|
418
319
|
const instructionLength = bin.readUshort(data, off);
|
|
419
320
|
off += 2;
|
|
420
|
-
if (
|
|
421
|
-
gl.instructions = bin.readBytes(data, off, instructionLength);
|
|
321
|
+
if (off + instructionLength > data.length) return null;
|
|
422
322
|
off += instructionLength;
|
|
423
323
|
const crdnum = gl.endPts[gl.noc - 1] + 1;
|
|
424
324
|
gl.flags = [];
|
|
@@ -1563,54 +1463,26 @@ var FontParser = class {
|
|
|
1563
1463
|
},
|
|
1564
1464
|
samplePathPoints(glyphPath, sampling) {
|
|
1565
1465
|
const rawContours = this.parseContours(glyphPath);
|
|
1566
|
-
const
|
|
1567
|
-
(contour
|
|
1568
|
-
contour,
|
|
1569
|
-
originalIndex: index,
|
|
1570
|
-
length: this.calculateContourLength(contour)
|
|
1571
|
-
})
|
|
1466
|
+
const prepared = rawContours.map(
|
|
1467
|
+
(contour) => this.buildSegments(contour)
|
|
1572
1468
|
);
|
|
1573
|
-
|
|
1469
|
+
prepared.sort((a, b) => b.totalLength - a.totalLength);
|
|
1574
1470
|
const allPoints = [];
|
|
1575
|
-
for (let i = 0; i <
|
|
1576
|
-
const {
|
|
1577
|
-
const
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
for (const seg of contour.segments) {
|
|
1588
|
-
if (seg.cmd === "L") {
|
|
1589
|
-
const endX = seg.coords[0];
|
|
1590
|
-
const endY = seg.coords[1];
|
|
1591
|
-
totalLength += Math.sqrt(
|
|
1592
|
-
(endX - currentX) ** 2 + (endY - currentY) ** 2
|
|
1593
|
-
);
|
|
1594
|
-
currentX = endX;
|
|
1595
|
-
currentY = endY;
|
|
1596
|
-
} else if (seg.cmd === "Q") {
|
|
1597
|
-
const controlX = seg.coords[0];
|
|
1598
|
-
const controlY = seg.coords[1];
|
|
1599
|
-
const endX = seg.coords[2];
|
|
1600
|
-
const endY = seg.coords[3];
|
|
1601
|
-
totalLength += this.approximateQuadraticLength(
|
|
1602
|
-
currentX,
|
|
1603
|
-
currentY,
|
|
1604
|
-
controlX,
|
|
1605
|
-
controlY,
|
|
1606
|
-
endX,
|
|
1607
|
-
endY
|
|
1608
|
-
);
|
|
1609
|
-
currentX = endX;
|
|
1610
|
-
currentY = endY;
|
|
1471
|
+
for (let i = 0; i < prepared.length; i++) {
|
|
1472
|
+
const { segments, totalLength } = prepared[i];
|
|
1473
|
+
const targetPoints = Math.max(
|
|
1474
|
+
5,
|
|
1475
|
+
Math.floor(totalLength * sampling * 0.1)
|
|
1476
|
+
);
|
|
1477
|
+
for (let j = 0; j <= targetPoints; j++) {
|
|
1478
|
+
const targetLength = j / targetPoints * totalLength;
|
|
1479
|
+
const pt = this.getPointAtLengthInContour(segments, targetLength);
|
|
1480
|
+
if (pt) {
|
|
1481
|
+
allPoints.push({ x: pt.x, y: pt.y, contour: i });
|
|
1482
|
+
}
|
|
1611
1483
|
}
|
|
1612
1484
|
}
|
|
1613
|
-
return
|
|
1485
|
+
return allPoints;
|
|
1614
1486
|
},
|
|
1615
1487
|
// Parse glyph path into separate contours
|
|
1616
1488
|
parseContours(glyphPath) {
|
|
@@ -1652,8 +1524,8 @@ var FontParser = class {
|
|
|
1652
1524
|
}
|
|
1653
1525
|
return contours;
|
|
1654
1526
|
},
|
|
1655
|
-
//
|
|
1656
|
-
|
|
1527
|
+
// Build segments from a contour with lengths pre-calculated
|
|
1528
|
+
buildSegments(contour) {
|
|
1657
1529
|
const segments = [];
|
|
1658
1530
|
let currentX = contour.startX;
|
|
1659
1531
|
let currentY = contour.startY;
|
|
@@ -1662,9 +1534,7 @@ var FontParser = class {
|
|
|
1662
1534
|
if (seg.cmd === "L") {
|
|
1663
1535
|
const endX = seg.coords[0];
|
|
1664
1536
|
const endY = seg.coords[1];
|
|
1665
|
-
const length = Math.
|
|
1666
|
-
(endX - currentX) ** 2 + (endY - currentY) ** 2
|
|
1667
|
-
);
|
|
1537
|
+
const length = Math.hypot(endX - currentX, endY - currentY);
|
|
1668
1538
|
segments.push({
|
|
1669
1539
|
type: "L",
|
|
1670
1540
|
startX: currentX,
|
|
@@ -1682,14 +1552,9 @@ var FontParser = class {
|
|
|
1682
1552
|
const controlY = seg.coords[1];
|
|
1683
1553
|
const endX = seg.coords[2];
|
|
1684
1554
|
const endY = seg.coords[3];
|
|
1685
|
-
const
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
controlX,
|
|
1689
|
-
controlY,
|
|
1690
|
-
endX,
|
|
1691
|
-
endY
|
|
1692
|
-
);
|
|
1555
|
+
const chord = Math.hypot(endX - currentX, endY - currentY);
|
|
1556
|
+
const ctrl = Math.hypot(controlX - currentX, controlY - currentY) + Math.hypot(endX - controlX, endY - controlY);
|
|
1557
|
+
const length = (chord + ctrl) / 2;
|
|
1693
1558
|
segments.push({
|
|
1694
1559
|
type: "Q",
|
|
1695
1560
|
startX: currentX,
|
|
@@ -1706,26 +1571,7 @@ var FontParser = class {
|
|
|
1706
1571
|
currentY = endY;
|
|
1707
1572
|
}
|
|
1708
1573
|
}
|
|
1709
|
-
|
|
1710
|
-
const targetPoints = Math.max(
|
|
1711
|
-
5,
|
|
1712
|
-
Math.floor(totalLength * sampling * 0.1)
|
|
1713
|
-
);
|
|
1714
|
-
for (let i = 0; i <= targetPoints; i++) {
|
|
1715
|
-
const targetLength = i / targetPoints * totalLength;
|
|
1716
|
-
const pointData = this.getPointAtLengthInContour(
|
|
1717
|
-
segments,
|
|
1718
|
-
targetLength
|
|
1719
|
-
);
|
|
1720
|
-
if (pointData) {
|
|
1721
|
-
points.push({
|
|
1722
|
-
x: pointData.x,
|
|
1723
|
-
y: pointData.y,
|
|
1724
|
-
contour: contourIndex
|
|
1725
|
-
});
|
|
1726
|
-
}
|
|
1727
|
-
}
|
|
1728
|
-
return points;
|
|
1574
|
+
return { segments, totalLength };
|
|
1729
1575
|
},
|
|
1730
1576
|
// Get point at length within a single contour
|
|
1731
1577
|
getPointAtLengthInContour(segments, targetLength) {
|
|
@@ -1747,12 +1593,6 @@ var FontParser = class {
|
|
|
1747
1593
|
}
|
|
1748
1594
|
}
|
|
1749
1595
|
return null;
|
|
1750
|
-
},
|
|
1751
|
-
// Helper function to approximate quadratic curve length
|
|
1752
|
-
approximateQuadraticLength(x0, y0, x1, y1, x2, y2) {
|
|
1753
|
-
const chordLength = Math.sqrt((x2 - x0) ** 2 + (y2 - y0) ** 2);
|
|
1754
|
-
const controlLength = Math.sqrt((x1 - x0) ** 2 + (y1 - y0) ** 2) + Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
|
|
1755
|
-
return (chordLength + controlLength) / 2;
|
|
1756
1596
|
}
|
|
1757
1597
|
};
|
|
1758
1598
|
}
|
|
@@ -1920,264 +1760,6 @@ var CatmullRom = class {
|
|
|
1920
1760
|
}
|
|
1921
1761
|
};
|
|
1922
1762
|
|
|
1923
|
-
// src/plugins/Things.tsx
|
|
1924
|
-
var Things = class {
|
|
1925
|
-
/**
|
|
1926
|
-
* Configure the plugin
|
|
1927
|
-
*/
|
|
1928
|
-
static configure(config) {
|
|
1929
|
-
this.config = { ...this.config, ...config };
|
|
1930
|
-
}
|
|
1931
|
-
/**
|
|
1932
|
-
* Create a new thing
|
|
1933
|
-
*/
|
|
1934
|
-
static create(options = {}) {
|
|
1935
|
-
if (this.things.size >= (this.config.maxThings || 1e3)) {
|
|
1936
|
-
throw new Error(
|
|
1937
|
-
`Maximum number of things (${this.config.maxThings}) reached`
|
|
1938
|
-
);
|
|
1939
|
-
}
|
|
1940
|
-
const thing = {
|
|
1941
|
-
id: options.id || `thing_${this.idCounter++}`,
|
|
1942
|
-
x: options.x || 0,
|
|
1943
|
-
y: options.y || 0,
|
|
1944
|
-
width: options.width || this.config.defaultSize || 50,
|
|
1945
|
-
height: options.height || this.config.defaultSize || 50,
|
|
1946
|
-
rotation: options.rotation || 0,
|
|
1947
|
-
scale: options.scale || 1,
|
|
1948
|
-
color: options.color || this.config.defaultColor || "#ffffff",
|
|
1949
|
-
data: options.data || {}
|
|
1950
|
-
};
|
|
1951
|
-
this.things.set(thing.id, thing);
|
|
1952
|
-
return thing;
|
|
1953
|
-
}
|
|
1954
|
-
/**
|
|
1955
|
-
* Get a thing by ID
|
|
1956
|
-
*/
|
|
1957
|
-
static get(id) {
|
|
1958
|
-
return this.things.get(id);
|
|
1959
|
-
}
|
|
1960
|
-
/**
|
|
1961
|
-
* Get all things
|
|
1962
|
-
*/
|
|
1963
|
-
static getAll() {
|
|
1964
|
-
return Array.from(this.things.values());
|
|
1965
|
-
}
|
|
1966
|
-
/**
|
|
1967
|
-
* Update a thing's properties
|
|
1968
|
-
*/
|
|
1969
|
-
static update(id, updates) {
|
|
1970
|
-
const thing = this.things.get(id);
|
|
1971
|
-
if (thing) {
|
|
1972
|
-
Object.assign(thing, updates);
|
|
1973
|
-
}
|
|
1974
|
-
}
|
|
1975
|
-
/**
|
|
1976
|
-
* Remove a thing
|
|
1977
|
-
*/
|
|
1978
|
-
static remove(id) {
|
|
1979
|
-
return this.things.delete(id);
|
|
1980
|
-
}
|
|
1981
|
-
/**
|
|
1982
|
-
* Clear all things
|
|
1983
|
-
*/
|
|
1984
|
-
static clear() {
|
|
1985
|
-
this.things.clear();
|
|
1986
|
-
this.idCounter = 0;
|
|
1987
|
-
}
|
|
1988
|
-
/**
|
|
1989
|
-
* Move a thing
|
|
1990
|
-
*/
|
|
1991
|
-
static move(id, dx, dy) {
|
|
1992
|
-
const thing = this.things.get(id);
|
|
1993
|
-
if (thing) {
|
|
1994
|
-
thing.x += dx;
|
|
1995
|
-
thing.y += dy;
|
|
1996
|
-
}
|
|
1997
|
-
}
|
|
1998
|
-
/**
|
|
1999
|
-
* Rotate a thing
|
|
2000
|
-
*/
|
|
2001
|
-
static rotate(id, angle) {
|
|
2002
|
-
const thing = this.things.get(id);
|
|
2003
|
-
if (thing) {
|
|
2004
|
-
thing.rotation += angle;
|
|
2005
|
-
}
|
|
2006
|
-
}
|
|
2007
|
-
/**
|
|
2008
|
-
* Scale a thing
|
|
2009
|
-
*/
|
|
2010
|
-
static scale(id, factor) {
|
|
2011
|
-
const thing = this.things.get(id);
|
|
2012
|
-
if (thing) {
|
|
2013
|
-
thing.scale *= factor;
|
|
2014
|
-
}
|
|
2015
|
-
}
|
|
2016
|
-
/**
|
|
2017
|
-
* Find things within a radius
|
|
2018
|
-
*/
|
|
2019
|
-
static findNear(x, y, radius) {
|
|
2020
|
-
const near = [];
|
|
2021
|
-
const radiusSq = radius * radius;
|
|
2022
|
-
this.things.forEach((thing) => {
|
|
2023
|
-
const dx = thing.x - x;
|
|
2024
|
-
const dy = thing.y - y;
|
|
2025
|
-
if (dx * dx + dy * dy <= radiusSq) {
|
|
2026
|
-
near.push(thing);
|
|
2027
|
-
}
|
|
2028
|
-
});
|
|
2029
|
-
return near;
|
|
2030
|
-
}
|
|
2031
|
-
/**
|
|
2032
|
-
* Find things that overlap with a rectangle
|
|
2033
|
-
*/
|
|
2034
|
-
static findInRect(x, y, width, height) {
|
|
2035
|
-
const found = [];
|
|
2036
|
-
this.things.forEach((thing) => {
|
|
2037
|
-
const halfW = thing.width * thing.scale / 2;
|
|
2038
|
-
const halfH = thing.height * thing.scale / 2;
|
|
2039
|
-
if (thing.x + halfW >= x && thing.x - halfW <= x + width && thing.y + halfH >= y && thing.y - halfH <= y + height) {
|
|
2040
|
-
found.push(thing);
|
|
2041
|
-
}
|
|
2042
|
-
});
|
|
2043
|
-
return found;
|
|
2044
|
-
}
|
|
2045
|
-
/**
|
|
2046
|
-
* Apply a function to all things
|
|
2047
|
-
*/
|
|
2048
|
-
static forEach(fn) {
|
|
2049
|
-
this.things.forEach(fn);
|
|
2050
|
-
}
|
|
2051
|
-
/**
|
|
2052
|
-
* Map things to a new array
|
|
2053
|
-
*/
|
|
2054
|
-
static map(fn) {
|
|
2055
|
-
return Array.from(this.things.values()).map(fn);
|
|
2056
|
-
}
|
|
2057
|
-
/**
|
|
2058
|
-
* Filter things
|
|
2059
|
-
*/
|
|
2060
|
-
static filter(fn) {
|
|
2061
|
-
return Array.from(this.things.values()).filter(fn);
|
|
2062
|
-
}
|
|
2063
|
-
/**
|
|
2064
|
-
* Sort things by a property or function
|
|
2065
|
-
*/
|
|
2066
|
-
static sort(fn) {
|
|
2067
|
-
return Array.from(this.things.values()).sort(fn);
|
|
2068
|
-
}
|
|
2069
|
-
/**
|
|
2070
|
-
* Draw all things
|
|
2071
|
-
*/
|
|
2072
|
-
static draw(ctx, options) {
|
|
2073
|
-
const thingsToDraw = options?.filter ? this.filter(options.filter) : this.getAll();
|
|
2074
|
-
thingsToDraw.forEach((thing) => {
|
|
2075
|
-
if (options?.customDraw) {
|
|
2076
|
-
options.customDraw(ctx, thing);
|
|
2077
|
-
} else {
|
|
2078
|
-
this.drawThing(ctx, thing);
|
|
2079
|
-
}
|
|
2080
|
-
});
|
|
2081
|
-
}
|
|
2082
|
-
/**
|
|
2083
|
-
* Default drawing method for a thing
|
|
2084
|
-
*/
|
|
2085
|
-
static drawThing(ctx, thing) {
|
|
2086
|
-
ctx.save();
|
|
2087
|
-
ctx.translate(thing.x, thing.y);
|
|
2088
|
-
ctx.rotate(thing.rotation);
|
|
2089
|
-
ctx.scale(thing.scale, thing.scale);
|
|
2090
|
-
ctx.fillStyle = thing.color;
|
|
2091
|
-
ctx.fillRect(
|
|
2092
|
-
-thing.width / 2,
|
|
2093
|
-
-thing.height / 2,
|
|
2094
|
-
thing.width,
|
|
2095
|
-
thing.height
|
|
2096
|
-
);
|
|
2097
|
-
ctx.restore();
|
|
2098
|
-
}
|
|
2099
|
-
/**
|
|
2100
|
-
* Animate things with a simple physics update
|
|
2101
|
-
*/
|
|
2102
|
-
static animatePhysics(deltaTime, options) {
|
|
2103
|
-
const gravity = options?.gravity || 0;
|
|
2104
|
-
const friction = options?.friction || 0.99;
|
|
2105
|
-
const bounds = options?.bounds;
|
|
2106
|
-
this.things.forEach((thing) => {
|
|
2107
|
-
if (!thing.data.vx) thing.data.vx = 0;
|
|
2108
|
-
if (!thing.data.vy) thing.data.vy = 0;
|
|
2109
|
-
thing.data.vy += gravity;
|
|
2110
|
-
thing.data.vx *= friction;
|
|
2111
|
-
thing.data.vy *= friction;
|
|
2112
|
-
thing.x += thing.data.vx;
|
|
2113
|
-
thing.y += thing.data.vy;
|
|
2114
|
-
if (bounds) {
|
|
2115
|
-
const halfW = thing.width * thing.scale / 2;
|
|
2116
|
-
const halfH = thing.height * thing.scale / 2;
|
|
2117
|
-
if (thing.x - halfW < bounds.x) {
|
|
2118
|
-
thing.x = bounds.x + halfW;
|
|
2119
|
-
thing.data.vx *= -0.8;
|
|
2120
|
-
}
|
|
2121
|
-
if (thing.x + halfW > bounds.x + bounds.width) {
|
|
2122
|
-
thing.x = bounds.x + bounds.width - halfW;
|
|
2123
|
-
thing.data.vx *= -0.8;
|
|
2124
|
-
}
|
|
2125
|
-
if (thing.y - halfH < bounds.y) {
|
|
2126
|
-
thing.y = bounds.y + halfH;
|
|
2127
|
-
thing.data.vy *= -0.8;
|
|
2128
|
-
}
|
|
2129
|
-
if (thing.y + halfH > bounds.y + bounds.height) {
|
|
2130
|
-
thing.y = bounds.y + bounds.height - halfH;
|
|
2131
|
-
thing.data.vy *= -0.8;
|
|
2132
|
-
}
|
|
2133
|
-
}
|
|
2134
|
-
});
|
|
2135
|
-
}
|
|
2136
|
-
/**
|
|
2137
|
-
* Get the count of things
|
|
2138
|
-
*/
|
|
2139
|
-
static count() {
|
|
2140
|
-
return this.things.size;
|
|
2141
|
-
}
|
|
2142
|
-
/**
|
|
2143
|
-
* Check if a thing exists
|
|
2144
|
-
*/
|
|
2145
|
-
static has(id) {
|
|
2146
|
-
return this.things.has(id);
|
|
2147
|
-
}
|
|
2148
|
-
/**
|
|
2149
|
-
* Utility: Get distance between two things
|
|
2150
|
-
*/
|
|
2151
|
-
static distance(id1, id2) {
|
|
2152
|
-
const thing1 = this.things.get(id1);
|
|
2153
|
-
const thing2 = this.things.get(id2);
|
|
2154
|
-
if (!thing1 || !thing2) return Infinity;
|
|
2155
|
-
const dx = thing2.x - thing1.x;
|
|
2156
|
-
const dy = thing2.y - thing1.y;
|
|
2157
|
-
return Math.sqrt(dx * dx + dy * dy);
|
|
2158
|
-
}
|
|
2159
|
-
/**
|
|
2160
|
-
* Utility: Check collision between two things
|
|
2161
|
-
*/
|
|
2162
|
-
static collides(id1, id2) {
|
|
2163
|
-
const thing1 = this.things.get(id1);
|
|
2164
|
-
const thing2 = this.things.get(id2);
|
|
2165
|
-
if (!thing1 || !thing2) return false;
|
|
2166
|
-
const halfW1 = thing1.width * thing1.scale / 2;
|
|
2167
|
-
const halfH1 = thing1.height * thing1.scale / 2;
|
|
2168
|
-
const halfW2 = thing2.width * thing2.scale / 2;
|
|
2169
|
-
const halfH2 = thing2.height * thing2.scale / 2;
|
|
2170
|
-
return Math.abs(thing1.x - thing2.x) < halfW1 + halfW2 && Math.abs(thing1.y - thing2.y) < halfH1 + halfH2;
|
|
2171
|
-
}
|
|
2172
|
-
};
|
|
2173
|
-
Things.things = /* @__PURE__ */ new Map();
|
|
2174
|
-
Things.config = {
|
|
2175
|
-
maxThings: 1e3,
|
|
2176
|
-
defaultSize: 50,
|
|
2177
|
-
defaultColor: "#ffffff"
|
|
2178
|
-
};
|
|
2179
|
-
Things.idCounter = 0;
|
|
2180
|
-
|
|
2181
1763
|
// src/plugins/Sprites.tsx
|
|
2182
1764
|
var Sprites = class {
|
|
2183
1765
|
/**
|
|
@@ -2480,10 +2062,555 @@ var SpriteAnimation = class {
|
|
|
2480
2062
|
return this.playing;
|
|
2481
2063
|
}
|
|
2482
2064
|
};
|
|
2065
|
+
|
|
2066
|
+
// src/plugins/MatterPhysics.tsx
|
|
2067
|
+
var _MatterPhysics = class _MatterPhysics {
|
|
2068
|
+
/**
|
|
2069
|
+
* Dynamically load matter-js and initialize the engine.
|
|
2070
|
+
* Tries a local `import("matter-js")` first — if that fails (package not installed),
|
|
2071
|
+
* falls back to loading from CDN via a script tag.
|
|
2072
|
+
* @param config - Engine configuration
|
|
2073
|
+
* @returns Promise that resolves when ready
|
|
2074
|
+
*
|
|
2075
|
+
* @example
|
|
2076
|
+
* ```tsx
|
|
2077
|
+
* // In preload or setup:
|
|
2078
|
+
* await MatterPhysics.load({ gravity: { x: 0, y: 1 } });
|
|
2079
|
+
* ```
|
|
2080
|
+
*/
|
|
2081
|
+
static async load(config = {}) {
|
|
2082
|
+
if (this._loaded && this.engine) return;
|
|
2083
|
+
try {
|
|
2084
|
+
const matterModule = await import("matter-js");
|
|
2085
|
+
const Matter = matterModule.default || matterModule;
|
|
2086
|
+
this.init(Matter, config);
|
|
2087
|
+
this._loaded = true;
|
|
2088
|
+
return;
|
|
2089
|
+
} catch {
|
|
2090
|
+
}
|
|
2091
|
+
try {
|
|
2092
|
+
const Matter = await this._loadFromCDN();
|
|
2093
|
+
this.init(Matter, config);
|
|
2094
|
+
this._loaded = true;
|
|
2095
|
+
} catch (e) {
|
|
2096
|
+
throw new Error(
|
|
2097
|
+
`MatterPhysics: failed to load matter-js. Install it with "npm install matter-js" or check your network connection for CDN fallback (${this.MATTER_CDN}).`
|
|
2098
|
+
);
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
/**
|
|
2102
|
+
* Load Matter.js from CDN by injecting a script tag.
|
|
2103
|
+
* Resolves with the global `Matter` object.
|
|
2104
|
+
*/
|
|
2105
|
+
static _loadFromCDN() {
|
|
2106
|
+
return new Promise((resolve, reject) => {
|
|
2107
|
+
if (typeof globalThis.Matter !== "undefined") {
|
|
2108
|
+
resolve(globalThis.Matter);
|
|
2109
|
+
return;
|
|
2110
|
+
}
|
|
2111
|
+
const script = document.createElement("script");
|
|
2112
|
+
script.src = this.MATTER_CDN;
|
|
2113
|
+
script.async = true;
|
|
2114
|
+
script.onload = () => {
|
|
2115
|
+
if (typeof globalThis.Matter !== "undefined") {
|
|
2116
|
+
resolve(globalThis.Matter);
|
|
2117
|
+
} else {
|
|
2118
|
+
reject(new Error("Matter global not found after script load"));
|
|
2119
|
+
}
|
|
2120
|
+
};
|
|
2121
|
+
script.onerror = () => {
|
|
2122
|
+
reject(new Error(`Failed to load script from ${this.MATTER_CDN}`));
|
|
2123
|
+
};
|
|
2124
|
+
document.head.appendChild(script);
|
|
2125
|
+
});
|
|
2126
|
+
}
|
|
2127
|
+
/**
|
|
2128
|
+
* Check if MatterPhysics has been loaded and initialized.
|
|
2129
|
+
*/
|
|
2130
|
+
static get isLoaded() {
|
|
2131
|
+
return this._loaded && this.engine !== null;
|
|
2132
|
+
}
|
|
2133
|
+
/**
|
|
2134
|
+
* Initialize the physics engine with a Matter.js module reference.
|
|
2135
|
+
* Prefer `load()` for dynamic import. Use this if you want to pass the module directly.
|
|
2136
|
+
* @param matterModule - The Matter.js module (import Matter from 'matter-js')
|
|
2137
|
+
* @param config - Engine configuration
|
|
2138
|
+
*/
|
|
2139
|
+
static init(matterModule, config = {}) {
|
|
2140
|
+
this.Matter = matterModule;
|
|
2141
|
+
this._loaded = true;
|
|
2142
|
+
this.engine = matterModule.Engine.create({
|
|
2143
|
+
enableSleeping: config.enableSleeping ?? false,
|
|
2144
|
+
constraintIterations: config.constraintIterations ?? 2,
|
|
2145
|
+
positionIterations: config.positionIterations ?? 6,
|
|
2146
|
+
velocityIterations: config.velocityIterations ?? 4
|
|
2147
|
+
});
|
|
2148
|
+
this.world = this.engine.world;
|
|
2149
|
+
if (config.gravity) {
|
|
2150
|
+
this.world.gravity.x = config.gravity.x;
|
|
2151
|
+
this.world.gravity.y = config.gravity.y;
|
|
2152
|
+
if (config.gravity.scale !== void 0) {
|
|
2153
|
+
this.world.gravity.scale = config.gravity.scale;
|
|
2154
|
+
}
|
|
2155
|
+
}
|
|
2156
|
+
this.bodies.clear();
|
|
2157
|
+
this.constraints.clear();
|
|
2158
|
+
this.idCounter = 0;
|
|
2159
|
+
this.constraintIdCounter = 0;
|
|
2160
|
+
}
|
|
2161
|
+
static ensureInit() {
|
|
2162
|
+
if (!this.Matter || !this.engine) {
|
|
2163
|
+
throw new Error(
|
|
2164
|
+
"MatterPhysics not initialized. Call MatterPhysics.init(Matter) first."
|
|
2165
|
+
);
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
static generateId(prefix = "body") {
|
|
2169
|
+
return `${prefix}_${this.idCounter++}`;
|
|
2170
|
+
}
|
|
2171
|
+
/**
|
|
2172
|
+
* Add a rectangle body.
|
|
2173
|
+
* @param x - Center X
|
|
2174
|
+
* @param y - Center Y
|
|
2175
|
+
* @param width - Width
|
|
2176
|
+
* @param height - Height
|
|
2177
|
+
* @param options - Body options
|
|
2178
|
+
* @returns Body reference with id
|
|
2179
|
+
*/
|
|
2180
|
+
static addRect(x, y, width, height, options = {}) {
|
|
2181
|
+
this.ensureInit();
|
|
2182
|
+
const id = options.id || this.generateId("rect");
|
|
2183
|
+
const body = this.Matter.Bodies.rectangle(x, y, width, height, options);
|
|
2184
|
+
this.Matter.Composite.add(this.world, body);
|
|
2185
|
+
const ref = { id, body, label: options.label };
|
|
2186
|
+
this.bodies.set(id, ref);
|
|
2187
|
+
return ref;
|
|
2188
|
+
}
|
|
2189
|
+
/**
|
|
2190
|
+
* Add a circle body.
|
|
2191
|
+
* @param x - Center X
|
|
2192
|
+
* @param y - Center Y
|
|
2193
|
+
* @param radius - Radius
|
|
2194
|
+
* @param options - Body options
|
|
2195
|
+
* @returns Body reference with id
|
|
2196
|
+
*/
|
|
2197
|
+
static addCircle(x, y, radius, options = {}) {
|
|
2198
|
+
this.ensureInit();
|
|
2199
|
+
const id = options.id || this.generateId("circle");
|
|
2200
|
+
const body = this.Matter.Bodies.circle(x, y, radius, options);
|
|
2201
|
+
this.Matter.Composite.add(this.world, body);
|
|
2202
|
+
const ref = { id, body, label: options.label };
|
|
2203
|
+
this.bodies.set(id, ref);
|
|
2204
|
+
return ref;
|
|
2205
|
+
}
|
|
2206
|
+
/**
|
|
2207
|
+
* Add a polygon body.
|
|
2208
|
+
* @param x - Center X
|
|
2209
|
+
* @param y - Center Y
|
|
2210
|
+
* @param sides - Number of sides
|
|
2211
|
+
* @param radius - Radius
|
|
2212
|
+
* @param options - Body options
|
|
2213
|
+
* @returns Body reference with id
|
|
2214
|
+
*/
|
|
2215
|
+
static addPolygon(x, y, sides, radius, options = {}) {
|
|
2216
|
+
this.ensureInit();
|
|
2217
|
+
const id = options.id || this.generateId("polygon");
|
|
2218
|
+
const body = this.Matter.Bodies.polygon(x, y, sides, radius, options);
|
|
2219
|
+
this.Matter.Composite.add(this.world, body);
|
|
2220
|
+
const ref = { id, body, label: options.label };
|
|
2221
|
+
this.bodies.set(id, ref);
|
|
2222
|
+
return ref;
|
|
2223
|
+
}
|
|
2224
|
+
/**
|
|
2225
|
+
* Add a body from custom vertices.
|
|
2226
|
+
* @param x - Center X
|
|
2227
|
+
* @param y - Center Y
|
|
2228
|
+
* @param vertices - Array of {x, y} points
|
|
2229
|
+
* @param options - Body options
|
|
2230
|
+
* @returns Body reference with id
|
|
2231
|
+
*/
|
|
2232
|
+
static addFromVertices(x, y, vertices, options = {}) {
|
|
2233
|
+
this.ensureInit();
|
|
2234
|
+
const id = options.id || this.generateId("custom");
|
|
2235
|
+
const body = this.Matter.Bodies.fromVertices(x, y, vertices, options);
|
|
2236
|
+
this.Matter.Composite.add(this.world, body);
|
|
2237
|
+
const ref = { id, body, label: options.label };
|
|
2238
|
+
this.bodies.set(id, ref);
|
|
2239
|
+
return ref;
|
|
2240
|
+
}
|
|
2241
|
+
/**
|
|
2242
|
+
* Add a constraint between two bodies.
|
|
2243
|
+
* @param bodyIdA - First body ID
|
|
2244
|
+
* @param bodyIdB - Second body ID
|
|
2245
|
+
* @param options - Constraint options
|
|
2246
|
+
* @returns Constraint ID
|
|
2247
|
+
*/
|
|
2248
|
+
static addConstraint(bodyIdA, bodyIdB, options = {}) {
|
|
2249
|
+
this.ensureInit();
|
|
2250
|
+
const refA = this.bodies.get(bodyIdA);
|
|
2251
|
+
const refB = this.bodies.get(bodyIdB);
|
|
2252
|
+
if (!refA || !refB) {
|
|
2253
|
+
throw new Error(`Body not found: ${!refA ? bodyIdA : bodyIdB}`);
|
|
2254
|
+
}
|
|
2255
|
+
const id = options.id || `constraint_${this.constraintIdCounter++}`;
|
|
2256
|
+
const constraint = this.Matter.Constraint.create({
|
|
2257
|
+
bodyA: refA.body,
|
|
2258
|
+
bodyB: refB.body,
|
|
2259
|
+
...options
|
|
2260
|
+
});
|
|
2261
|
+
this.Matter.Composite.add(this.world, constraint);
|
|
2262
|
+
this.constraints.set(id, constraint);
|
|
2263
|
+
return id;
|
|
2264
|
+
}
|
|
2265
|
+
/**
|
|
2266
|
+
* Add a constraint anchored to a world point.
|
|
2267
|
+
* @param bodyId - Body ID
|
|
2268
|
+
* @param worldPoint - World anchor point {x, y}
|
|
2269
|
+
* @param options - Constraint options
|
|
2270
|
+
* @returns Constraint ID
|
|
2271
|
+
*/
|
|
2272
|
+
static addWorldConstraint(bodyId, worldPoint, options = {}) {
|
|
2273
|
+
this.ensureInit();
|
|
2274
|
+
const ref = this.bodies.get(bodyId);
|
|
2275
|
+
if (!ref) throw new Error(`Body not found: ${bodyId}`);
|
|
2276
|
+
const id = options.id || `constraint_${this.constraintIdCounter++}`;
|
|
2277
|
+
const constraint = this.Matter.Constraint.create({
|
|
2278
|
+
bodyA: ref.body,
|
|
2279
|
+
pointB: worldPoint,
|
|
2280
|
+
...options
|
|
2281
|
+
});
|
|
2282
|
+
this.Matter.Composite.add(this.world, constraint);
|
|
2283
|
+
this.constraints.set(id, constraint);
|
|
2284
|
+
return id;
|
|
2285
|
+
}
|
|
2286
|
+
/**
|
|
2287
|
+
* Apply force to a body.
|
|
2288
|
+
* @param bodyId - Body ID
|
|
2289
|
+
* @param force - Force vector {x, y}
|
|
2290
|
+
*/
|
|
2291
|
+
static applyForce(bodyId, force) {
|
|
2292
|
+
this.ensureInit();
|
|
2293
|
+
const ref = this.bodies.get(bodyId);
|
|
2294
|
+
if (!ref) return;
|
|
2295
|
+
this.Matter.Body.applyForce(ref.body, ref.body.position, force);
|
|
2296
|
+
}
|
|
2297
|
+
/**
|
|
2298
|
+
* Set velocity of a body.
|
|
2299
|
+
* @param bodyId - Body ID
|
|
2300
|
+
* @param velocity - Velocity vector {x, y}
|
|
2301
|
+
*/
|
|
2302
|
+
static setVelocity(bodyId, velocity) {
|
|
2303
|
+
this.ensureInit();
|
|
2304
|
+
const ref = this.bodies.get(bodyId);
|
|
2305
|
+
if (!ref) return;
|
|
2306
|
+
this.Matter.Body.setVelocity(ref.body, velocity);
|
|
2307
|
+
}
|
|
2308
|
+
/**
|
|
2309
|
+
* Set position of a body.
|
|
2310
|
+
* @param bodyId - Body ID
|
|
2311
|
+
* @param position - Position {x, y}
|
|
2312
|
+
*/
|
|
2313
|
+
static setPosition(bodyId, position) {
|
|
2314
|
+
this.ensureInit();
|
|
2315
|
+
const ref = this.bodies.get(bodyId);
|
|
2316
|
+
if (!ref) return;
|
|
2317
|
+
this.Matter.Body.setPosition(ref.body, position);
|
|
2318
|
+
}
|
|
2319
|
+
/**
|
|
2320
|
+
* Set gravity.
|
|
2321
|
+
* @param x - Horizontal gravity
|
|
2322
|
+
* @param y - Vertical gravity
|
|
2323
|
+
*/
|
|
2324
|
+
static setGravity(x, y) {
|
|
2325
|
+
this.ensureInit();
|
|
2326
|
+
this.world.gravity.x = x;
|
|
2327
|
+
this.world.gravity.y = y;
|
|
2328
|
+
}
|
|
2329
|
+
/**
|
|
2330
|
+
* Get a body reference by ID.
|
|
2331
|
+
*/
|
|
2332
|
+
static getBody(id) {
|
|
2333
|
+
return this.bodies.get(id);
|
|
2334
|
+
}
|
|
2335
|
+
/**
|
|
2336
|
+
* Get all body references.
|
|
2337
|
+
*/
|
|
2338
|
+
static getAllBodies() {
|
|
2339
|
+
return Array.from(this.bodies.values());
|
|
2340
|
+
}
|
|
2341
|
+
/**
|
|
2342
|
+
* Remove a body.
|
|
2343
|
+
* @param id - Body ID
|
|
2344
|
+
*/
|
|
2345
|
+
static removeBody(id) {
|
|
2346
|
+
this.ensureInit();
|
|
2347
|
+
const ref = this.bodies.get(id);
|
|
2348
|
+
if (!ref) return false;
|
|
2349
|
+
this.Matter.Composite.remove(this.world, ref.body);
|
|
2350
|
+
return this.bodies.delete(id);
|
|
2351
|
+
}
|
|
2352
|
+
/**
|
|
2353
|
+
* Remove a constraint.
|
|
2354
|
+
* @param id - Constraint ID
|
|
2355
|
+
*/
|
|
2356
|
+
static removeConstraint(id) {
|
|
2357
|
+
this.ensureInit();
|
|
2358
|
+
const constraint = this.constraints.get(id);
|
|
2359
|
+
if (!constraint) return false;
|
|
2360
|
+
this.Matter.Composite.remove(this.world, constraint);
|
|
2361
|
+
return this.constraints.delete(id);
|
|
2362
|
+
}
|
|
2363
|
+
/**
|
|
2364
|
+
* Step the physics engine.
|
|
2365
|
+
* @param deltaTime - Time step in milliseconds
|
|
2366
|
+
*/
|
|
2367
|
+
static update(deltaTime) {
|
|
2368
|
+
this.ensureInit();
|
|
2369
|
+
this.Matter.Engine.update(this.engine, deltaTime);
|
|
2370
|
+
}
|
|
2371
|
+
/**
|
|
2372
|
+
* Debug draw all bodies and constraints using Klint drawing primitives.
|
|
2373
|
+
* @param ctx - Klint context
|
|
2374
|
+
* @param options - Drawing options
|
|
2375
|
+
*/
|
|
2376
|
+
static draw(ctx, options = {}) {
|
|
2377
|
+
const {
|
|
2378
|
+
showBodies = true,
|
|
2379
|
+
showConstraints = true,
|
|
2380
|
+
showBounds = false,
|
|
2381
|
+
bodyStroke = "#ffffff",
|
|
2382
|
+
bodyFill = "transparent",
|
|
2383
|
+
staticFill = "#666666",
|
|
2384
|
+
constraintStroke = "#44ff44",
|
|
2385
|
+
lineWidth = 1
|
|
2386
|
+
} = options;
|
|
2387
|
+
ctx.save();
|
|
2388
|
+
ctx.strokeWidth(lineWidth);
|
|
2389
|
+
if (showBodies) {
|
|
2390
|
+
this.bodies.forEach((ref) => {
|
|
2391
|
+
const body = ref.body;
|
|
2392
|
+
const vertices = body.vertices;
|
|
2393
|
+
ctx.fillStyle = body.isStatic ? staticFill : bodyFill;
|
|
2394
|
+
ctx.strokeStyle = bodyStroke;
|
|
2395
|
+
ctx.beginShape();
|
|
2396
|
+
for (let i = 0; i < vertices.length; i++) {
|
|
2397
|
+
ctx.vertex(vertices[i].x, vertices[i].y);
|
|
2398
|
+
}
|
|
2399
|
+
ctx.endShape(true);
|
|
2400
|
+
if (showBounds) {
|
|
2401
|
+
const { min, max } = body.bounds;
|
|
2402
|
+
ctx.noFill();
|
|
2403
|
+
ctx.strokeStyle = "#ff444444";
|
|
2404
|
+
ctx.setRectOrigin("corner");
|
|
2405
|
+
ctx.rectangle(min.x, min.y, max.x - min.x, max.y - min.y);
|
|
2406
|
+
}
|
|
2407
|
+
});
|
|
2408
|
+
}
|
|
2409
|
+
if (showConstraints) {
|
|
2410
|
+
ctx.strokeStyle = constraintStroke;
|
|
2411
|
+
this.constraints.forEach((constraint) => {
|
|
2412
|
+
const { bodyA, bodyB, pointA, pointB } = constraint;
|
|
2413
|
+
const startX = bodyA ? bodyA.position.x + (pointA?.x || 0) : pointA?.x || 0;
|
|
2414
|
+
const startY = bodyA ? bodyA.position.y + (pointA?.y || 0) : pointA?.y || 0;
|
|
2415
|
+
const endX = bodyB ? bodyB.position.x + (pointB?.x || 0) : pointB?.x || 0;
|
|
2416
|
+
const endY = bodyB ? bodyB.position.y + (pointB?.y || 0) : pointB?.y || 0;
|
|
2417
|
+
ctx.line(startX, startY, endX, endY);
|
|
2418
|
+
});
|
|
2419
|
+
}
|
|
2420
|
+
ctx.restore();
|
|
2421
|
+
}
|
|
2422
|
+
/**
|
|
2423
|
+
* Iterate over all bodies with their position and angle.
|
|
2424
|
+
* Convenience method for syncing physics bodies to visual objects.
|
|
2425
|
+
* @param fn - Callback receiving body data
|
|
2426
|
+
*/
|
|
2427
|
+
static forEach(fn) {
|
|
2428
|
+
this.bodies.forEach((ref) => {
|
|
2429
|
+
fn({
|
|
2430
|
+
id: ref.id,
|
|
2431
|
+
x: ref.body.position.x,
|
|
2432
|
+
y: ref.body.position.y,
|
|
2433
|
+
angle: ref.body.angle,
|
|
2434
|
+
velocity: ref.body.velocity,
|
|
2435
|
+
body: ref.body,
|
|
2436
|
+
label: ref.label
|
|
2437
|
+
});
|
|
2438
|
+
});
|
|
2439
|
+
}
|
|
2440
|
+
/**
|
|
2441
|
+
* Register a collision callback.
|
|
2442
|
+
* @param event - Event type: 'collisionStart', 'collisionActive', 'collisionEnd'
|
|
2443
|
+
* @param callback - Callback receiving collision pairs
|
|
2444
|
+
*/
|
|
2445
|
+
static onCollision(event, callback) {
|
|
2446
|
+
this.ensureInit();
|
|
2447
|
+
this.Matter.Events.on(this.engine, event, (e) => {
|
|
2448
|
+
callback(e.pairs);
|
|
2449
|
+
});
|
|
2450
|
+
}
|
|
2451
|
+
/**
|
|
2452
|
+
* Get the raw Matter.js engine for advanced usage.
|
|
2453
|
+
*/
|
|
2454
|
+
static getEngine() {
|
|
2455
|
+
return this.engine;
|
|
2456
|
+
}
|
|
2457
|
+
/**
|
|
2458
|
+
* Get the raw Matter.js world for advanced usage.
|
|
2459
|
+
*/
|
|
2460
|
+
static getWorld() {
|
|
2461
|
+
return this.world;
|
|
2462
|
+
}
|
|
2463
|
+
/**
|
|
2464
|
+
* Clear all bodies, constraints, and reset the engine.
|
|
2465
|
+
*/
|
|
2466
|
+
static clear() {
|
|
2467
|
+
if (this.world) {
|
|
2468
|
+
this.Matter?.Composite.clear(this.world, false);
|
|
2469
|
+
}
|
|
2470
|
+
this.bodies.clear();
|
|
2471
|
+
this.constraints.clear();
|
|
2472
|
+
this.idCounter = 0;
|
|
2473
|
+
this.constraintIdCounter = 0;
|
|
2474
|
+
}
|
|
2475
|
+
/**
|
|
2476
|
+
* Destroy the engine entirely.
|
|
2477
|
+
*/
|
|
2478
|
+
static destroy() {
|
|
2479
|
+
this.clear();
|
|
2480
|
+
if (this.engine) {
|
|
2481
|
+
this.Matter?.Engine.clear(this.engine);
|
|
2482
|
+
}
|
|
2483
|
+
this.engine = null;
|
|
2484
|
+
this.world = null;
|
|
2485
|
+
this.Matter = null;
|
|
2486
|
+
this._loaded = false;
|
|
2487
|
+
}
|
|
2488
|
+
};
|
|
2489
|
+
_MatterPhysics.Matter = null;
|
|
2490
|
+
_MatterPhysics.engine = null;
|
|
2491
|
+
_MatterPhysics.world = null;
|
|
2492
|
+
_MatterPhysics.bodies = /* @__PURE__ */ new Map();
|
|
2493
|
+
_MatterPhysics.constraints = /* @__PURE__ */ new Map();
|
|
2494
|
+
_MatterPhysics.idCounter = 0;
|
|
2495
|
+
_MatterPhysics.constraintIdCounter = 0;
|
|
2496
|
+
_MatterPhysics._loaded = false;
|
|
2497
|
+
/** Matter.js version loaded from CDN when not installed locally */
|
|
2498
|
+
_MatterPhysics.MATTER_VERSION = "0.20.0";
|
|
2499
|
+
/** CDN URL template — version is interpolated */
|
|
2500
|
+
_MatterPhysics.MATTER_CDN = `https://cdnjs.cloudflare.com/ajax/libs/matter-js/${_MatterPhysics.MATTER_VERSION}/matter.min.js`;
|
|
2501
|
+
var MatterPhysics = _MatterPhysics;
|
|
2502
|
+
|
|
2503
|
+
// src/plugins/Projector.tsx
|
|
2504
|
+
var RAD_TO_DEG = 180 / Math.PI;
|
|
2505
|
+
var Projector = class {
|
|
2506
|
+
constructor(options) {
|
|
2507
|
+
this.perspective = options?.perspective ?? 2;
|
|
2508
|
+
this.radius = options?.radius ?? 1;
|
|
2509
|
+
}
|
|
2510
|
+
/**
|
|
2511
|
+
* Build a DOMMatrix from a transform callback.
|
|
2512
|
+
* The callback receives a Transform3D object — call rotateX, rotateY, etc.
|
|
2513
|
+
* in the order you want them applied.
|
|
2514
|
+
*/
|
|
2515
|
+
buildMatrix(transforms) {
|
|
2516
|
+
const m = new DOMMatrix();
|
|
2517
|
+
if (!transforms) return m;
|
|
2518
|
+
transforms({
|
|
2519
|
+
rotateX: (r) => m.rotateAxisAngleSelf(1, 0, 0, r * RAD_TO_DEG),
|
|
2520
|
+
rotateY: (r) => m.rotateAxisAngleSelf(0, 1, 0, r * RAD_TO_DEG),
|
|
2521
|
+
rotateZ: (r) => m.rotateAxisAngleSelf(0, 0, 1, r * RAD_TO_DEG),
|
|
2522
|
+
translate: (x, y, z) => m.translateSelf(x, y, z),
|
|
2523
|
+
scale: (x, y, z) => m.scaleSelf(x, y ?? x, z ?? x)
|
|
2524
|
+
});
|
|
2525
|
+
return m;
|
|
2526
|
+
}
|
|
2527
|
+
/**
|
|
2528
|
+
* Apply perspective projection to a transformed 3D point.
|
|
2529
|
+
* Returns 2D coordinates + depth + scale factor.
|
|
2530
|
+
*/
|
|
2531
|
+
projectPoint(m, point) {
|
|
2532
|
+
const p = m.transformPoint(new DOMPoint(point.x, point.y, point.z, 1));
|
|
2533
|
+
const d = this.perspective;
|
|
2534
|
+
const f = d === 0 ? 1 : 1 / (d - p.z);
|
|
2535
|
+
const r = this.radius;
|
|
2536
|
+
return {
|
|
2537
|
+
x: p.x * f * r,
|
|
2538
|
+
y: p.y * f * r,
|
|
2539
|
+
z: p.z,
|
|
2540
|
+
scale: Math.abs(f)
|
|
2541
|
+
};
|
|
2542
|
+
}
|
|
2543
|
+
/**
|
|
2544
|
+
* Project a single 3D point.
|
|
2545
|
+
*
|
|
2546
|
+
* @param point - The 3D point to project.
|
|
2547
|
+
* @param transforms - Optional callback to apply 3D transforms before projection.
|
|
2548
|
+
* @returns Projected 2D point with depth and scale info.
|
|
2549
|
+
*
|
|
2550
|
+
* @example
|
|
2551
|
+
* ```tsx
|
|
2552
|
+
* const p = projector.project({ x: 0, y: 1, z: 0 }, (t) => {
|
|
2553
|
+
* t.rotateY(angle);
|
|
2554
|
+
* });
|
|
2555
|
+
* K.circle(p.x, p.y, 8 * p.scale);
|
|
2556
|
+
* ```
|
|
2557
|
+
*/
|
|
2558
|
+
project(point, transforms) {
|
|
2559
|
+
return this.projectPoint(this.buildMatrix(transforms), point);
|
|
2560
|
+
}
|
|
2561
|
+
/**
|
|
2562
|
+
* Project an array of 3D points with the same transform.
|
|
2563
|
+
* The transform matrix is built once and reused for all points.
|
|
2564
|
+
*
|
|
2565
|
+
* @param points - Array of 3D points.
|
|
2566
|
+
* @param transforms - Optional transform callback (applied identically to all points).
|
|
2567
|
+
* @returns Array of projected points (same order as input).
|
|
2568
|
+
*
|
|
2569
|
+
* @example
|
|
2570
|
+
* ```tsx
|
|
2571
|
+
* const projected = projector.projectAll(cubeVertices, (t) => {
|
|
2572
|
+
* t.rotateX(K.time);
|
|
2573
|
+
* t.rotateY(K.time * 0.7);
|
|
2574
|
+
* });
|
|
2575
|
+
* for (const p of projected) {
|
|
2576
|
+
* K.circle(p.x, p.y, 6 * p.scale);
|
|
2577
|
+
* }
|
|
2578
|
+
* ```
|
|
2579
|
+
*/
|
|
2580
|
+
projectAll(points, transforms) {
|
|
2581
|
+
const m = this.buildMatrix(transforms);
|
|
2582
|
+
return points.map((pt) => this.projectPoint(m, pt));
|
|
2583
|
+
}
|
|
2584
|
+
/**
|
|
2585
|
+
* Project and depth-sort an array of 3D points (back-to-front).
|
|
2586
|
+
* Each result includes `index` — the original index in the input array,
|
|
2587
|
+
* so you can map back to colors, labels, etc.
|
|
2588
|
+
*
|
|
2589
|
+
* @param points - Array of 3D points.
|
|
2590
|
+
* @param transforms - Optional transform callback.
|
|
2591
|
+
* @returns Depth-sorted array of projected points with original indices.
|
|
2592
|
+
*
|
|
2593
|
+
* @example
|
|
2594
|
+
* ```tsx
|
|
2595
|
+
* const sorted = projector.projectSorted(points, (t) => {
|
|
2596
|
+
* t.rotateX(K.time);
|
|
2597
|
+
* });
|
|
2598
|
+
* for (const p of sorted) {
|
|
2599
|
+
* K.fillColor(colors[p.index]);
|
|
2600
|
+
* K.circle(p.x, p.y, 10 * p.scale);
|
|
2601
|
+
* }
|
|
2602
|
+
* ```
|
|
2603
|
+
*/
|
|
2604
|
+
projectSorted(points, transforms) {
|
|
2605
|
+
const m = this.buildMatrix(transforms);
|
|
2606
|
+
return points.map((pt, i) => ({ ...this.projectPoint(m, pt), index: i })).sort((a, b) => a.z - b.z);
|
|
2607
|
+
}
|
|
2608
|
+
};
|
|
2483
2609
|
export {
|
|
2484
2610
|
CatmullRom,
|
|
2485
2611
|
Delaunay,
|
|
2486
2612
|
FontParser,
|
|
2487
|
-
|
|
2488
|
-
|
|
2613
|
+
MatterPhysics,
|
|
2614
|
+
Projector,
|
|
2615
|
+
Sprites
|
|
2489
2616
|
};
|