rig-play 1.5.0 → 1.6.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
@@ -263,6 +263,30 @@ box, an inverse clip removes nothing, and bounding boxes, paths and points do
263
263
  not draw and do not count. Both cost nothing until called and read the same
264
264
  numbers in `dom` and `canvas` mode.
265
265
 
266
+ ### Drawing some slots only
267
+
268
+ `slots` is a draw filter — an allow list of slot names or a predicate — settable up
269
+ front and on a live player. The pose is the whole rig's whatever the filter:
270
+ bones, constraints, physics, `bone()`, `adjust`, events and tracks see no filter;
271
+ only the draw does, in both modes, and `bounds()` reads the drawn slots. A
272
+ clipping slot the filter leaves out does not clip. An allow list naming a slot
273
+ the document does not have is refused by name.
274
+
275
+ ```ts
276
+ const hand = new RigPlayer(frontRoot, assets, { slots: ['hand_f', 'arm_f_lo'] });
277
+ player.slots = (slot) => !slot.startsWith('weapon');
278
+ ```
279
+
280
+ Two players over one `ModelAssets` share the load but each poses the rig. To
281
+ pose it once and draw it twice — a hand drawn again in front of another
282
+ character — give a second renderer this player's pose under another filter:
283
+
284
+ ```ts
285
+ const front = new DomRenderer(frontRoot, assets.regionImages, assets.pageImages);
286
+ // each frame, after player.frame(dt):
287
+ front.render(player.posedFrame(['hand_f', 'arm_f_lo']));
288
+ ```
289
+
266
290
  ### Several tracks
267
291
 
268
292
  A base on track 0, a layer over it, a one-shot played once and mixed back out —
@@ -628,6 +652,7 @@ name, and unloading is still elements first, bitmaps second.
628
652
  | `DomTexture` | none | Not exported; there is no atlas page to attach a texture to. |
629
653
  | `new Skeleton(assets.data)` and `new AnimationState(new AnimationStateData(assets.data))` | `new RigPlayer(root, assets, options?)` | The player holds the pose and the track. |
630
654
  | `state.setAnimation(0, 'walk', true)` | `player.play('walk', { loop: true })` | `loop` defaults to true. `play` opens the animation at its first pose with the physics reset there. `player.current` names what is playing. |
655
+ | `skeleton.findSlot('x').attachment = null` to hide a slot, or a second skeleton with slots emptied | `RigPlayerOptions.slots`, `player.slots`, `player.posedFrame(slots)` | A draw filter since 1.6.0 — the pose is the whole rig's; a clipping slot left out does not clip. See *Drawing some slots only*. |
631
656
  | `state.setAnimation(1, 'smile', false)` with a mix, `addAnimation`, `setEmptyAnimation`, `addEmptyAnimation`, `getCurrent(track)` | `player.play(name, { track, loop, mix })`, `player.queue(name, { track, loop, mix, delay })`, `player.clear(track, { mix })`, `player.queueClear(track, { mix, delay })`, `player.entry(track)` | Since 1.5.0, rig-c's live tracks: the runtime's mixing at tolerance 0. Constraint, sequence, draw-order and path-deform timelines are not mixed and are refused on the tracks. See *Several tracks*. |
632
657
  | `state.update(delta)`, `state.apply(skeleton)`, `skeleton.update(delta)`, `skeleton.updateWorldTransform(Physics.update)`, `renderer.render(skeleton)` | `player.frame(dt)` | Or `player.advance(dt)` then `player.draw()`, to time the two apart. |
633
658
  | time scale (the caller's own, on the spine-core side) | `player.timeScale` | Multiplies every step. 0 holds the pose without re-posing it; a negative value is not stepped. |
package/dist/index.d.ts CHANGED
@@ -2,6 +2,6 @@ export { type CanvasBackend, CanvasRenderer, type CanvasViewport } from './Canva
2
2
  export { DomRenderer, type MeshBackend } from './DomRenderer.js';
3
3
  export { type PageLayout, type PageRegion, type RegionEncoding, type RegionImage, revokeRegions, type UnpackRegionsOptions, unpackRegions } from './DomTexture.js';
4
4
  export { type LoadModelOptions, loadModel, type ModelAssets, type ModelDocument, type RegionLoading } from './loadModel.js';
5
- export { type AdjustableBones, type BoneAdjust, type BoneLocals, type BoneWorld, type PlayOptions, type PoseBounds, type QueueOptions, type RenderMode, type RigComplete, type RigEvent, RigPlayer, type RigPlayerOptions, type TrackEntry } from './player.js';
5
+ export { type AdjustableBones, type BoneAdjust, type BoneLocals, type BoneWorld, type PlayOptions, type PoseBounds, type QueueOptions, type RenderMode, type RigComplete, type RigEvent, RigPlayer, type RigPlayerOptions, type SlotFilter, type TrackEntry } from './player.js';
6
6
  export type { BlendMode, PosedClip, PosedFrame, PosedMesh, PosedRegion, PosedSlot, } from './posedFrame.js';
7
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,aAAa,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC9F,OAAO,EAAE,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,KAAK,cAAc,EAAE,KAAK,WAAW,EAAE,aAAa,EAAE,KAAK,oBAAoB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACnK,OAAO,EAAE,KAAK,gBAAgB,EAAE,SAAS,EAAE,KAAK,WAAW,EAAE,KAAK,aAAa,EAAE,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC5H,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,KAAK,YAAY,EAAE,KAAK,UAAU,EAAE,KAAK,WAAW,EAAE,KAAK,QAAQ,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAChQ,YAAY,EACV,SAAS,EACT,SAAS,EACT,UAAU,EACV,SAAS,EACT,WAAW,EACX,SAAS,GACV,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,aAAa,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC9F,OAAO,EAAE,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,KAAK,cAAc,EAAE,KAAK,WAAW,EAAE,aAAa,EAAE,KAAK,oBAAoB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACnK,OAAO,EAAE,KAAK,gBAAgB,EAAE,SAAS,EAAE,KAAK,WAAW,EAAE,KAAK,aAAa,EAAE,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC5H,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,KAAK,YAAY,EAAE,KAAK,UAAU,EAAE,KAAK,WAAW,EAAE,KAAK,QAAQ,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AACjR,YAAY,EACV,SAAS,EACT,SAAS,EACT,UAAU,EACV,SAAS,EACT,WAAW,EACX,SAAS,GACV,MAAM,iBAAiB,CAAC"}
package/dist/index.js CHANGED
@@ -8216,7 +8216,7 @@ function trianglesOf(source, drawn) {
8216
8216
  }
8217
8217
  return kept;
8218
8218
  }
8219
- function clipCover(pose, active) {
8219
+ function clipCover(pose, active, draws = () => true) {
8220
8220
  const polygons = new Map(pose.clips.map((c) => [c[0], { end: c[2], polygon: c[3] }]));
8221
8221
  const shownOf = new Map(pose.shown.map((s) => [s.slot, s]));
8222
8222
  const cover = /* @__PURE__ */ new Map();
@@ -8227,7 +8227,7 @@ function clipCover(pose, active) {
8227
8227
  const clip = g?.kind === "clipping" ? polygons.get(slot) : void 0;
8228
8228
  if (shown !== void 0 && g?.kind === "clipping" && clip !== void 0) {
8229
8229
  if (current !== null && current.end === slot) current = null;
8230
- if (active.has(shown.bone) && current === null) {
8230
+ if (active.has(shown.bone) && current === null && draws(slot)) {
8231
8231
  current = { end: clip.end, clip: { polygon: clip.polygon, inverse: g.inverse, convex: g.convex } };
8232
8232
  }
8233
8233
  continue;
@@ -8237,16 +8237,17 @@ function clipCover(pose, active) {
8237
8237
  }
8238
8238
  return cover;
8239
8239
  }
8240
- function frameOf(source, pose, color = [1, 1, 1, 1]) {
8240
+ function frameOf(source, pose, color = [1, 1, 1, 1], draws = () => true) {
8241
8241
  const { drawn: regions, why } = drawnRegions(source.doc, pose.shown, pose.drawOrder, source.uv);
8242
8242
  if (regions === null) throw new CoreInputError(`the page UVs are not posed: ${why}`);
8243
8243
  const regionOf = new Map(regions.map((r) => [r.slot, r]));
8244
8244
  const rows = new Map(pose.slots.map((row) => [row[0], row]));
8245
8245
  const z = new Map(pose.drawOrder.map((slot, index) => [slot, index]));
8246
- const cover = clipCover(pose, source.active);
8246
+ const cover = clipCover(pose, source.active, draws);
8247
8247
  const [sr, sg, sb, sa] = color;
8248
8248
  const order = [];
8249
8249
  for (const d of pose.drawn) {
8250
+ if (!draws(d.slot)) continue;
8250
8251
  const bone2 = source.boneOf.get(d.slot);
8251
8252
  if (bone2 === void 0 || !source.active.has(bone2)) continue;
8252
8253
  const found = regionOf.get(d.slot);
@@ -8294,6 +8295,10 @@ function frameOf(source, pose, color = [1, 1, 1, 1]) {
8294
8295
  }
8295
8296
  return { order };
8296
8297
  }
8298
+ function predicateOf(names) {
8299
+ const allowed = new Set(names);
8300
+ return (slot) => allowed.has(slot);
8301
+ }
8297
8302
  class RigPlayer {
8298
8303
  /**
8299
8304
  * The renderer this player draws through — its knobs and counters are the
@@ -8341,6 +8346,9 @@ class RigPlayer {
8341
8346
  pose = null;
8342
8347
  setup = null;
8343
8348
  adjustFn = null;
8349
+ slotFilter = null;
8350
+ /** `slotFilter` as a predicate; every slot when null. */
8351
+ draws = () => true;
8344
8352
  /** Handed to every track the player opens; reads the current `adjust` so setting it later needs no reopen. */
8345
8353
  adjustHook = (locals) => {
8346
8354
  if (this.adjustFn !== null) this.adjustFn(locals);
@@ -8361,6 +8369,7 @@ class RigPlayer {
8361
8369
  this.assets = assets;
8362
8370
  this.meshBackend = options.meshBackend;
8363
8371
  this.adjustFn = options.adjust ?? null;
8372
+ if (options.slots !== void 0 && options.slots !== null) this.slots = options.slots;
8364
8373
  this.active = this.create(options.mode ?? "dom");
8365
8374
  }
8366
8375
  /**
@@ -8619,6 +8628,37 @@ class RigPlayer {
8619
8628
  this.stepTo(time);
8620
8629
  }
8621
8630
  }
8631
+ /**
8632
+ * Which slots this player draws (`SlotFilter`), or null for every slot.
8633
+ * Settable on a live player: the next `draw` shows it, and `bounds()` reads
8634
+ * the drawn slots. An allow list naming a slot the document does not have
8635
+ * is refused by name. The pose is the whole rig's: `bone()`, `adjust`, the
8636
+ * events and the tracks see no filter.
8637
+ */
8638
+ get slots() {
8639
+ return this.slotFilter;
8640
+ }
8641
+ set slots(filter) {
8642
+ if (filter === null) {
8643
+ this.slotFilter = null;
8644
+ this.draws = () => true;
8645
+ return;
8646
+ }
8647
+ if (typeof filter === "function") {
8648
+ this.slotFilter = filter;
8649
+ this.draws = filter;
8650
+ return;
8651
+ }
8652
+ if (!Array.isArray(filter)) throw new Error(`slots must be an array of slot names, a function, or null, got ${typeof filter}`);
8653
+ const known = new Set(this.read.doc.slots.map((slot) => slot.name));
8654
+ const unknown = filter.filter((name) => !known.has(name));
8655
+ if (unknown.length > 0) {
8656
+ throw new Error(`no slot named ${unknown.map((n) => JSON.stringify(n)).join(", ")} in this document (slots: ${[...known].join(", ")})`);
8657
+ }
8658
+ const allowed = new Set(filter);
8659
+ this.slotFilter = [...filter];
8660
+ this.draws = (slot) => allowed.has(slot);
8661
+ }
8622
8662
  /**
8623
8663
  * The per-pose adjustment of bone locals (`BoneAdjust`), or null for none.
8624
8664
  * It is applied by every track the player opens — `play`, the reopen inside
@@ -8684,7 +8724,7 @@ class RigPlayer {
8684
8724
  * mode; nothing is computed until called.
8685
8725
  */
8686
8726
  bounds() {
8687
- const frame2 = this.pose === null ? frameOf(this.source, this.posed(), this.color) : this.posedFrame();
8727
+ const frame2 = this.pose === null ? frameOf(this.source, this.posed(), this.color, this.draws) : this.posedFrame();
8688
8728
  let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
8689
8729
  for (const slot of frame2.order) {
8690
8730
  const points = slot.kind === "region" ? slot.corners : slot.vertices;
@@ -8720,10 +8760,17 @@ class RigPlayer {
8720
8760
  if (this.setup === null) this.setup = poseRawSetup(this.view);
8721
8761
  return this.setup;
8722
8762
  }
8723
- /** The pose the player holds, as the frame record the renderer draws. */
8724
- posedFrame() {
8763
+ /**
8764
+ * The pose the player holds, as the frame record the renderer draws — under
8765
+ * this player's `slots` filter, or under `slots` given here instead (a
8766
+ * second renderer over the same assets drawing this player's pose on
8767
+ * another root: `other.render(player.posedFrame(['hand_f']))`, the one rig
8768
+ * posed once).
8769
+ */
8770
+ posedFrame(slots) {
8725
8771
  if (this.pose === null) return { order: [] };
8726
- return frameOf(this.source, this.pose, this.color);
8772
+ const draws = slots === void 0 ? this.draws : slots === null ? () => true : typeof slots === "function" ? slots : predicateOf(slots);
8773
+ return frameOf(this.source, this.pose, this.color, draws);
8727
8774
  }
8728
8775
  /** Removes the renderer's elements and hands its GPU pages back. The assets stay the caller's. */
8729
8776
  dispose() {