@blorkfield/overlay-core 0.9.0 → 0.10.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 +171 -40
- package/dist/index.cjs +300 -113
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +97 -35
- package/dist/index.d.ts +97 -35
- package/dist/index.js +297 -112
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -33,10 +33,12 @@ __export(index_exports, {
|
|
|
33
33
|
BackgroundManager: () => BackgroundManager,
|
|
34
34
|
OverlayScene: () => OverlayScene,
|
|
35
35
|
TAGS: () => TAGS,
|
|
36
|
-
TAG_FALLING: () => TAG_FALLING,
|
|
37
36
|
TAG_FOLLOW_WINDOW: () => TAG_FOLLOW_WINDOW,
|
|
38
37
|
TAG_GRABABLE: () => TAG_GRABABLE,
|
|
39
38
|
TAG_GRAVITY_OVERRIDE: () => TAG_GRAVITY_OVERRIDE,
|
|
39
|
+
TAG_MASS_OVERRIDE: () => TAG_MASS_OVERRIDE,
|
|
40
|
+
TAG_SPEED_OVERRIDE: () => TAG_SPEED_OVERRIDE,
|
|
41
|
+
TAG_STATIC: () => TAG_STATIC,
|
|
40
42
|
clearFontCache: () => clearFontCache,
|
|
41
43
|
getGlyphData: () => getGlyphData,
|
|
42
44
|
getKerning: () => getKerning,
|
|
@@ -56,7 +58,7 @@ var import_matter_js = __toESM(require("matter-js"), 1);
|
|
|
56
58
|
function createEngine(gravity) {
|
|
57
59
|
const engine = import_matter_js.default.Engine.create();
|
|
58
60
|
engine.gravity.x = gravity.x;
|
|
59
|
-
engine.gravity.y = gravity.y;
|
|
61
|
+
engine.gravity.y = -gravity.y;
|
|
60
62
|
return engine;
|
|
61
63
|
}
|
|
62
64
|
function createRender(engine, canvas, config) {
|
|
@@ -506,6 +508,7 @@ function createBodyFromVertices(id, x, y, vertices, renderOptions) {
|
|
|
506
508
|
restitution: 0.3,
|
|
507
509
|
friction: 0.1,
|
|
508
510
|
frictionAir: 0.01,
|
|
511
|
+
density: 5e-3,
|
|
509
512
|
label: `entity:${id}`,
|
|
510
513
|
render: renderOptions
|
|
511
514
|
});
|
|
@@ -598,6 +601,7 @@ function createCircleEntity(id, config) {
|
|
|
598
601
|
restitution: 0.3,
|
|
599
602
|
friction: 0.1,
|
|
600
603
|
frictionAir: 0.01,
|
|
604
|
+
density: 5e-3,
|
|
601
605
|
label: `entity:${id}`,
|
|
602
606
|
render: createFillRenderOptions(config)
|
|
603
607
|
});
|
|
@@ -609,6 +613,7 @@ function createCircleEntityWithSprite(id, config, imageWidth, imageHeight) {
|
|
|
609
613
|
restitution: 0.3,
|
|
610
614
|
friction: 0.1,
|
|
611
615
|
frictionAir: 0.01,
|
|
616
|
+
density: 5e-3,
|
|
612
617
|
label: `entity:${id}`,
|
|
613
618
|
render: createSpriteRenderOptions(config, imageWidth, imageHeight)
|
|
614
619
|
});
|
|
@@ -1039,8 +1044,7 @@ var EffectManager = class {
|
|
|
1039
1044
|
const objectConfig = this.selectObjectConfig(config.objectConfigs);
|
|
1040
1045
|
if (!objectConfig) continue;
|
|
1041
1046
|
const radius = this.calculateRadius(objectConfig);
|
|
1042
|
-
const tags =
|
|
1043
|
-
if (!tags.includes("falling")) tags.push("falling");
|
|
1047
|
+
const tags = (objectConfig.objectConfig.tags ?? []).filter((t) => t !== "static");
|
|
1044
1048
|
const fullConfig = {
|
|
1045
1049
|
...objectConfig.objectConfig,
|
|
1046
1050
|
x: originX,
|
|
@@ -1079,8 +1083,7 @@ var EffectManager = class {
|
|
|
1079
1083
|
const radius = this.calculateRadius(objectConfig);
|
|
1080
1084
|
const x = this.randomInRange(spawnAreaStart + radius, spawnAreaStart + spawnAreaWidth - radius);
|
|
1081
1085
|
const y = bounds.top - radius;
|
|
1082
|
-
const tags =
|
|
1083
|
-
if (!tags.includes("falling")) tags.push("falling");
|
|
1086
|
+
const tags = (objectConfig.objectConfig.tags ?? []).filter((t) => t !== "static");
|
|
1084
1087
|
const fullConfig = {
|
|
1085
1088
|
...objectConfig.objectConfig,
|
|
1086
1089
|
x,
|
|
@@ -1105,8 +1108,7 @@ var EffectManager = class {
|
|
|
1105
1108
|
const objectConfig = this.selectObjectConfig(config.objectConfigs);
|
|
1106
1109
|
if (!objectConfig) return;
|
|
1107
1110
|
const radius = this.calculateRadius(objectConfig);
|
|
1108
|
-
const tags =
|
|
1109
|
-
if (!tags.includes("falling")) tags.push("falling");
|
|
1111
|
+
const tags = (objectConfig.objectConfig.tags ?? []).filter((t) => t !== "static");
|
|
1110
1112
|
const fullConfig = {
|
|
1111
1113
|
...objectConfig.objectConfig,
|
|
1112
1114
|
x: config.origin.x,
|
|
@@ -1460,6 +1462,10 @@ var OverlayScene = class {
|
|
|
1460
1462
|
this.substeps = 2;
|
|
1461
1463
|
// Tracks only the bodies with a gravity override — engine gravity handles everyone else
|
|
1462
1464
|
this.gravityOverrideEntries = /* @__PURE__ */ new Set();
|
|
1465
|
+
// Tracks only the bodies with follow_window tag — avoids scanning all objects each frame
|
|
1466
|
+
this.followWindowEntries = /* @__PURE__ */ new Set();
|
|
1467
|
+
// IDs of static objects that have pressure thresholds — empty = skip updatePressure entirely
|
|
1468
|
+
this.pressureObstacleIds = /* @__PURE__ */ new Set();
|
|
1463
1469
|
/** Handle mouse down - start grab via programmatic API */
|
|
1464
1470
|
this.handleMouseDown = (event) => {
|
|
1465
1471
|
const rect = this.canvas.getBoundingClientRect();
|
|
@@ -1491,7 +1497,7 @@ var OverlayScene = class {
|
|
|
1491
1497
|
for (const body of bodies) {
|
|
1492
1498
|
const entry = this.findObjectByBody(body);
|
|
1493
1499
|
if (!entry) continue;
|
|
1494
|
-
if (entry.tags.includes("
|
|
1500
|
+
if (!entry.tags.includes("static")) continue;
|
|
1495
1501
|
if (entry.clicksRemaining === void 0) continue;
|
|
1496
1502
|
entry.clicksRemaining--;
|
|
1497
1503
|
const name = this.getObstacleDisplayName(entry);
|
|
@@ -1550,9 +1556,10 @@ var OverlayScene = class {
|
|
|
1550
1556
|
import_matter_js5.default.Engine.update(this.engine, substepDelta);
|
|
1551
1557
|
}
|
|
1552
1558
|
this.effectManager.update();
|
|
1553
|
-
this.
|
|
1554
|
-
this.
|
|
1555
|
-
|
|
1559
|
+
this.checkExpiration();
|
|
1560
|
+
if (this.pressureObstacleIds.size > 0 || this.floorSegments.length > 0) {
|
|
1561
|
+
this.updatePressure();
|
|
1562
|
+
}
|
|
1556
1563
|
if (this.grabbedObjectId && this.lastGrabMousePosition) {
|
|
1557
1564
|
const entry = this.objects.get(this.grabbedObjectId);
|
|
1558
1565
|
const mouseTarget = this.followTargets.get("mouse");
|
|
@@ -1566,27 +1573,39 @@ var OverlayScene = class {
|
|
|
1566
1573
|
this.lastGrabMousePosition = { x: mouseTarget.x, y: mouseTarget.y };
|
|
1567
1574
|
}
|
|
1568
1575
|
}
|
|
1569
|
-
for (const entry of this.
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1576
|
+
for (const entry of this.followWindowEntries) {
|
|
1577
|
+
if (this.grabbedObjectId === entry.id) continue;
|
|
1578
|
+
const targetKey = entry.followTarget ?? "mouse";
|
|
1579
|
+
let targetPos;
|
|
1580
|
+
targetPos = this.followTargets.get(targetKey);
|
|
1581
|
+
if (!targetPos) {
|
|
1582
|
+
const targetEntry = this.objects.get(targetKey);
|
|
1583
|
+
if (targetEntry) targetPos = targetEntry.body.position;
|
|
1584
|
+
}
|
|
1585
|
+
if (!targetPos) {
|
|
1586
|
+
for (const e of this.objects.values()) {
|
|
1587
|
+
if (e !== entry && e.tags.includes(targetKey)) {
|
|
1588
|
+
targetPos = e.body.position;
|
|
1589
|
+
break;
|
|
1583
1590
|
}
|
|
1584
1591
|
}
|
|
1585
1592
|
}
|
|
1586
|
-
if (
|
|
1593
|
+
if (targetPos) {
|
|
1594
|
+
const dx = targetPos.x - entry.body.position.x;
|
|
1595
|
+
const dy = targetPos.y - entry.body.position.y;
|
|
1596
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
1597
|
+
if (dist > 0) {
|
|
1598
|
+
const speedMult = entry.speedOverride ?? 1;
|
|
1599
|
+
const mag = 1e-3 * speedMult / dist;
|
|
1600
|
+
import_matter_js5.default.Body.applyForce(entry.body, entry.body.position, { x: mag * dx, y: mag * dy });
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
for (const entry of this.objects.values()) {
|
|
1605
|
+
if (this.config.wrapHorizontal && !entry.tags.includes("static")) {
|
|
1587
1606
|
wrapHorizontal(entry.body, this.config.bounds);
|
|
1588
1607
|
}
|
|
1589
|
-
if (entry.domElement && entry.tags.includes("
|
|
1608
|
+
if (entry.domElement && !entry.tags.includes("static")) {
|
|
1590
1609
|
this.updateDOMElementTransform(entry);
|
|
1591
1610
|
}
|
|
1592
1611
|
if (entry.tags.includes("grabable") && !entry.body.isStatic) {
|
|
@@ -1609,7 +1628,7 @@ var OverlayScene = class {
|
|
|
1609
1628
|
};
|
|
1610
1629
|
this.canvas = canvas;
|
|
1611
1630
|
this.config = {
|
|
1612
|
-
gravity: { x: 0, y: 1 },
|
|
1631
|
+
gravity: { x: 0, y: -1 },
|
|
1613
1632
|
wrapHorizontal: true,
|
|
1614
1633
|
debug: false,
|
|
1615
1634
|
...config
|
|
@@ -1683,7 +1702,7 @@ var OverlayScene = class {
|
|
|
1683
1702
|
const obstacles = [];
|
|
1684
1703
|
const dynamics = [];
|
|
1685
1704
|
for (const entry of this.objects.values()) {
|
|
1686
|
-
if (entry.tags.includes("
|
|
1705
|
+
if (!entry.tags.includes("static")) {
|
|
1687
1706
|
if (Math.abs(entry.body.velocity.y) < 2) {
|
|
1688
1707
|
dynamics.push(entry);
|
|
1689
1708
|
}
|
|
@@ -1906,16 +1925,17 @@ var OverlayScene = class {
|
|
|
1906
1925
|
}
|
|
1907
1926
|
/** Convert a static obstacle to dynamic (make it fall) */
|
|
1908
1927
|
collapseObstacle(entry) {
|
|
1909
|
-
if (entry.tags.includes("
|
|
1928
|
+
if (!entry.tags.includes("static")) return;
|
|
1910
1929
|
const name = this.getObstacleDisplayName(entry);
|
|
1911
1930
|
console.log(`[Pressure] Collapsed: ${name}`);
|
|
1912
1931
|
if (entry.shadow && entry.originalPosition) {
|
|
1913
1932
|
this.createShadow(entry);
|
|
1914
1933
|
}
|
|
1915
|
-
entry.tags.
|
|
1934
|
+
entry.tags = entry.tags.filter((t) => t !== "static");
|
|
1916
1935
|
import_matter_js5.default.Body.setStatic(entry.body, false);
|
|
1917
1936
|
entry.pressureThreshold = void 0;
|
|
1918
1937
|
entry.wordCollapseTag = void 0;
|
|
1938
|
+
this.pressureObstacleIds.delete(entry.id);
|
|
1919
1939
|
}
|
|
1920
1940
|
/** Create a static shadow copy of an obstacle at its original position */
|
|
1921
1941
|
async createShadow(entry) {
|
|
@@ -1960,6 +1980,7 @@ var OverlayScene = class {
|
|
|
1960
1980
|
id: shadowId,
|
|
1961
1981
|
body,
|
|
1962
1982
|
tags: ["shadow"],
|
|
1983
|
+
entityTag: shadowId,
|
|
1963
1984
|
spawnTime: performance.now(),
|
|
1964
1985
|
weight: 0,
|
|
1965
1986
|
ttfGlyph: {
|
|
@@ -1987,6 +2008,7 @@ var OverlayScene = class {
|
|
|
1987
2008
|
id: shadowId,
|
|
1988
2009
|
body: result.body,
|
|
1989
2010
|
tags: ["shadow"],
|
|
2011
|
+
entityTag: shadowId,
|
|
1990
2012
|
spawnTime: performance.now(),
|
|
1991
2013
|
weight: 0
|
|
1992
2014
|
// Shadows don't contribute to pressure
|
|
@@ -2028,10 +2050,6 @@ var OverlayScene = class {
|
|
|
2028
2050
|
}
|
|
2029
2051
|
return null;
|
|
2030
2052
|
}
|
|
2031
|
-
/** Check if a body is grounded (low vertical velocity indicates resting on something) */
|
|
2032
|
-
isGrounded(body) {
|
|
2033
|
-
return Math.abs(body.velocity.y) < 0.5;
|
|
2034
|
-
}
|
|
2035
2053
|
start() {
|
|
2036
2054
|
import_matter_js5.default.Render.run(this.render);
|
|
2037
2055
|
this.loop();
|
|
@@ -2055,6 +2073,9 @@ var OverlayScene = class {
|
|
|
2055
2073
|
import_matter_js5.default.Events.off(this.engine, "collisionStart", this.handleCollisionStart);
|
|
2056
2074
|
import_matter_js5.default.Engine.clear(this.engine);
|
|
2057
2075
|
this.objects.clear();
|
|
2076
|
+
this.gravityOverrideEntries.clear();
|
|
2077
|
+
this.followWindowEntries.clear();
|
|
2078
|
+
this.pressureObstacleIds.clear();
|
|
2058
2079
|
this.obstaclePressure.clear();
|
|
2059
2080
|
this.previousPressure.clear();
|
|
2060
2081
|
this.pressureLogTimer = 0;
|
|
@@ -2076,17 +2097,17 @@ var OverlayScene = class {
|
|
|
2076
2097
|
}
|
|
2077
2098
|
}
|
|
2078
2099
|
/**
|
|
2079
|
-
* Set gravity at runtime.
|
|
2100
|
+
* Set gravity at runtime. Y axis uses physical convention: negative = down, positive = up.
|
|
2080
2101
|
* @example
|
|
2081
|
-
* scene.setGravity({ x: 0, y: 1 });
|
|
2082
|
-
* scene.setGravity({ x: 0, y:
|
|
2083
|
-
* scene.setGravity({ x: 1, y: 0 }); //
|
|
2102
|
+
* scene.setGravity({ x: 0, y: -1 }); // Normal downward gravity
|
|
2103
|
+
* scene.setGravity({ x: 0, y: 1 }); // Upward gravity
|
|
2104
|
+
* scene.setGravity({ x: 1, y: 0 }); // Rightward gravity
|
|
2084
2105
|
* scene.setGravity({ x: 0, y: 0 }); // Zero gravity
|
|
2085
2106
|
*/
|
|
2086
2107
|
setGravity(gravity) {
|
|
2087
2108
|
this.config.gravity = gravity;
|
|
2088
2109
|
this.engine.gravity.x = gravity.x;
|
|
2089
|
-
this.engine.gravity.y = gravity.y;
|
|
2110
|
+
this.engine.gravity.y = -gravity.y;
|
|
2090
2111
|
}
|
|
2091
2112
|
/**
|
|
2092
2113
|
* Set or clear a per-object gravity override at runtime.
|
|
@@ -2096,18 +2117,85 @@ var OverlayScene = class {
|
|
|
2096
2117
|
const entry = this.objects.get(id);
|
|
2097
2118
|
if (!entry) return;
|
|
2098
2119
|
if (gravity === null) {
|
|
2099
|
-
|
|
2100
|
-
this.gravityOverrideEntries.delete(entry);
|
|
2101
|
-
const idx = entry.tags.indexOf("gravity_override");
|
|
2102
|
-
if (idx !== -1) entry.tags.splice(idx, 1);
|
|
2120
|
+
this.removeTag(id, "gravity_override");
|
|
2103
2121
|
} else {
|
|
2104
2122
|
entry.gravityOverride = gravity;
|
|
2105
|
-
this.
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2123
|
+
this.addTag(id, "gravity_override");
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
/**
|
|
2127
|
+
* Set or change the follow target for an object's 'follow_window' tag.
|
|
2128
|
+
* Target can be 'mouse', an entity ID, or a tag string (follows first matching entity).
|
|
2129
|
+
* Adds 'follow_window' tag if not already present.
|
|
2130
|
+
*/
|
|
2131
|
+
setFollowWindowTarget(id, target) {
|
|
2132
|
+
const entry = this.objects.get(id);
|
|
2133
|
+
if (!entry) return;
|
|
2134
|
+
entry.followTarget = target;
|
|
2135
|
+
this.addTag(id, "follow_window");
|
|
2136
|
+
}
|
|
2137
|
+
/**
|
|
2138
|
+
* Set or change the speed multiplier for an object's movement (follow_window and future
|
|
2139
|
+
* movement behaviors). Pass null to remove the override and reset to default speed.
|
|
2140
|
+
* Negative values cause the object to run away from its target.
|
|
2141
|
+
* Adds 'speed_override' tag if not already present.
|
|
2142
|
+
*/
|
|
2143
|
+
setObjectSpeedOverride(id, speed) {
|
|
2144
|
+
const entry = this.objects.get(id);
|
|
2145
|
+
if (!entry) return;
|
|
2146
|
+
if (speed === null) {
|
|
2147
|
+
this.removeTag(id, "speed_override");
|
|
2148
|
+
} else {
|
|
2149
|
+
entry.speedOverride = speed;
|
|
2150
|
+
this.addTag(id, "speed_override");
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
/**
|
|
2154
|
+
* Set or change the physics mass for an object. Pass null to remove the override and restore
|
|
2155
|
+
* the original density-based mass. Adds 'mass_override' tag if not already present.
|
|
2156
|
+
* Higher mass resists applied forces (including follow forces) more strongly.
|
|
2157
|
+
*/
|
|
2158
|
+
setObjectMassOverride(id, mass) {
|
|
2159
|
+
const entry = this.objects.get(id);
|
|
2160
|
+
if (!entry) return;
|
|
2161
|
+
if (mass === null) {
|
|
2162
|
+
this.removeTag(id, "mass_override");
|
|
2163
|
+
} else {
|
|
2164
|
+
if (entry.originalMass === void 0) entry.originalMass = entry.body.mass;
|
|
2165
|
+
entry.massOverride = mass;
|
|
2166
|
+
import_matter_js5.default.Body.setMass(entry.body, mass);
|
|
2167
|
+
this.addTag(id, "mass_override");
|
|
2109
2168
|
}
|
|
2110
2169
|
}
|
|
2170
|
+
/**
|
|
2171
|
+
* Set the angular velocity (spin) of an object in radians per second.
|
|
2172
|
+
* Positive = counter-clockwise, negative = clockwise.
|
|
2173
|
+
*/
|
|
2174
|
+
setObjectAngularVelocity(id, omega) {
|
|
2175
|
+
const entry = this.objects.get(id);
|
|
2176
|
+
if (!entry) return;
|
|
2177
|
+
import_matter_js5.default.Body.setAngularVelocity(entry.body, omega);
|
|
2178
|
+
}
|
|
2179
|
+
/**
|
|
2180
|
+
* Set the absolute scale of an object on each axis. Both physics collision shape and
|
|
2181
|
+
* sprite rendering are updated together. Scaling changes the body's mass proportionally
|
|
2182
|
+
* (area scales by x*y). Use setObjectMassOverride afterwards if you need a fixed mass.
|
|
2183
|
+
* @param x - Scale factor on the X axis (1 = original size)
|
|
2184
|
+
* @param y - Scale factor on the Y axis (1 = original size)
|
|
2185
|
+
*/
|
|
2186
|
+
setObjectScale(id, x, y) {
|
|
2187
|
+
const entry = this.objects.get(id);
|
|
2188
|
+
if (!entry) return;
|
|
2189
|
+
const currentX = entry.scaleX ?? 1;
|
|
2190
|
+
const currentY = entry.scaleY ?? 1;
|
|
2191
|
+
import_matter_js5.default.Body.scale(entry.body, x / currentX, y / currentY);
|
|
2192
|
+
if (entry.body.render.sprite && entry.baseSpriteSX !== void 0) {
|
|
2193
|
+
entry.body.render.sprite.xScale = entry.baseSpriteSX * x;
|
|
2194
|
+
entry.body.render.sprite.yScale = entry.baseSpriteSY * y;
|
|
2195
|
+
}
|
|
2196
|
+
entry.scaleX = x;
|
|
2197
|
+
entry.scaleY = y;
|
|
2198
|
+
}
|
|
2111
2199
|
/**
|
|
2112
2200
|
* Update the background configuration at runtime.
|
|
2113
2201
|
*/
|
|
@@ -2143,10 +2231,9 @@ var OverlayScene = class {
|
|
|
2143
2231
|
/**
|
|
2144
2232
|
* Spawn an object synchronously.
|
|
2145
2233
|
* Object behavior is determined by tags:
|
|
2146
|
-
* - '
|
|
2234
|
+
* - 'static': Object is not affected by gravity (obstacle). Without this tag, object is dynamic by default.
|
|
2147
2235
|
* - 'follow_window': Object follows mouse when grounded (walks toward mouse)
|
|
2148
2236
|
* - 'grabable': Object can be grabbed and moved with mouse
|
|
2149
|
-
* Without 'falling' tag, object is static.
|
|
2150
2237
|
*/
|
|
2151
2238
|
spawnObject(config) {
|
|
2152
2239
|
if (config.element) {
|
|
@@ -2165,11 +2252,20 @@ var OverlayScene = class {
|
|
|
2165
2252
|
return result.id;
|
|
2166
2253
|
}
|
|
2167
2254
|
const id = crypto.randomUUID();
|
|
2255
|
+
const entityDescriptor = config.imageUrl ? config.imageUrl.split("/").pop()?.replace(/\.[^.]+$/, "") ?? "image" : config.radius ? "circle" : config.shape?.type ?? "rect";
|
|
2256
|
+
const entityTag = `${entityDescriptor.replace(/[^a-zA-Z0-9]/g, "-").toLowerCase().slice(0, 16)}-${id.slice(0, 4)}`;
|
|
2168
2257
|
const tags = [...config.tags ?? []];
|
|
2258
|
+
tags.push(entityTag);
|
|
2169
2259
|
if (config.gravityOverride && !tags.includes("gravity_override")) {
|
|
2170
2260
|
tags.push("gravity_override");
|
|
2171
2261
|
}
|
|
2172
|
-
|
|
2262
|
+
if (config.speedOverride !== void 0 && !tags.includes("speed_override")) {
|
|
2263
|
+
tags.push("speed_override");
|
|
2264
|
+
}
|
|
2265
|
+
if (config.massOverride !== void 0 && !tags.includes("mass_override")) {
|
|
2266
|
+
tags.push("mass_override");
|
|
2267
|
+
}
|
|
2268
|
+
const isStatic = tags.includes("static");
|
|
2173
2269
|
logger.debug("OverlayScene", `Spawning object`, {
|
|
2174
2270
|
id,
|
|
2175
2271
|
tags,
|
|
@@ -2186,6 +2282,10 @@ var OverlayScene = class {
|
|
|
2186
2282
|
} else {
|
|
2187
2283
|
body = createObstacle(id, config, isStatic);
|
|
2188
2284
|
}
|
|
2285
|
+
const naturalMass = body.mass;
|
|
2286
|
+
if (config.massOverride !== void 0) {
|
|
2287
|
+
import_matter_js5.default.Body.setMass(body, config.massOverride);
|
|
2288
|
+
}
|
|
2189
2289
|
let pressureThreshold;
|
|
2190
2290
|
if (config.pressureThreshold) {
|
|
2191
2291
|
pressureThreshold = typeof config.pressureThreshold.value === "number" ? config.pressureThreshold.value : config.pressureThreshold.value[0];
|
|
@@ -2209,13 +2309,24 @@ var OverlayScene = class {
|
|
|
2209
2309
|
shadow,
|
|
2210
2310
|
originalPosition: shadow || clicksRemaining !== void 0 ? { x: config.x, y: config.y } : void 0,
|
|
2211
2311
|
clicksRemaining,
|
|
2212
|
-
gravityOverride: config.gravityOverride
|
|
2312
|
+
gravityOverride: config.gravityOverride,
|
|
2313
|
+
followTarget: tags.includes("follow_window") ? config.followTarget ?? "mouse" : void 0,
|
|
2314
|
+
speedOverride: tags.includes("speed_override") ? config.speedOverride ?? 1 : void 0,
|
|
2315
|
+
massOverride: tags.includes("mass_override") ? config.massOverride : void 0,
|
|
2316
|
+
originalMass: tags.includes("mass_override") ? naturalMass : void 0,
|
|
2317
|
+
entityTag,
|
|
2318
|
+
scaleX: 1,
|
|
2319
|
+
scaleY: 1,
|
|
2320
|
+
baseSpriteSX: body.render.sprite?.xScale,
|
|
2321
|
+
baseSpriteSY: body.render.sprite?.yScale
|
|
2213
2322
|
};
|
|
2214
2323
|
this.objects.set(id, entry);
|
|
2215
2324
|
if (config.gravityOverride) this.gravityOverrideEntries.add(entry);
|
|
2325
|
+
if (tags.includes("follow_window")) this.followWindowEntries.add(entry);
|
|
2216
2326
|
import_matter_js5.default.Composite.add(this.engine.world, body);
|
|
2217
2327
|
if (isStatic && pressureThreshold !== void 0) {
|
|
2218
2328
|
this.obstaclePressure.set(id, /* @__PURE__ */ new Set());
|
|
2329
|
+
this.pressureObstacleIds.add(id);
|
|
2219
2330
|
}
|
|
2220
2331
|
this.emitLifecycleEvent("objectSpawned", this.toObjectState(entry));
|
|
2221
2332
|
return id;
|
|
@@ -2226,11 +2337,20 @@ var OverlayScene = class {
|
|
|
2226
2337
|
*/
|
|
2227
2338
|
async spawnObjectAsync(config) {
|
|
2228
2339
|
const id = crypto.randomUUID();
|
|
2340
|
+
const entityDescriptor = config.imageUrl ? config.imageUrl.split("/").pop()?.replace(/\.[^.]+$/, "") ?? "image" : config.radius ? "circle" : config.shape?.type ?? "rect";
|
|
2341
|
+
const entityTag = `${entityDescriptor.replace(/[^a-zA-Z0-9]/g, "-").toLowerCase().slice(0, 16)}-${id.slice(0, 4)}`;
|
|
2229
2342
|
const tags = [...config.tags ?? []];
|
|
2343
|
+
tags.push(entityTag);
|
|
2230
2344
|
if (config.gravityOverride && !tags.includes("gravity_override")) {
|
|
2231
2345
|
tags.push("gravity_override");
|
|
2232
2346
|
}
|
|
2233
|
-
|
|
2347
|
+
if (config.speedOverride !== void 0 && !tags.includes("speed_override")) {
|
|
2348
|
+
tags.push("speed_override");
|
|
2349
|
+
}
|
|
2350
|
+
if (config.massOverride !== void 0 && !tags.includes("mass_override")) {
|
|
2351
|
+
tags.push("mass_override");
|
|
2352
|
+
}
|
|
2353
|
+
const isStatic = tags.includes("static");
|
|
2234
2354
|
logger.debug("OverlayScene", `Spawning object async`, {
|
|
2235
2355
|
id,
|
|
2236
2356
|
tags,
|
|
@@ -2247,6 +2367,10 @@ var OverlayScene = class {
|
|
|
2247
2367
|
} else {
|
|
2248
2368
|
body = await createObstacleAsync(id, config, isStatic);
|
|
2249
2369
|
}
|
|
2370
|
+
const naturalMass = body.mass;
|
|
2371
|
+
if (config.massOverride !== void 0) {
|
|
2372
|
+
import_matter_js5.default.Body.setMass(body, config.massOverride);
|
|
2373
|
+
}
|
|
2250
2374
|
let pressureThreshold;
|
|
2251
2375
|
if (config.pressureThreshold) {
|
|
2252
2376
|
pressureThreshold = typeof config.pressureThreshold.value === "number" ? config.pressureThreshold.value : config.pressureThreshold.value[0];
|
|
@@ -2270,27 +2394,38 @@ var OverlayScene = class {
|
|
|
2270
2394
|
shadow,
|
|
2271
2395
|
originalPosition: shadow || clicksRemaining !== void 0 ? { x: config.x, y: config.y } : void 0,
|
|
2272
2396
|
clicksRemaining,
|
|
2273
|
-
gravityOverride: config.gravityOverride
|
|
2397
|
+
gravityOverride: config.gravityOverride,
|
|
2398
|
+
followTarget: tags.includes("follow_window") ? config.followTarget ?? "mouse" : void 0,
|
|
2399
|
+
speedOverride: tags.includes("speed_override") ? config.speedOverride ?? 1 : void 0,
|
|
2400
|
+
massOverride: tags.includes("mass_override") ? config.massOverride : void 0,
|
|
2401
|
+
originalMass: tags.includes("mass_override") ? naturalMass : void 0,
|
|
2402
|
+
entityTag,
|
|
2403
|
+
scaleX: 1,
|
|
2404
|
+
scaleY: 1,
|
|
2405
|
+
baseSpriteSX: body.render.sprite?.xScale,
|
|
2406
|
+
baseSpriteSY: body.render.sprite?.yScale
|
|
2274
2407
|
};
|
|
2275
2408
|
this.objects.set(id, entry);
|
|
2276
2409
|
if (config.gravityOverride) this.gravityOverrideEntries.add(entry);
|
|
2410
|
+
if (tags.includes("follow_window")) this.followWindowEntries.add(entry);
|
|
2277
2411
|
import_matter_js5.default.Composite.add(this.engine.world, body);
|
|
2278
2412
|
if (isStatic && pressureThreshold !== void 0) {
|
|
2279
2413
|
this.obstaclePressure.set(id, /* @__PURE__ */ new Set());
|
|
2414
|
+
this.pressureObstacleIds.add(id);
|
|
2280
2415
|
}
|
|
2281
2416
|
this.emitLifecycleEvent("objectSpawned", this.toObjectState(entry));
|
|
2282
2417
|
return id;
|
|
2283
2418
|
}
|
|
2284
2419
|
/**
|
|
2285
|
-
*
|
|
2420
|
+
* Make an object dynamic (remove 'static' tag, affected by gravity).
|
|
2286
2421
|
* Also adds 'grabable' tag so released objects can be dragged.
|
|
2287
2422
|
* This is the tag-based replacement for releaseObstacle().
|
|
2288
2423
|
*/
|
|
2289
2424
|
addFallingTag(id) {
|
|
2290
2425
|
const entry = this.objects.get(id);
|
|
2291
2426
|
if (!entry) return;
|
|
2292
|
-
if (
|
|
2293
|
-
entry.tags.
|
|
2427
|
+
if (entry.tags.includes("static")) {
|
|
2428
|
+
entry.tags = entry.tags.filter((t) => t !== "static");
|
|
2294
2429
|
import_matter_js5.default.Body.setStatic(entry.body, false);
|
|
2295
2430
|
}
|
|
2296
2431
|
if (!entry.tags.includes("grabable")) {
|
|
@@ -2298,34 +2433,66 @@ var OverlayScene = class {
|
|
|
2298
2433
|
}
|
|
2299
2434
|
}
|
|
2300
2435
|
/**
|
|
2301
|
-
* Add a tag to an object.
|
|
2436
|
+
* Add a tag to an object. Tags drive behavior — adding a tag activates the associated effect.
|
|
2437
|
+
* For 'gravity_override', also pass a gravityOverride value via setObjectGravityOverride first,
|
|
2438
|
+
* or the tag will default to {x:0, y:0} (hovering).
|
|
2302
2439
|
*/
|
|
2303
2440
|
addTag(id, tag) {
|
|
2304
2441
|
const entry = this.objects.get(id);
|
|
2305
2442
|
if (!entry) return;
|
|
2306
2443
|
if (!entry.tags.includes(tag)) {
|
|
2307
2444
|
entry.tags.push(tag);
|
|
2308
|
-
if (tag === "
|
|
2309
|
-
import_matter_js5.default.Body.setStatic(entry.body,
|
|
2445
|
+
if (tag === "static") {
|
|
2446
|
+
import_matter_js5.default.Body.setStatic(entry.body, true);
|
|
2447
|
+
} else if (tag === "gravity_override") {
|
|
2448
|
+
if (!entry.gravityOverride) entry.gravityOverride = { x: 0, y: 0 };
|
|
2449
|
+
this.gravityOverrideEntries.add(entry);
|
|
2450
|
+
} else if (tag === "follow_window") {
|
|
2451
|
+
if (!entry.followTarget) entry.followTarget = "mouse";
|
|
2452
|
+
this.followWindowEntries.add(entry);
|
|
2453
|
+
} else if (tag === "speed_override") {
|
|
2454
|
+
if (entry.speedOverride === void 0) entry.speedOverride = 1;
|
|
2455
|
+
} else if (tag === "mass_override") {
|
|
2456
|
+
if (entry.originalMass === void 0) entry.originalMass = entry.body.mass;
|
|
2457
|
+
if (entry.massOverride === void 0) entry.massOverride = Math.round(entry.body.mass);
|
|
2458
|
+
import_matter_js5.default.Body.setMass(entry.body, entry.massOverride);
|
|
2310
2459
|
}
|
|
2311
2460
|
}
|
|
2312
2461
|
}
|
|
2313
2462
|
/**
|
|
2314
|
-
* Remove a tag from an object.
|
|
2463
|
+
* Remove a tag from an object. Tags drive behavior — removing a tag deactivates the associated effect.
|
|
2315
2464
|
*/
|
|
2316
2465
|
removeTag(id, tag) {
|
|
2317
2466
|
const entry = this.objects.get(id);
|
|
2318
2467
|
if (!entry) return;
|
|
2468
|
+
if (tag === entry.entityTag) {
|
|
2469
|
+
logger.warn("OverlayScene", `Cannot remove entity tag '${tag}' from '${id}' \u2014 entity tags are permanent identifiers and cannot be removed`);
|
|
2470
|
+
return;
|
|
2471
|
+
}
|
|
2319
2472
|
const index = entry.tags.indexOf(tag);
|
|
2320
2473
|
if (index !== -1) {
|
|
2321
2474
|
entry.tags.splice(index, 1);
|
|
2322
|
-
if (tag === "
|
|
2323
|
-
import_matter_js5.default.Body.setStatic(entry.body,
|
|
2475
|
+
if (tag === "static") {
|
|
2476
|
+
import_matter_js5.default.Body.setStatic(entry.body, false);
|
|
2477
|
+
} else if (tag === "gravity_override") {
|
|
2478
|
+
this.gravityOverrideEntries.delete(entry);
|
|
2479
|
+
entry.gravityOverride = void 0;
|
|
2480
|
+
} else if (tag === "follow_window") {
|
|
2481
|
+
entry.followTarget = void 0;
|
|
2482
|
+
this.followWindowEntries.delete(entry);
|
|
2483
|
+
} else if (tag === "speed_override") {
|
|
2484
|
+
entry.speedOverride = void 0;
|
|
2485
|
+
} else if (tag === "mass_override") {
|
|
2486
|
+
if (entry.originalMass !== void 0) {
|
|
2487
|
+
import_matter_js5.default.Body.setMass(entry.body, entry.originalMass);
|
|
2488
|
+
}
|
|
2489
|
+
entry.massOverride = void 0;
|
|
2490
|
+
entry.originalMass = void 0;
|
|
2324
2491
|
}
|
|
2325
2492
|
}
|
|
2326
2493
|
}
|
|
2327
2494
|
/**
|
|
2328
|
-
* Release an object (
|
|
2495
|
+
* Release an object (remove 'static' tag to make it dynamic).
|
|
2329
2496
|
* Convenience method - equivalent to addFallingTag().
|
|
2330
2497
|
*/
|
|
2331
2498
|
releaseObject(id) {
|
|
@@ -2340,7 +2507,7 @@ var OverlayScene = class {
|
|
|
2340
2507
|
}
|
|
2341
2508
|
}
|
|
2342
2509
|
/**
|
|
2343
|
-
* Release all static objects (
|
|
2510
|
+
* Release all static objects (remove 'static' tag, add 'grabable' tag).
|
|
2344
2511
|
*/
|
|
2345
2512
|
releaseAllObjects() {
|
|
2346
2513
|
for (const [id] of this.objects) {
|
|
@@ -2348,7 +2515,7 @@ var OverlayScene = class {
|
|
|
2348
2515
|
}
|
|
2349
2516
|
}
|
|
2350
2517
|
/**
|
|
2351
|
-
* Release objects by tag (
|
|
2518
|
+
* Release objects by tag (remove 'static' tag, add 'grabable' tag to matching objects).
|
|
2352
2519
|
*/
|
|
2353
2520
|
releaseObjectsByTag(tag) {
|
|
2354
2521
|
for (const [id, entry] of this.objects) {
|
|
@@ -2363,6 +2530,8 @@ var OverlayScene = class {
|
|
|
2363
2530
|
this.emitLifecycleEvent("objectRemoved", this.toObjectState(entry));
|
|
2364
2531
|
import_matter_js5.default.Composite.remove(this.engine.world, entry.body);
|
|
2365
2532
|
this.gravityOverrideEntries.delete(entry);
|
|
2533
|
+
this.followWindowEntries.delete(entry);
|
|
2534
|
+
this.pressureObstacleIds.delete(id);
|
|
2366
2535
|
this.objects.delete(id);
|
|
2367
2536
|
}
|
|
2368
2537
|
removeObjects(ids) {
|
|
@@ -2376,6 +2545,8 @@ var OverlayScene = class {
|
|
|
2376
2545
|
}
|
|
2377
2546
|
this.objects.clear();
|
|
2378
2547
|
this.gravityOverrideEntries.clear();
|
|
2548
|
+
this.followWindowEntries.clear();
|
|
2549
|
+
this.pressureObstacleIds.clear();
|
|
2379
2550
|
}
|
|
2380
2551
|
removeObjectsByTag(tag) {
|
|
2381
2552
|
const toRemove = [];
|
|
@@ -2533,14 +2704,14 @@ var OverlayScene = class {
|
|
|
2533
2704
|
}
|
|
2534
2705
|
}
|
|
2535
2706
|
/**
|
|
2536
|
-
* Set the velocity of an object.
|
|
2707
|
+
* Set the velocity of an object. Y axis uses physical convention: negative = down, positive = up.
|
|
2537
2708
|
* @param objectId - The ID of the object
|
|
2538
2709
|
* @param velocity - The velocity vector to set
|
|
2539
2710
|
*/
|
|
2540
2711
|
setVelocity(objectId, velocity) {
|
|
2541
2712
|
const entry = this.objects.get(objectId);
|
|
2542
2713
|
if (!entry) return;
|
|
2543
|
-
import_matter_js5.default.Body.setVelocity(entry.body, velocity);
|
|
2714
|
+
import_matter_js5.default.Body.setVelocity(entry.body, { x: velocity.x, y: -velocity.y });
|
|
2544
2715
|
}
|
|
2545
2716
|
/**
|
|
2546
2717
|
* Set the position of an object.
|
|
@@ -2763,7 +2934,7 @@ var OverlayScene = class {
|
|
|
2763
2934
|
const width = config.width ?? element.offsetWidth;
|
|
2764
2935
|
const height = config.height ?? element.offsetHeight;
|
|
2765
2936
|
const tags = config.tags ?? [];
|
|
2766
|
-
const isStatic =
|
|
2937
|
+
const isStatic = tags.includes("static");
|
|
2767
2938
|
const body = import_matter_js5.default.Bodies.rectangle(x, y, width, height, {
|
|
2768
2939
|
isStatic,
|
|
2769
2940
|
label: `dom-${crypto.randomUUID().slice(0, 8)}`,
|
|
@@ -2771,6 +2942,8 @@ var OverlayScene = class {
|
|
|
2771
2942
|
// Don't render the body, DOM element is the visual
|
|
2772
2943
|
});
|
|
2773
2944
|
const id = body.label;
|
|
2945
|
+
const entityTag = `dom-${id.slice(4, 8)}`;
|
|
2946
|
+
tags.push(entityTag);
|
|
2774
2947
|
let pressureThreshold;
|
|
2775
2948
|
if (config.pressureThreshold) {
|
|
2776
2949
|
pressureThreshold = typeof config.pressureThreshold.value === "number" ? config.pressureThreshold.value : config.pressureThreshold.value[0];
|
|
@@ -2784,6 +2957,7 @@ var OverlayScene = class {
|
|
|
2784
2957
|
id,
|
|
2785
2958
|
body,
|
|
2786
2959
|
tags,
|
|
2960
|
+
entityTag,
|
|
2787
2961
|
spawnTime: performance.now(),
|
|
2788
2962
|
pressureThreshold,
|
|
2789
2963
|
weight: config.weight ?? 1,
|
|
@@ -2798,12 +2972,13 @@ var OverlayScene = class {
|
|
|
2798
2972
|
this.updateDOMElementTransform(entry);
|
|
2799
2973
|
if (isStatic && pressureThreshold !== void 0) {
|
|
2800
2974
|
this.obstaclePressure.set(id, /* @__PURE__ */ new Set());
|
|
2975
|
+
this.pressureObstacleIds.add(id);
|
|
2801
2976
|
}
|
|
2802
2977
|
if (clicksRemaining !== void 0) {
|
|
2803
2978
|
const clickHandler = () => {
|
|
2804
2979
|
const currentEntry = this.objects.get(id);
|
|
2805
2980
|
if (!currentEntry) return;
|
|
2806
|
-
if (currentEntry.tags.includes("
|
|
2981
|
+
if (!currentEntry.tags.includes("static")) return;
|
|
2807
2982
|
if (currentEntry.clicksRemaining === void 0) return;
|
|
2808
2983
|
currentEntry.clicksRemaining--;
|
|
2809
2984
|
logger.debug("OverlayScene", `Click on DOM element: ${currentEntry.clicksRemaining} clicks remaining`);
|
|
@@ -2848,8 +3023,13 @@ var OverlayScene = class {
|
|
|
2848
3023
|
const fontName = config.fontName ?? this.getDefaultFont()?.name ?? "handwritten";
|
|
2849
3024
|
const basePath = `${fontsBasePath}${fontName}/`;
|
|
2850
3025
|
const stringTag = config.stringTag ?? `str-${crypto.randomUUID().slice(0, 8)}`;
|
|
2851
|
-
const
|
|
2852
|
-
const
|
|
3026
|
+
const isStatic = config.isStatic !== false;
|
|
3027
|
+
const baseTags = [...config.tags ?? []];
|
|
3028
|
+
if (isStatic && !baseTags.includes("static")) baseTags.push("static");
|
|
3029
|
+
else if (!isStatic) {
|
|
3030
|
+
const i = baseTags.indexOf("static");
|
|
3031
|
+
if (i !== -1) baseTags.splice(i, 1);
|
|
3032
|
+
}
|
|
2853
3033
|
const letterColor = config.letterColor;
|
|
2854
3034
|
const letterIds = [];
|
|
2855
3035
|
const letterMap = /* @__PURE__ */ new Map();
|
|
@@ -2991,8 +3171,10 @@ var OverlayScene = class {
|
|
|
2991
3171
|
const resolvedChar = charFileNames.get(char) ?? char;
|
|
2992
3172
|
const originalImageUrl = `${basePath}${resolvedChar}.png`;
|
|
2993
3173
|
const imageUrl = letterColor ? await tintImage(originalImageUrl, letterColor) : originalImageUrl;
|
|
2994
|
-
const tags = [...config.tags ?? [], stringTag, wordTag, `letter-${char}`, `letter-index-${globalCharIndex}`];
|
|
2995
3174
|
const id = crypto.randomUUID();
|
|
3175
|
+
const safeChar = char.match(/[a-zA-Z0-9]/) ? char.toLowerCase() : "sym";
|
|
3176
|
+
const entityTag = `letter-${safeChar}-${id.slice(0, 4)}`;
|
|
3177
|
+
const tags = [...baseTags, entityTag, stringTag, wordTag, `letter-${char}`, `letter-index-${globalCharIndex}`];
|
|
2996
3178
|
const objectConfig = {
|
|
2997
3179
|
x: centerX,
|
|
2998
3180
|
y: centerY,
|
|
@@ -3038,10 +3220,12 @@ var OverlayScene = class {
|
|
|
3038
3220
|
originalPosition: shadow || clicksRemaining !== void 0 ? { x: centerX, y: centerY } : void 0,
|
|
3039
3221
|
imageUrl: shadow || clicksRemaining !== void 0 ? imageUrl : void 0,
|
|
3040
3222
|
imageSize: shadow || clicksRemaining !== void 0 ? letterSize : void 0,
|
|
3041
|
-
clicksRemaining
|
|
3223
|
+
clicksRemaining,
|
|
3224
|
+
entityTag
|
|
3042
3225
|
};
|
|
3043
3226
|
this.objects.set(id, entry);
|
|
3044
3227
|
import_matter_js5.default.Composite.add(this.engine.world, result.body);
|
|
3228
|
+
if (pressureThreshold !== void 0) this.pressureObstacleIds.add(id);
|
|
3045
3229
|
letterIds.push(id);
|
|
3046
3230
|
letterMap.set(`${char}-${globalCharIndex}`, id);
|
|
3047
3231
|
debugInfo.push({
|
|
@@ -3092,15 +3276,13 @@ var OverlayScene = class {
|
|
|
3092
3276
|
}
|
|
3093
3277
|
/**
|
|
3094
3278
|
* Spawn falling text objects from a string.
|
|
3095
|
-
* Same as addTextObstacles but
|
|
3279
|
+
* Same as addTextObstacles but ensures objects are dynamic (removes 'static' tag if present).
|
|
3096
3280
|
*/
|
|
3097
3281
|
async spawnFallingTextObstacles(config) {
|
|
3098
|
-
|
|
3099
|
-
if (!tags.includes("falling")) tags.push("falling");
|
|
3100
|
-
return this.addTextObstacles({ ...config, tags });
|
|
3282
|
+
return this.addTextObstacles({ ...config, isStatic: false });
|
|
3101
3283
|
}
|
|
3102
3284
|
/**
|
|
3103
|
-
* Release all letters in a word (
|
|
3285
|
+
* Release all letters in a word (remove 'static' tag so they fall).
|
|
3104
3286
|
* @param wordTag - The word tag returned from addTextObstacles
|
|
3105
3287
|
*/
|
|
3106
3288
|
releaseTextObstacles(wordTag) {
|
|
@@ -3147,8 +3329,13 @@ var OverlayScene = class {
|
|
|
3147
3329
|
const { x, y, fontSize, fontUrl } = config;
|
|
3148
3330
|
const text = config.text.replace(/\\n/g, "\n");
|
|
3149
3331
|
const stringTag = config.stringTag ?? `str-${crypto.randomUUID().slice(0, 8)}`;
|
|
3150
|
-
const
|
|
3151
|
-
const
|
|
3332
|
+
const isStatic = config.isStatic !== false;
|
|
3333
|
+
const baseTags = [...config.tags ?? []];
|
|
3334
|
+
if (isStatic && !baseTags.includes("static")) baseTags.push("static");
|
|
3335
|
+
else if (!isStatic) {
|
|
3336
|
+
const i = baseTags.indexOf("static");
|
|
3337
|
+
if (i !== -1) baseTags.splice(i, 1);
|
|
3338
|
+
}
|
|
3152
3339
|
const fillColor = config.fillColor ?? "#ffffff";
|
|
3153
3340
|
const fillColors = config.fillColors;
|
|
3154
3341
|
const lineHeight = config.lineHeight ?? fontSize * 1.2;
|
|
@@ -3222,7 +3409,9 @@ var OverlayScene = class {
|
|
|
3222
3409
|
const wordTag = `${stringTag}-word-${currentWordIndex}`;
|
|
3223
3410
|
wordTagsSet.add(wordTag);
|
|
3224
3411
|
const id = crypto.randomUUID();
|
|
3225
|
-
const
|
|
3412
|
+
const safeChar = char.match(/[a-zA-Z0-9]/) ? char.toLowerCase() : "sym";
|
|
3413
|
+
const entityTag = `letter-${safeChar}-${id.slice(0, 4)}`;
|
|
3414
|
+
const tags = [...baseTags, entityTag, stringTag, wordTag, `letter-${char}`, `letter-index-${globalCharIndex}`];
|
|
3226
3415
|
const bbox = glyphData.boundingBox;
|
|
3227
3416
|
const glyphWidth = bbox ? bbox.x2 - bbox.x1 : glyphData.advanceWidth;
|
|
3228
3417
|
const glyphHeight = bbox ? bbox.y2 - bbox.y1 : fontSize;
|
|
@@ -3284,10 +3473,12 @@ var OverlayScene = class {
|
|
|
3284
3473
|
weight,
|
|
3285
3474
|
shadow,
|
|
3286
3475
|
originalPosition: shadow || clicksRemaining !== void 0 ? { x: body.position.x, y: body.position.y } : void 0,
|
|
3287
|
-
clicksRemaining
|
|
3476
|
+
clicksRemaining,
|
|
3477
|
+
entityTag
|
|
3288
3478
|
};
|
|
3289
3479
|
this.objects.set(id, entry);
|
|
3290
3480
|
import_matter_js5.default.Composite.add(this.engine.world, body);
|
|
3481
|
+
if (pressureThreshold !== void 0) this.pressureObstacleIds.add(id);
|
|
3291
3482
|
letterIds.push(id);
|
|
3292
3483
|
letterMap.set(`${char}-${globalCharIndex}`, id);
|
|
3293
3484
|
currentX += glyphData.advanceWidth;
|
|
@@ -3327,12 +3518,10 @@ var OverlayScene = class {
|
|
|
3327
3518
|
}
|
|
3328
3519
|
/**
|
|
3329
3520
|
* Spawn falling TTF text objects.
|
|
3330
|
-
* Same as addTTFTextObstacles but
|
|
3521
|
+
* Same as addTTFTextObstacles but ensures objects are dynamic (removes 'static' tag if present).
|
|
3331
3522
|
*/
|
|
3332
3523
|
async spawnFallingTTFTextObstacles(config) {
|
|
3333
|
-
|
|
3334
|
-
if (!tags.includes("falling")) tags.push("falling");
|
|
3335
|
-
return this.addTTFTextObstacles({ ...config, tags });
|
|
3524
|
+
return this.addTTFTextObstacles({ ...config, isStatic: false });
|
|
3336
3525
|
}
|
|
3337
3526
|
// ==================== COMBINED TAG METHODS ====================
|
|
3338
3527
|
removeAllByTag(tag) {
|
|
@@ -3443,37 +3632,29 @@ var OverlayScene = class {
|
|
|
3443
3632
|
entry.domElement.style.setProperty("top", `${y - height / 2}px`, "important");
|
|
3444
3633
|
entry.domElement.style.setProperty("transform", `rotate(${angleDeg}deg)`, "important");
|
|
3445
3634
|
}
|
|
3446
|
-
|
|
3635
|
+
/** TTL expiration + below-floor despawn in a single O(N) pass */
|
|
3636
|
+
checkExpiration() {
|
|
3447
3637
|
const now = performance.now();
|
|
3448
|
-
const expiredObjects = [];
|
|
3449
|
-
for (const [id, entry] of this.objects) {
|
|
3450
|
-
if (entry.ttl !== void 0 && now - entry.spawnTime >= entry.ttl) {
|
|
3451
|
-
expiredObjects.push(id);
|
|
3452
|
-
}
|
|
3453
|
-
}
|
|
3454
|
-
for (const id of expiredObjects) {
|
|
3455
|
-
this.removeObject(id);
|
|
3456
|
-
}
|
|
3457
|
-
}
|
|
3458
|
-
/** Despawn objects that have fallen below the floor by the configured distance */
|
|
3459
|
-
checkDespawnBelowFloor() {
|
|
3460
3638
|
const despawnDistance = this.config.despawnBelowFloor ?? 1;
|
|
3461
3639
|
const containerHeight = this.config.bounds.bottom - this.config.bounds.top;
|
|
3462
3640
|
const despawnY = this.config.bounds.bottom + containerHeight * despawnDistance;
|
|
3463
|
-
const
|
|
3641
|
+
const toRemove = [];
|
|
3464
3642
|
for (const [id, entry] of this.objects) {
|
|
3465
|
-
if (entry.
|
|
3466
|
-
|
|
3643
|
+
if (entry.ttl !== void 0 && now - entry.spawnTime >= entry.ttl) {
|
|
3644
|
+
toRemove.push(id);
|
|
3645
|
+
} else if (entry.body.position.y > despawnY) {
|
|
3646
|
+
toRemove.push(id);
|
|
3467
3647
|
}
|
|
3468
3648
|
}
|
|
3469
|
-
for (const id of
|
|
3649
|
+
for (const id of toRemove) {
|
|
3470
3650
|
this.removeObject(id);
|
|
3471
3651
|
}
|
|
3472
3652
|
}
|
|
3473
3653
|
fireUpdateCallbacks() {
|
|
3654
|
+
if (this.updateCallbacks.length === 0) return;
|
|
3474
3655
|
const objects = [];
|
|
3475
|
-
this.objects.
|
|
3476
|
-
if (entry.tags.includes("
|
|
3656
|
+
for (const entry of this.objects.values()) {
|
|
3657
|
+
if (!entry.tags.includes("static")) {
|
|
3477
3658
|
objects.push({
|
|
3478
3659
|
id: entry.id,
|
|
3479
3660
|
x: entry.body.position.x,
|
|
@@ -3482,32 +3663,38 @@ var OverlayScene = class {
|
|
|
3482
3663
|
tags: entry.tags
|
|
3483
3664
|
});
|
|
3484
3665
|
}
|
|
3485
|
-
}
|
|
3666
|
+
}
|
|
3486
3667
|
const data = { objects };
|
|
3487
|
-
this.updateCallbacks
|
|
3668
|
+
for (const cb of this.updateCallbacks) cb(data);
|
|
3488
3669
|
}
|
|
3489
3670
|
};
|
|
3490
3671
|
|
|
3491
3672
|
// src/tags.ts
|
|
3492
|
-
var
|
|
3673
|
+
var TAG_STATIC = "static";
|
|
3493
3674
|
var TAG_FOLLOW_WINDOW = "follow_window";
|
|
3494
3675
|
var TAG_GRABABLE = "grabable";
|
|
3495
3676
|
var TAG_GRAVITY_OVERRIDE = "gravity_override";
|
|
3677
|
+
var TAG_SPEED_OVERRIDE = "speed_override";
|
|
3678
|
+
var TAG_MASS_OVERRIDE = "mass_override";
|
|
3496
3679
|
var TAGS = {
|
|
3497
|
-
|
|
3680
|
+
STATIC: TAG_STATIC,
|
|
3498
3681
|
FOLLOW_WINDOW: TAG_FOLLOW_WINDOW,
|
|
3499
3682
|
GRABABLE: TAG_GRABABLE,
|
|
3500
|
-
GRAVITY_OVERRIDE: TAG_GRAVITY_OVERRIDE
|
|
3683
|
+
GRAVITY_OVERRIDE: TAG_GRAVITY_OVERRIDE,
|
|
3684
|
+
SPEED_OVERRIDE: TAG_SPEED_OVERRIDE,
|
|
3685
|
+
MASS_OVERRIDE: TAG_MASS_OVERRIDE
|
|
3501
3686
|
};
|
|
3502
3687
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3503
3688
|
0 && (module.exports = {
|
|
3504
3689
|
BackgroundManager,
|
|
3505
3690
|
OverlayScene,
|
|
3506
3691
|
TAGS,
|
|
3507
|
-
TAG_FALLING,
|
|
3508
3692
|
TAG_FOLLOW_WINDOW,
|
|
3509
3693
|
TAG_GRABABLE,
|
|
3510
3694
|
TAG_GRAVITY_OVERRIDE,
|
|
3695
|
+
TAG_MASS_OVERRIDE,
|
|
3696
|
+
TAG_SPEED_OVERRIDE,
|
|
3697
|
+
TAG_STATIC,
|
|
3511
3698
|
clearFontCache,
|
|
3512
3699
|
getGlyphData,
|
|
3513
3700
|
getKerning,
|