@blorkfield/overlay-core 0.8.9 → 0.8.10

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
@@ -1451,6 +1451,12 @@ var OverlayScene = class {
1451
1451
  this.lastGrabMousePosition = null;
1452
1452
  this.grabbedWasDynamic = false;
1453
1453
  this.grabVelocity = { x: 0, y: 0 };
1454
+ // Position history for sweep-based grab detection (catches fast-moving bodies)
1455
+ this.bodyPositionHistory = /* @__PURE__ */ new Map();
1456
+ this.grabHistoryFrames = 5;
1457
+ this.grabHistoryRadius = 20;
1458
+ // Number of physics substeps per frame — more substeps = better collision at high speeds, more CPU
1459
+ this.substeps = 2;
1454
1460
  /** Handle mouse down - start grab via programmatic API */
1455
1461
  this.handleMouseDown = (event) => {
1456
1462
  const rect = this.canvas.getBoundingClientRect();
@@ -1527,6 +1533,10 @@ var OverlayScene = class {
1527
1533
  };
1528
1534
  // ==================== PRIVATE ====================
1529
1535
  this.loop = () => {
1536
+ const substepDelta = 1e3 / 60 / this.substeps;
1537
+ for (let i = 0; i < this.substeps; i++) {
1538
+ import_matter_js5.default.Engine.update(this.engine, substepDelta);
1539
+ }
1530
1540
  this.effectManager.update();
1531
1541
  this.checkTTLExpiration();
1532
1542
  this.checkDespawnBelowFloor();
@@ -1570,6 +1580,14 @@ var OverlayScene = class {
1570
1580
  if (entry.domElement && entry.tags.includes("falling")) {
1571
1581
  this.updateDOMElementTransform(entry);
1572
1582
  }
1583
+ if (entry.tags.includes("grabable") && !entry.body.isStatic) {
1584
+ const history = this.bodyPositionHistory.get(entry.body.id) ?? [];
1585
+ history.push({ x: entry.body.position.x, y: entry.body.position.y });
1586
+ if (history.length > this.grabHistoryFrames) {
1587
+ history.shift();
1588
+ }
1589
+ this.bodyPositionHistory.set(entry.body.id, history);
1590
+ }
1573
1591
  }
1574
1592
  if (!this.config.debug) {
1575
1593
  this.drawTTFGlyphs();
@@ -1831,7 +1849,7 @@ var OverlayScene = class {
1831
1849
  }
1832
1850
  }
1833
1851
  if (parts.length > 0) {
1834
- console.log("[Pressure]", parts.join(" "));
1852
+ logger.debug("[Pressure]", parts.join(" "));
1835
1853
  }
1836
1854
  }
1837
1855
  /** Calculate weighted pressure from a set of object IDs */
@@ -2008,7 +2026,6 @@ var OverlayScene = class {
2008
2026
  }
2009
2027
  start() {
2010
2028
  import_matter_js5.default.Render.run(this.render);
2011
- import_matter_js5.default.Runner.run(this.runner, this.engine);
2012
2029
  this.loop();
2013
2030
  }
2014
2031
  stop() {
@@ -2382,6 +2399,7 @@ var OverlayScene = class {
2382
2399
  import_matter_js5.default.Composite.allBodies(this.engine.world),
2383
2400
  position
2384
2401
  );
2402
+ logger.debug("OverlayScene", "Grabbed position " + position + ', had "' + bodies.length + '"');
2385
2403
  for (const body of bodies) {
2386
2404
  const entry = this.findObjectByBody(body);
2387
2405
  if (entry && entry.tags.includes("grabable")) {
@@ -2393,6 +2411,24 @@ var OverlayScene = class {
2393
2411
  return entry.id;
2394
2412
  }
2395
2413
  }
2414
+ const r2 = this.grabHistoryRadius * this.grabHistoryRadius;
2415
+ for (const entry of this.objects.values()) {
2416
+ if (!entry.tags.includes("grabable")) continue;
2417
+ const history = this.bodyPositionHistory.get(entry.body.id);
2418
+ if (!history) continue;
2419
+ for (const pastPos of history) {
2420
+ const dx = position.x - pastPos.x;
2421
+ const dy = position.y - pastPos.y;
2422
+ if (dx * dx + dy * dy <= r2) {
2423
+ this.grabbedObjectId = entry.id;
2424
+ this.lastGrabMousePosition = { x: position.x, y: position.y };
2425
+ this.grabVelocity = { x: 0, y: 0 };
2426
+ this.grabbedWasDynamic = !entry.body.isStatic;
2427
+ import_matter_js5.default.Body.setStatic(entry.body, true);
2428
+ return entry.id;
2429
+ }
2430
+ }
2431
+ }
2396
2432
  return null;
2397
2433
  }
2398
2434
  /**
@@ -2406,6 +2442,7 @@ var OverlayScene = class {
2406
2442
  import_matter_js5.default.Sleeping.set(entry.body, false);
2407
2443
  import_matter_js5.default.Body.setVelocity(entry.body, this.grabVelocity);
2408
2444
  import_matter_js5.default.Body.setAngularVelocity(entry.body, 0);
2445
+ this.bodyPositionHistory.delete(entry.body.id);
2409
2446
  }
2410
2447
  }
2411
2448
  this.grabbedObjectId = null;