@blorkfield/overlay-core 0.11.1 → 0.11.3
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 +5 -3
- package/dist/index.cjs +91 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +91 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,7 +42,7 @@ scene.spawnObject({ tags: [STATIC], ... });
|
|
|
42
42
|
|----------|-------|----------|
|
|
43
43
|
| `TAG_STATIC` / `TAGS.STATIC` | `'static'` | Object is a static obstacle, not affected by gravity. Without this tag, objects are dynamic by default. |
|
|
44
44
|
| `TAG_FOLLOW_WINDOW` / `TAGS.FOLLOW_WINDOW` | `'follow_window'` | Object walks toward a target when grounded (default: mouse) |
|
|
45
|
-
| `TAG_GRABABLE` / `TAGS.GRABABLE` | `'grabable'` | Object can be grabbed and moved with mouse |
|
|
45
|
+
| `TAG_GRABABLE` / `TAGS.GRABABLE` | `'grabable'` | Object can be grabbed and moved with mouse or touch |
|
|
46
46
|
| `TAG_GRAVITY_OVERRIDE` / `TAGS.GRAVITY_OVERRIDE` | `'gravity_override'` | Object uses its own gravity vector instead of scene gravity |
|
|
47
47
|
| `TAG_SPEED_OVERRIDE` / `TAGS.SPEED_OVERRIDE` | `'speed_override'` | Multiplies movement speed for `follow_window` and future movement behaviors. Negative = run away from target. |
|
|
48
48
|
| `TAG_MASS_OVERRIDE` / `TAGS.MASS_OVERRIDE` | `'mass_override'` | Overrides the physics mass. Higher mass resists follow forces more; lower mass allows the follow force to overcome gravity. |
|
|
@@ -469,7 +469,9 @@ scene.setObjectScale(id, 2, 0.5); // stretch wide, squash tall
|
|
|
469
469
|
scene.setObjectScale(id, 1, 1); // restore original size
|
|
470
470
|
```
|
|
471
471
|
|
|
472
|
-
## Mouse
|
|
472
|
+
## Mouse / Touch and Grab API
|
|
473
|
+
|
|
474
|
+
Mouse and touch input are handled automatically. On touch devices, a single finger grabs and drags `grabable` objects; a tap on a static object with `clicksToFall` set triggers the click-to-fall behavior. Two-finger gestures are ignored by the canvas so the browser can handle scroll and pinch-zoom normally. Touching over empty canvas (no `grabable` object) also passes through to the browser.
|
|
473
475
|
|
|
474
476
|
For scenarios where mouse input comes from an external source (e.g., system-wide mouse capture via WebSocket), you can programmatically control mouse position and grab/release behavior. This is useful when the canvas is positioned with an offset from the screen origin.
|
|
475
477
|
|
|
@@ -802,7 +804,7 @@ Tags are the source of truth for all behavior. Boolean tags (presence = active,
|
|
|
802
804
|
| Tag | Behavior |
|
|
803
805
|
|-----|----------|
|
|
804
806
|
| `static` | Static obstacle, not affected by gravity. Absent by default — objects are dynamic unless tagged static. |
|
|
805
|
-
| `grabable` | Can be grabbed and dragged with the mouse |
|
|
807
|
+
| `grabable` | Can be grabbed and dragged with the mouse or touch |
|
|
806
808
|
| `follow_window` | Always applies a directional force toward its target (in all axes). Gravity determines whether the entity can actually reach targets above/below it. |
|
|
807
809
|
| `gravity_override` | Uses its own gravity vector instead of scene gravity (value set via `setObjectGravityOverride`) |
|
|
808
810
|
| `speed_override` | Multiplies movement speed for `follow_window` and future movement behaviors (value set via `setObjectSpeedOverride`). Negative = runs away from target. Default multiplier: 1 |
|
package/dist/index.cjs
CHANGED
|
@@ -513,6 +513,17 @@ function createBodyFromVertices(id, x, y, vertices, renderOptions) {
|
|
|
513
513
|
render: renderOptions
|
|
514
514
|
});
|
|
515
515
|
import_matter_js2.default.Body.setPosition(body, { x, y });
|
|
516
|
+
if (!body.parts.every((part) => part.vertices && part.vertices.length >= 3)) {
|
|
517
|
+
logger.warn(LOG_PREFIX2, `fromVertices produced degenerate parts, falling back to circle`, { id });
|
|
518
|
+
return import_matter_js2.default.Bodies.circle(x, y, DEFAULT_RADIUS, {
|
|
519
|
+
restitution: 0.3,
|
|
520
|
+
friction: 0.1,
|
|
521
|
+
frictionAir: 0.01,
|
|
522
|
+
density: 5e-3,
|
|
523
|
+
label: `entity:${id}`,
|
|
524
|
+
render: renderOptions
|
|
525
|
+
});
|
|
526
|
+
}
|
|
516
527
|
return body;
|
|
517
528
|
}
|
|
518
529
|
function createBoundariesWithFloorConfig(bounds, floorConfig) {
|
|
@@ -704,11 +715,26 @@ async function createBoxObstacleWithInfo(id, config, isStatic = true) {
|
|
|
704
715
|
}
|
|
705
716
|
}
|
|
706
717
|
});
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
718
|
+
if (!body.parts.every((part) => part.vertices && part.vertices.length >= 3)) {
|
|
719
|
+
logger.warn(LOG_PREFIX2, `fromVertices produced degenerate obstacle, falling back to rectangle`, { id });
|
|
720
|
+
body = import_matter_js2.default.Bodies.rectangle(config.x, config.y, scaledWidth, scaledHeight, {
|
|
721
|
+
isStatic,
|
|
722
|
+
label: `obstacle:${id}`,
|
|
723
|
+
render: {
|
|
724
|
+
sprite: {
|
|
725
|
+
texture: config.imageUrl,
|
|
726
|
+
xScale: spriteScale,
|
|
727
|
+
yScale: spriteScale
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
});
|
|
731
|
+
} else {
|
|
732
|
+
const spriteOffsetX = (config.x - body.position.x) / scaledWidth;
|
|
733
|
+
const spriteOffsetY = (config.y - body.position.y) / scaledHeight;
|
|
734
|
+
if (body.render.sprite) {
|
|
735
|
+
body.render.sprite.xOffset = 0.5 + spriteOffsetX;
|
|
736
|
+
body.render.sprite.yOffset = 0.5 + spriteOffsetY;
|
|
737
|
+
}
|
|
712
738
|
}
|
|
713
739
|
} else {
|
|
714
740
|
body = import_matter_js2.default.Bodies.rectangle(config.x, config.y, scaledWidth, scaledHeight, {
|
|
@@ -757,6 +783,10 @@ async function createObstacleAsync(id, config, isStatic = true) {
|
|
|
757
783
|
}
|
|
758
784
|
});
|
|
759
785
|
import_matter_js2.default.Body.setPosition(body, { x: config.x, y: config.y });
|
|
786
|
+
if (!body.parts.every((part) => part.vertices && part.vertices.length >= 3)) {
|
|
787
|
+
logger.warn(LOG_PREFIX2, `Image obstacle fromVertices degenerate, falling back to rectangle`, { id });
|
|
788
|
+
return createObstacle(id, config, isStatic);
|
|
789
|
+
}
|
|
760
790
|
return body;
|
|
761
791
|
}
|
|
762
792
|
logger.warn(LOG_PREFIX2, `Image obstacle shape extraction failed, falling back to rectangle`, { id });
|
|
@@ -1492,6 +1522,10 @@ var OverlayScene = class {
|
|
|
1492
1522
|
const rect = this.canvas.getBoundingClientRect();
|
|
1493
1523
|
const x = event.clientX - rect.left;
|
|
1494
1524
|
const y = event.clientY - rect.top;
|
|
1525
|
+
this.handleTap(x, y);
|
|
1526
|
+
};
|
|
1527
|
+
/** Shared tap logic for mouse click and touch tap */
|
|
1528
|
+
this.handleTap = (x, y) => {
|
|
1495
1529
|
const bodies = import_matter_js5.default.Query.point(
|
|
1496
1530
|
import_matter_js5.default.Composite.allBodies(this.engine.world),
|
|
1497
1531
|
{ x, y }
|
|
@@ -1509,6 +1543,42 @@ var OverlayScene = class {
|
|
|
1509
1543
|
}
|
|
1510
1544
|
}
|
|
1511
1545
|
};
|
|
1546
|
+
/** Handle touch start — single finger grabs, multi-finger passes through for scroll/zoom */
|
|
1547
|
+
this.handleTouchStart = (event) => {
|
|
1548
|
+
if (event.touches.length >= 2) return;
|
|
1549
|
+
const touch = event.touches[0];
|
|
1550
|
+
const rect = this.canvas.getBoundingClientRect();
|
|
1551
|
+
const x = touch.clientX - rect.left;
|
|
1552
|
+
const y = touch.clientY - rect.top;
|
|
1553
|
+
this.followTargets.set("mouse", { x, y });
|
|
1554
|
+
const grabbed = this.startGrab();
|
|
1555
|
+
if (grabbed) event.preventDefault();
|
|
1556
|
+
};
|
|
1557
|
+
/** Handle touch move — only tracks if something is grabbed */
|
|
1558
|
+
this.handleTouchMove = (event) => {
|
|
1559
|
+
if (event.touches.length >= 2) return;
|
|
1560
|
+
if (!this.grabbedObjectId) return;
|
|
1561
|
+
event.preventDefault();
|
|
1562
|
+
const touch = event.touches[0];
|
|
1563
|
+
const rect = this.canvas.getBoundingClientRect();
|
|
1564
|
+
const x = touch.clientX - rect.left;
|
|
1565
|
+
const y = touch.clientY - rect.top;
|
|
1566
|
+
this.followTargets.set("mouse", { x, y });
|
|
1567
|
+
};
|
|
1568
|
+
/** Handle touch end/cancel — release grab; if it was a tap (no grab), trigger click-to-fall */
|
|
1569
|
+
this.handleTouchEnd = (event) => {
|
|
1570
|
+
const wasGrabbed = this.grabbedObjectId !== null;
|
|
1571
|
+
this.endGrab();
|
|
1572
|
+
if (wasGrabbed) {
|
|
1573
|
+
event.preventDefault();
|
|
1574
|
+
return;
|
|
1575
|
+
}
|
|
1576
|
+
if (event.changedTouches.length > 0) {
|
|
1577
|
+
const touch = event.changedTouches[0];
|
|
1578
|
+
const rect = this.canvas.getBoundingClientRect();
|
|
1579
|
+
this.handleTap(touch.clientX - rect.left, touch.clientY - rect.top);
|
|
1580
|
+
}
|
|
1581
|
+
};
|
|
1512
1582
|
/**
|
|
1513
1583
|
* Handler for Matter.js beforeRender event.
|
|
1514
1584
|
* Draws base background layers (color + image) before physics objects.
|
|
@@ -1661,6 +1731,10 @@ var OverlayScene = class {
|
|
|
1661
1731
|
canvas.addEventListener("mousemove", this.handleMouseMove);
|
|
1662
1732
|
canvas.addEventListener("mouseup", this.handleMouseUp);
|
|
1663
1733
|
canvas.addEventListener("click", this.handleCanvasClick);
|
|
1734
|
+
canvas.addEventListener("touchstart", this.handleTouchStart, { passive: false });
|
|
1735
|
+
canvas.addEventListener("touchmove", this.handleTouchMove, { passive: false });
|
|
1736
|
+
canvas.addEventListener("touchend", this.handleTouchEnd, { passive: false });
|
|
1737
|
+
canvas.addEventListener("touchcancel", this.handleTouchEnd, { passive: false });
|
|
1664
1738
|
this.effectManager = new EffectManager(
|
|
1665
1739
|
this.config.bounds,
|
|
1666
1740
|
(cfg) => this.spawnObjectAsync(cfg),
|
|
@@ -2085,6 +2159,10 @@ var OverlayScene = class {
|
|
|
2085
2159
|
this.canvas.removeEventListener("mousemove", this.handleMouseMove);
|
|
2086
2160
|
this.canvas.removeEventListener("mouseup", this.handleMouseUp);
|
|
2087
2161
|
this.canvas.removeEventListener("click", this.handleCanvasClick);
|
|
2162
|
+
this.canvas.removeEventListener("touchstart", this.handleTouchStart);
|
|
2163
|
+
this.canvas.removeEventListener("touchmove", this.handleTouchMove);
|
|
2164
|
+
this.canvas.removeEventListener("touchend", this.handleTouchEnd);
|
|
2165
|
+
this.canvas.removeEventListener("touchcancel", this.handleTouchEnd);
|
|
2088
2166
|
import_matter_js5.default.Events.off(this.render, "beforeRender", this.handleBeforeRender);
|
|
2089
2167
|
import_matter_js5.default.Events.off(this.render, "afterRender", this.handleAfterRender);
|
|
2090
2168
|
import_matter_js5.default.Events.off(this.engine, "collisionStart", this.handleCollisionStart);
|
|
@@ -3473,13 +3551,20 @@ var OverlayScene = class {
|
|
|
3473
3551
|
x: currentX + v.x,
|
|
3474
3552
|
y: currentY + v.y
|
|
3475
3553
|
}));
|
|
3476
|
-
|
|
3554
|
+
let body = import_matter_js5.default.Bodies.fromVertices(glyphCenterX, glyphCenterY, [worldVertices], {
|
|
3477
3555
|
isStatic,
|
|
3478
3556
|
label: `obstacle:${id}`,
|
|
3479
3557
|
render: {
|
|
3480
3558
|
visible: false
|
|
3481
3559
|
}
|
|
3482
3560
|
});
|
|
3561
|
+
if (!body.parts.every((part) => part.vertices && part.vertices.length >= 3)) {
|
|
3562
|
+
body = import_matter_js5.default.Bodies.rectangle(glyphCenterX, glyphCenterY, Math.max(glyphWidth, 1), Math.max(Math.abs(glyphHeight), 1), {
|
|
3563
|
+
isStatic,
|
|
3564
|
+
label: `obstacle:${id}`,
|
|
3565
|
+
render: { visible: false }
|
|
3566
|
+
});
|
|
3567
|
+
}
|
|
3483
3568
|
const offsetX = currentX - body.position.x;
|
|
3484
3569
|
const offsetY = currentY - body.position.y;
|
|
3485
3570
|
let pressureThreshold;
|