@blorkfield/overlay-core 0.8.9 → 0.8.11

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 CHANGED
@@ -389,6 +389,8 @@ The offset calculation is your responsibility - overlay-core uses whatever posit
389
389
 
390
390
  Grab uses delta-based movement: when grabbed, the entity and mouse become linked. The entity moves BY the same amount as the mouse moves, not TO the mouse position. This ensures the entity stays at its original position on grab and follows mouse movement naturally.
391
391
 
392
+ Grab detection uses a two-pass approach to handle fast-moving bodies. The first pass does an exact point query at the click position. If that misses (the body tunneled through the cursor between frames), a second pass sweeps the body's recent position history (last 5 frames, 20px radius) to catch it. This means you can grab entities even when they are moving quickly.
393
+
392
394
  ```typescript
393
395
  // Grab object at current mouse position (only 'grabable' tagged objects)
394
396
  const grabbedId = scene.startGrab();
package/dist/index.cjs CHANGED
@@ -1424,7 +1424,6 @@ var OverlayScene = class {
1424
1424
  this.boundaries = [];
1425
1425
  this.updateCallbacks = [];
1426
1426
  this.animationFrameId = null;
1427
- this.mouse = null;
1428
1427
  this.fonts = [];
1429
1428
  this.fontsInitialized = false;
1430
1429
  this.letterDebugInfo = /* @__PURE__ */ new Map();
@@ -1451,6 +1450,12 @@ var OverlayScene = class {
1451
1450
  this.lastGrabMousePosition = null;
1452
1451
  this.grabbedWasDynamic = false;
1453
1452
  this.grabVelocity = { x: 0, y: 0 };
1453
+ // Position history for sweep-based grab detection (catches fast-moving bodies)
1454
+ this.bodyPositionHistory = /* @__PURE__ */ new Map();
1455
+ this.grabHistoryFrames = 5;
1456
+ this.grabHistoryRadius = 20;
1457
+ // Number of physics substeps per frame — more substeps = better collision at high speeds, more CPU
1458
+ this.substeps = 2;
1454
1459
  /** Handle mouse down - start grab via programmatic API */
1455
1460
  this.handleMouseDown = (event) => {
1456
1461
  const rect = this.canvas.getBoundingClientRect();
@@ -1527,13 +1532,14 @@ var OverlayScene = class {
1527
1532
  };
1528
1533
  // ==================== PRIVATE ====================
1529
1534
  this.loop = () => {
1535
+ const substepDelta = 1e3 / 60 / this.substeps;
1536
+ for (let i = 0; i < this.substeps; i++) {
1537
+ import_matter_js5.default.Engine.update(this.engine, substepDelta);
1538
+ }
1530
1539
  this.effectManager.update();
1531
1540
  this.checkTTLExpiration();
1532
1541
  this.checkDespawnBelowFloor();
1533
1542
  this.updatePressure();
1534
- if (!this.followTargets.has("mouse") && this.mouse) {
1535
- this.followTargets.set("mouse", { x: this.mouse.position.x, y: this.mouse.position.y });
1536
- }
1537
1543
  if (this.grabbedObjectId && this.lastGrabMousePosition) {
1538
1544
  const entry = this.objects.get(this.grabbedObjectId);
1539
1545
  const mouseTarget = this.followTargets.get("mouse");
@@ -1570,6 +1576,14 @@ var OverlayScene = class {
1570
1576
  if (entry.domElement && entry.tags.includes("falling")) {
1571
1577
  this.updateDOMElementTransform(entry);
1572
1578
  }
1579
+ if (entry.tags.includes("grabable") && !entry.body.isStatic) {
1580
+ const history = this.bodyPositionHistory.get(entry.body.id) ?? [];
1581
+ history.push({ x: entry.body.position.x, y: entry.body.position.y });
1582
+ if (history.length > this.grabHistoryFrames) {
1583
+ history.shift();
1584
+ }
1585
+ this.bodyPositionHistory.set(entry.body.id, history);
1586
+ }
1573
1587
  }
1574
1588
  if (!this.config.debug) {
1575
1589
  this.drawTTFGlyphs();
@@ -1595,7 +1609,6 @@ var OverlayScene = class {
1595
1609
  this.floorSegments = boundariesResult.floorSegments;
1596
1610
  import_matter_js5.default.Composite.add(this.engine.world, this.boundaries);
1597
1611
  this.checkInitialFloorIntegrity();
1598
- this.mouse = import_matter_js5.default.Mouse.create(canvas);
1599
1612
  canvas.addEventListener("mousedown", this.handleMouseDown);
1600
1613
  canvas.addEventListener("mousemove", this.handleMouseMove);
1601
1614
  canvas.addEventListener("mouseup", this.handleMouseUp);
@@ -1831,7 +1844,7 @@ var OverlayScene = class {
1831
1844
  }
1832
1845
  }
1833
1846
  if (parts.length > 0) {
1834
- console.log("[Pressure]", parts.join(" "));
1847
+ logger.debug("[Pressure]", parts.join(" "));
1835
1848
  }
1836
1849
  }
1837
1850
  /** Calculate weighted pressure from a set of object IDs */
@@ -2008,7 +2021,6 @@ var OverlayScene = class {
2008
2021
  }
2009
2022
  start() {
2010
2023
  import_matter_js5.default.Render.run(this.render);
2011
- import_matter_js5.default.Runner.run(this.runner, this.engine);
2012
2024
  this.loop();
2013
2025
  }
2014
2026
  stop() {
@@ -2375,13 +2387,13 @@ var OverlayScene = class {
2375
2387
  * @returns The ID of the grabbed object, or null if no grabable object at position
2376
2388
  */
2377
2389
  startGrab() {
2378
- const mouseTarget = this.followTargets.get("mouse");
2379
- const position = mouseTarget ?? (this.mouse ? { x: this.mouse.position.x, y: this.mouse.position.y } : null);
2390
+ const position = this.followTargets.get("mouse") ?? null;
2380
2391
  if (!position) return null;
2381
2392
  const bodies = import_matter_js5.default.Query.point(
2382
2393
  import_matter_js5.default.Composite.allBodies(this.engine.world),
2383
2394
  position
2384
2395
  );
2396
+ logger.debug("OverlayScene", "Grabbed position " + position + ', had "' + bodies.length + '"');
2385
2397
  for (const body of bodies) {
2386
2398
  const entry = this.findObjectByBody(body);
2387
2399
  if (entry && entry.tags.includes("grabable")) {
@@ -2393,6 +2405,24 @@ var OverlayScene = class {
2393
2405
  return entry.id;
2394
2406
  }
2395
2407
  }
2408
+ const r2 = this.grabHistoryRadius * this.grabHistoryRadius;
2409
+ for (const entry of this.objects.values()) {
2410
+ if (!entry.tags.includes("grabable")) continue;
2411
+ const history = this.bodyPositionHistory.get(entry.body.id);
2412
+ if (!history) continue;
2413
+ for (const pastPos of history) {
2414
+ const dx = position.x - pastPos.x;
2415
+ const dy = position.y - pastPos.y;
2416
+ if (dx * dx + dy * dy <= r2) {
2417
+ this.grabbedObjectId = entry.id;
2418
+ this.lastGrabMousePosition = { x: position.x, y: position.y };
2419
+ this.grabVelocity = { x: 0, y: 0 };
2420
+ this.grabbedWasDynamic = !entry.body.isStatic;
2421
+ import_matter_js5.default.Body.setStatic(entry.body, true);
2422
+ return entry.id;
2423
+ }
2424
+ }
2425
+ }
2396
2426
  return null;
2397
2427
  }
2398
2428
  /**
@@ -2406,6 +2436,7 @@ var OverlayScene = class {
2406
2436
  import_matter_js5.default.Sleeping.set(entry.body, false);
2407
2437
  import_matter_js5.default.Body.setVelocity(entry.body, this.grabVelocity);
2408
2438
  import_matter_js5.default.Body.setAngularVelocity(entry.body, 0);
2439
+ this.bodyPositionHistory.delete(entry.body.id);
2409
2440
  }
2410
2441
  }
2411
2442
  this.grabbedObjectId = null;