@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.
@@ -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
- Sprites: () => Sprites,
27
- Things: () => Things
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
- const obj = {};
120
- const tableVersion = bin.readFixed(data, offset);
121
- offset += 4;
122
- obj.fontRevision = bin.readFixed(data, offset);
123
- offset += 4;
124
- const checkSumAdjustment = bin.readUint(data, offset);
125
- offset += 4;
126
- const magicNumber = bin.readUint(data, offset);
127
- offset += 4;
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
- const obj = {};
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
- const obj = {};
170
- const tableVersion = bin.readFixed(data, offset);
171
- offset += 4;
172
- const keys = [
173
- "ascender",
174
- "descender",
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, lsb = 0, i = 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, lsBearing };
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
- const startOffset = offset;
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 += 2;
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
- const startOffset = offset;
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
- const startOffset = offset;
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 (data.length - off < instructionLength) return null;
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 contoursWithLength = rawContours.map(
1595
- (contour, index) => ({
1596
- contour,
1597
- originalIndex: index,
1598
- length: this.calculateContourLength(contour)
1599
- })
1507
+ const prepared = rawContours.map(
1508
+ (contour) => this.buildSegments(contour)
1600
1509
  );
1601
- contoursWithLength.sort((a, b) => b.length - a.length);
1510
+ prepared.sort((a, b) => b.totalLength - a.totalLength);
1602
1511
  const allPoints = [];
1603
- for (let i = 0; i < contoursWithLength.length; i++) {
1604
- const { contour } = contoursWithLength[i];
1605
- const contourPoints = this.sampleContour(contour, sampling, i);
1606
- allPoints.push(...contourPoints);
1607
- }
1608
- return allPoints;
1609
- },
1610
- // Calculate total length of a contour
1611
- calculateContourLength(contour) {
1612
- let totalLength = 0;
1613
- let currentX = contour.startX;
1614
- let currentY = contour.startY;
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 totalLength;
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
- // Sample a single contour uniformly
1684
- sampleContour(contour, sampling, contourIndex) {
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.sqrt(
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 length = this.approximateQuadraticLength(
1714
- currentX,
1715
- currentY,
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
- const points = [];
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
- Sprites,
2517
- Things
2655
+ MatterPhysics,
2656
+ Projector,
2657
+ Sprites
2518
2658
  });