@blorkfield/overlay-core 0.10.0 → 0.11.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 CHANGED
@@ -381,6 +381,26 @@ scene.setEffect({
381
381
  });
382
382
  ```
383
383
 
384
+ ### Managing Effects
385
+
386
+ ```typescript
387
+ // Pause/resume an effect without removing it
388
+ scene.setEffectEnabled('my-rain', false); // pause
389
+ scene.setEffectEnabled('my-rain', true); // resume
390
+
391
+ // Check if an effect is enabled
392
+ scene.isEffectEnabled('my-rain'); // → true/false
393
+
394
+ // Get effect config
395
+ const effect = scene.getEffect('my-rain'); // → EffectConfig | undefined
396
+
397
+ // List all effect IDs
398
+ const ids = scene.getEffectIds(); // → string[]
399
+
400
+ // Remove an effect entirely
401
+ scene.removeEffect('my-rain');
402
+ ```
403
+
384
404
  ## Managing Objects
385
405
 
386
406
  ```typescript
@@ -608,7 +628,7 @@ scene.on('objectRemoved', (obj) => {
608
628
  console.log(`Removed: ${obj.id}`);
609
629
  });
610
630
 
611
- // Objects collided
631
+ // Objects collided (fires on collision start)
612
632
  scene.on('objectCollision', (a, b) => {
613
633
  console.log(`Collision: ${a.id} hit ${b.id}`);
614
634
  });
@@ -617,6 +637,23 @@ scene.on('objectCollision', (a, b) => {
617
637
  scene.off('objectSpawned', myCallback);
618
638
  ```
619
639
 
640
+ ### Querying Active Collisions
641
+
642
+ The scene tracks which objects are currently in contact, so you can query collision state at any time without maintaining your own bookkeeping:
643
+
644
+ ```typescript
645
+ // Get all object IDs currently touching a specific object
646
+ const touching = scene.getCollidingWith('my-object-id'); // string[]
647
+
648
+ // Get all active collision pairs
649
+ const pairs = scene.getActiveCollisions(); // Array<{ a: string, b: string }>
650
+ for (const { a, b } of pairs) {
651
+ console.log(`${a} is touching ${b}`);
652
+ }
653
+ ```
654
+
655
+ These reflect live physics state — pairs are added on `collisionStart` and removed on `collisionEnd`. Only tracked objects are included (floor, boundaries, and DOM obstacles are excluded).
656
+
620
657
  ## Font Setup
621
658
 
622
659
  ### Bundled Fonts
@@ -820,6 +857,23 @@ scene.setObjectMassOverride(id, 50); // now heavy
820
857
  scene.setObjectMassOverride(id, null); // restore natural mass
821
858
  ```
822
859
 
860
+ ### Scale
861
+
862
+ `setObjectScale(id, x, y)` resizes an object at runtime. Both the physics collision shape and sprite rendering are updated together. Scale is absolute — calling `setObjectScale(id, 2, 2)` always doubles the original size regardless of current scale. Scaling also changes body mass proportionally (area scales by x×y); use `setObjectMassOverride` after if you need a fixed mass.
863
+
864
+ ```typescript
865
+ // Uniform scale
866
+ scene.setObjectScale(id, 2, 2); // double size
867
+ scene.setObjectScale(id, 0.5, 0.5); // half size
868
+
869
+ // Non-uniform scale (independent x and y)
870
+ scene.setObjectScale(id, 3, 1); // stretch wide, keep height
871
+ scene.setObjectScale(id, 1, 0.5); // squash vertically
872
+
873
+ // Restore original size
874
+ scene.setObjectScale(id, 1, 1);
875
+ ```
876
+
823
877
  ## Examples
824
878
 
825
879
  Working examples are provided in the `/examples` directory:
@@ -863,6 +917,11 @@ import type {
863
917
  TTFTextObstacleConfig,
864
918
  TextAlign,
865
919
  TextBounds,
920
+ LetterDebugInfo,
921
+
922
+ // DOM obstacle types
923
+ DOMObstacleConfig,
924
+ DOMObstacleResult,
866
925
 
867
926
  // Effect types
868
927
  EffectConfig,
@@ -905,5 +964,5 @@ import type {
905
964
  } from '@blorkfield/overlay-core';
906
965
 
907
966
  // Tag constants (values, not types)
908
- import { TAGS, TAG_FALLING, TAG_GRABABLE, TAG_FOLLOW_WINDOW, TAG_GRAVITY_OVERRIDE } from '@blorkfield/overlay-core';
967
+ import { TAGS, TAG_STATIC, TAG_GRABABLE, TAG_FOLLOW_WINDOW, TAG_GRAVITY_OVERRIDE, TAG_SPEED_OVERRIDE, TAG_MASS_OVERRIDE } from '@blorkfield/overlay-core';
909
968
  ```
package/dist/index.cjs CHANGED
@@ -1466,6 +1466,8 @@ var OverlayScene = class {
1466
1466
  this.followWindowEntries = /* @__PURE__ */ new Set();
1467
1467
  // IDs of static objects that have pressure thresholds — empty = skip updatePressure entirely
1468
1468
  this.pressureObstacleIds = /* @__PURE__ */ new Set();
1469
+ // Active collision pairs: id -> Set of ids currently colliding with it
1470
+ this.activeCollisions = /* @__PURE__ */ new Map();
1469
1471
  /** Handle mouse down - start grab via programmatic API */
1470
1472
  this.handleMouseDown = (event) => {
1471
1473
  const rect = this.canvas.getBoundingClientRect();
@@ -1532,6 +1534,10 @@ var OverlayScene = class {
1532
1534
  const entryA = this.findObjectByBody(pair.bodyA);
1533
1535
  const entryB = this.findObjectByBody(pair.bodyB);
1534
1536
  if (entryA && entryB) {
1537
+ if (!this.activeCollisions.has(entryA.id)) this.activeCollisions.set(entryA.id, /* @__PURE__ */ new Set());
1538
+ if (!this.activeCollisions.has(entryB.id)) this.activeCollisions.set(entryB.id, /* @__PURE__ */ new Set());
1539
+ this.activeCollisions.get(entryA.id).add(entryB.id);
1540
+ this.activeCollisions.get(entryB.id).add(entryA.id);
1535
1541
  this.emitLifecycleEvent(
1536
1542
  "objectCollision",
1537
1543
  this.toObjectState(entryA),
@@ -1540,6 +1546,16 @@ var OverlayScene = class {
1540
1546
  }
1541
1547
  }
1542
1548
  };
1549
+ this.handleCollisionEnd = (event) => {
1550
+ for (const pair of event.pairs) {
1551
+ const entryA = this.findObjectByBody(pair.bodyA);
1552
+ const entryB = this.findObjectByBody(pair.bodyB);
1553
+ if (entryA && entryB) {
1554
+ this.activeCollisions.get(entryA.id)?.delete(entryB.id);
1555
+ this.activeCollisions.get(entryB.id)?.delete(entryA.id);
1556
+ }
1557
+ }
1558
+ };
1543
1559
  // ==================== PRIVATE ====================
1544
1560
  this.loop = () => {
1545
1561
  const substepDelta = 1e3 / 60 / this.substeps;
@@ -1668,6 +1684,7 @@ var OverlayScene = class {
1668
1684
  import_matter_js5.default.Events.on(this.render, "beforeRender", this.handleBeforeRender);
1669
1685
  import_matter_js5.default.Events.on(this.render, "afterRender", this.handleAfterRender);
1670
1686
  import_matter_js5.default.Events.on(this.engine, "collisionStart", this.handleCollisionStart);
1687
+ import_matter_js5.default.Events.on(this.engine, "collisionEnd", this.handleCollisionEnd);
1671
1688
  }
1672
1689
  static createContainer(parent, options = {}) {
1673
1690
  const canvas = document.createElement("canvas");
@@ -2071,6 +2088,7 @@ var OverlayScene = class {
2071
2088
  import_matter_js5.default.Events.off(this.render, "beforeRender", this.handleBeforeRender);
2072
2089
  import_matter_js5.default.Events.off(this.render, "afterRender", this.handleAfterRender);
2073
2090
  import_matter_js5.default.Events.off(this.engine, "collisionStart", this.handleCollisionStart);
2091
+ import_matter_js5.default.Events.off(this.engine, "collisionEnd", this.handleCollisionEnd);
2074
2092
  import_matter_js5.default.Engine.clear(this.engine);
2075
2093
  this.objects.clear();
2076
2094
  this.gravityOverrideEntries.clear();
@@ -2081,6 +2099,7 @@ var OverlayScene = class {
2081
2099
  this.pressureLogTimer = 0;
2082
2100
  this.floorSegmentPressure.clear();
2083
2101
  this.collapsedSegments.clear();
2102
+ this.activeCollisions.clear();
2084
2103
  this.updateCallbacks = [];
2085
2104
  this.lifecycleCallbacks.objectSpawned = [];
2086
2105
  this.lifecycleCallbacks.objectRemoved = [];
@@ -2532,6 +2551,13 @@ var OverlayScene = class {
2532
2551
  this.gravityOverrideEntries.delete(entry);
2533
2552
  this.followWindowEntries.delete(entry);
2534
2553
  this.pressureObstacleIds.delete(id);
2554
+ const colliding = this.activeCollisions.get(id);
2555
+ if (colliding) {
2556
+ for (const otherId of colliding) {
2557
+ this.activeCollisions.get(otherId)?.delete(id);
2558
+ }
2559
+ this.activeCollisions.delete(id);
2560
+ }
2535
2561
  this.objects.delete(id);
2536
2562
  }
2537
2563
  removeObjects(ids) {
@@ -2781,6 +2807,32 @@ var OverlayScene = class {
2781
2807
  const idx = arr.indexOf(callback);
2782
2808
  if (idx !== -1) arr.splice(idx, 1);
2783
2809
  }
2810
+ /**
2811
+ * Get the IDs of all objects currently colliding with a given object.
2812
+ * @param id - The ID of the object to query
2813
+ * @returns Array of IDs of objects currently in contact with it
2814
+ */
2815
+ getCollidingWith(id) {
2816
+ return Array.from(this.activeCollisions.get(id) ?? []);
2817
+ }
2818
+ /**
2819
+ * Get all currently active collision pairs.
2820
+ * @returns Array of `{ a, b }` pairs where `a` and `b` are colliding object IDs
2821
+ */
2822
+ getActiveCollisions() {
2823
+ const pairs = [];
2824
+ const seen = /* @__PURE__ */ new Set();
2825
+ for (const [id, others] of this.activeCollisions) {
2826
+ for (const otherId of others) {
2827
+ const key = id < otherId ? `${id}:${otherId}` : `${otherId}:${id}`;
2828
+ if (!seen.has(key)) {
2829
+ seen.add(key);
2830
+ pairs.push({ a: id, b: otherId });
2831
+ }
2832
+ }
2833
+ }
2834
+ return pairs;
2835
+ }
2784
2836
  /** Create ObjectState from an ObjectEntry */
2785
2837
  toObjectState(entry) {
2786
2838
  return {