spine-html 1.12.1 → 1.13.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
@@ -450,6 +450,42 @@ box, an inverse clip removes nothing, and bounding boxes, paths and points do
450
450
  not draw and do not count. Both cost nothing until called and read the same
451
451
  numbers in `dom` and `canvas` mode.
452
452
 
453
+ `readSetupMesh` reads a named mesh from loaded assets before or during playback,
454
+ including an attachment the setup pose does not select:
455
+
456
+ ```ts
457
+ import { readSetupMesh } from '@rig-c/play';
458
+
459
+ const mesh = readSetupMesh(assets, { slot: 'flag', attachment: 'alternate' });
460
+ // mesh: { slot, attachment, skin, vertices, triangles, hull }
461
+ // vertices: flat world x,y pairs, Y up; hull counts vertices, or is null.
462
+ ```
463
+
464
+ Names are exact slot names and attachment table keys, not display names or atlas
465
+ regions. Omitted or `null` skin means no skin set, as for a fresh player. A named
466
+ `skin` uses that skin's activation lists with default-table fallback; the returned
467
+ `skin` names the supplying table. `skin: 'all'` uses all activation lists and
468
+ refuses differing definitions rather than guessing a merge order. Linked meshes
469
+ retain the requested identity and borrow source topology/bindings; unweighted
470
+ vertices use the queried slot's bone. Inactive bones retain core's zero matrix.
471
+ The result and its arrays are detached and mutable. Missing slot/attachment/skin,
472
+ non-mesh and ambiguous targets throw core's named `CoreInputError` messages;
473
+ setup/non-finite refusals propagate unchanged. The read creates no player and
474
+ changes no document, existing player, time, generation or rendered frame.
475
+
476
+ **Setup-slider boundary (core 2.66.0):** selected meshes include setup-slider
477
+ deform, exactly as core's setup pose does. Unselected meshes use stored geometry
478
+ through the same setup constraint/reset-physics matrices, without switching the
479
+ attachment or borrowing another selection's deform. Core measured **four of six
480
+ nonselected official-runtime comparisons differing by up to
481
+ `2.9999999999999982` world units (about 3.0)**: spine-core 4.3.13's
482
+ `computeWorldVertices` borrows the selected slot's deform, including for a linked
483
+ mesh with `timelines: false`. The other two nonselected calls and all three
484
+ selected reads agree exactly. This wrapper passes core's output through; it does
485
+ not correct or round it. Stored-only formula comparisons therefore have a
486
+ different domain from selected geometry with setup deform. See
487
+ [core AUTHORING §14, including S1538_09](https://github.com/rig-c/core/blob/v2.66.0/docs/AUTHORING.md#14-read-a-named-mesh-at-setup).
488
+
453
489
  ### Drawing some slots only
454
490
 
455
491
  `slots` is a draw filter — an allow list of slot names or a predicate — settable up
@@ -1009,6 +1045,7 @@ name, and unloading is still elements first, bitmaps second.
1009
1045
  | `skeleton.findBone('head').worldX`, `.worldY`, `.a` … `.d` | `player.bone('head')` | `{ x, y, a, b, c, d, rotationX, rotationY, scaleX, scaleY, active }`, the core's world transform for the pose the player holds (the setup pose before `play`). An unknown name is refused by name. Since 1.1.0. |
1010
1046
  | `bone.scaleY = …` on a `Skeleton` between `state.apply` and `updateWorldTransform` | `RigPlayerOptions.adjust`, `player.adjust`, `player.repose()` | A function called once per pose with the pose's bone locals (`bones.bone('mouth').scaleY = v`). A write lasts one pose: the core poses every step from the setup pose, so write the value you want, every pose — nothing compounds. See *Adjusting bones*. Since 1.2.0. |
1011
1047
  | `skeleton.getBounds(offset, size)` | `player.bounds()` | `{ x, y, width, height }` or null, world units, Y up: the box of every drawn region's corners and mesh's vertices, a slot under a clip counted inside the clip's own box. `ModelAssets.stage` remains the declared framing, not this box. Since 1.1.0. |
1048
+ | named setup mesh geometry | `readSetupMesh(assets, { slot, attachment, skin? })` | Detached full-double world x/y (Y up), triangles and hull for any named mesh/linked mesh, including non-setup selections; no player/time mutation. Selected geometry includes setup-slider deform; unselected geometry is stored geometry through unchanged setup matrices. See the measured official-runtime distinction under *Reading the pose*. |
1012
1049
 
1013
1050
  ### No counterpart in 1.0.0
1014
1051
 
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { type CanvasBackend, CanvasRenderer, type CanvasViewport } from './CanvasRenderer.js';
2
2
  export { DomRenderer, type MeshBackend, type MeshSourceRect } from './DomRenderer.js';
3
3
  export { type PageLayout, type PageRegion, type RegionEncoding, type RegionImage, revokeRegions, type UnpackRegionsOptions, unpackRegions } from './DomTexture.js';
4
- export { type LoadModelOptions, loadModel, type ModelAssets, type ModelDocument, type RegionLoading, type RigSet } from './loadModel.js';
4
+ export { type LoadModelOptions, loadModel, type ModelAssets, type ModelDocument, readSetupMesh, type RegionLoading, type RigSet, type SetupMesh, type SetupMeshTarget } from './loadModel.js';
5
5
  export { type AdjustableBones, type BoneAdjust, type BoneLocals, type BoneWorld, type PlayOptions, type PoseBounds, type QueueOptions, type RenderMode, type RigComplete, type RigEnvironment, type RigEvent, RigPlayer, type RigPlayerOptions, type SlotFilter, type SlotIdentity, type SlotIdentityReason, type SlotIdentitySnapshot, 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,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACtF,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,KAAK,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACzI,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,cAAc,EAAE,KAAK,QAAQ,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,KAAK,UAAU,EAAE,KAAK,YAAY,EAAE,KAAK,kBAAkB,EAAE,KAAK,oBAAoB,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAC7W,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,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACtF,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,aAAa,EAAE,KAAK,aAAa,EAAE,KAAK,MAAM,EAAE,KAAK,SAAS,EAAE,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAC9L,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,cAAc,EAAE,KAAK,QAAQ,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,KAAK,UAAU,EAAE,KAAK,YAAY,EAAE,KAAK,kBAAkB,EAAE,KAAK,oBAAoB,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAC7W,YAAY,EACV,SAAS,EACT,SAAS,EACT,UAAU,EACV,SAAS,EACT,WAAW,EACX,SAAS,GACV,MAAM,iBAAiB,CAAC"}
package/dist/index.js CHANGED
@@ -377,6 +377,83 @@ function timelineIdentity(slot, shown) {
377
377
  return `${shown.skin}/${slot}/${shown.placeholder}`;
378
378
  }
379
379
  const nameOf = (a) => `${a.skin}/${a.slot}/${a.attachment}`;
380
+ const identitiesOfView = /* @__PURE__ */ new WeakMap();
381
+ function readIdentity(doc, resolve, slot, placeholder) {
382
+ const s = resolve(doc, { ...slot, setup: placeholder });
383
+ if (s === null || "conflict" in s || s.record.geometry === void 0) return null;
384
+ const g = s.record.geometry;
385
+ const source = g.kind === "linkedmesh" ? doc.skins.find((k) => k.name === g.skin)?.attachments[g.slot]?.[g.source] : s.record;
386
+ const sg = source?.geometry;
387
+ return Object.freeze({
388
+ entry: Object.freeze({ slot: slot.name, bone: slot.bone, name: shownRow(s).name, placeholder: s.placeholder, skin: s.skin, geometry: g }),
389
+ identity: timelineIdentity(slot.name, s),
390
+ vertices: sg !== void 0 && sg.kind !== "region" && sg.kind !== "linkedmesh" ? sg.vertices : null,
391
+ count: source?.sequenceCount,
392
+ fillers: Object.freeze(fillingSkins(doc, slot.name, s.placeholder).map((k) => `"${k}"`))
393
+ });
394
+ }
395
+ function identityOf(doc, resolve, slot, placeholder) {
396
+ if (placeholder === null) return null;
397
+ if (resolve !== shownAttachment) return readIdentity(doc, resolve, slot, placeholder);
398
+ let slots = identitiesOfView.get(doc);
399
+ if (slots === void 0) identitiesOfView.set(doc, slots = /* @__PURE__ */ new Map());
400
+ let placeholders = slots.get(slot.name);
401
+ if (placeholders === void 0) slots.set(slot.name, placeholders = /* @__PURE__ */ new Map());
402
+ if (placeholders.has(placeholder)) return placeholders.get(placeholder);
403
+ const identity = readIdentity(doc, resolve, slot, placeholder);
404
+ placeholders.set(placeholder, identity);
405
+ return identity;
406
+ }
407
+ const attachmentsByIdentity = /* @__PURE__ */ new WeakMap();
408
+ const switchesBySlot = /* @__PURE__ */ new WeakMap();
409
+ function matchingAttachments(timelines, identity) {
410
+ let index = attachmentsByIdentity.get(timelines);
411
+ if (index === void 0) {
412
+ const rows = /* @__PURE__ */ new Map();
413
+ for (const a of timelines) {
414
+ const name = nameOf(a);
415
+ const found = rows.get(name);
416
+ if (found === void 0) rows.set(name, [a]);
417
+ else found.push(a);
418
+ }
419
+ attachmentsByIdentity.set(timelines, index = rows);
420
+ }
421
+ return index.get(identity);
422
+ }
423
+ function slotSwitches(slots, slot) {
424
+ let index = switchesBySlot.get(slots);
425
+ if (index === void 0) {
426
+ const rows = /* @__PURE__ */ new Map();
427
+ for (const target of slots) {
428
+ for (const tl of target.timelines) {
429
+ if (tl.kind !== "attachment" || tl.keys === void 0) continue;
430
+ const found = rows.get(target.name);
431
+ if (found === void 0) rows.set(target.name, [tl.keys]);
432
+ else found.push(tl.keys);
433
+ }
434
+ }
435
+ switchesBySlot.set(slots, index = rows);
436
+ }
437
+ return index.get(slot);
438
+ }
439
+ function applyAttachment(now, values, timelines, t, blend, evalDeform, blendOf2, evalFrame) {
440
+ if (now === null || now.identity === null) return values;
441
+ const matching = matchingAttachments(timelines, now.identity);
442
+ if (matching === void 0) return values;
443
+ values ??= { deform: null, frame: null, seen: [] };
444
+ if (!values.seen.includes(now.identity)) values.seen.push(now.identity);
445
+ for (const a of matching) {
446
+ if (a.deform !== null && now.vertices !== null) {
447
+ const target = evalDeform(now.vertices, a.deform, t);
448
+ if (target !== null) values.deform = blend === null ? target : blendOf2(now.vertices, values.deform, target, blend.alpha, blend.additive);
449
+ }
450
+ if (a.sequence !== null && now.count !== void 0) {
451
+ const f = evalFrame(a.sequence, now.count, t);
452
+ if (f !== null) values.frame = f;
453
+ }
454
+ }
455
+ return values;
456
+ }
380
457
  function attachmentStates(doc, resolve, placeholders, sample, sliders, plant = {}, mixed) {
381
458
  const evalDeform = plant.deform ?? deformAt;
382
459
  const blendOf2 = plant.deformBlend ?? blendDeform;
@@ -389,64 +466,37 @@ function attachmentStates(doc, resolve, placeholders, sample, sliders, plant = {
389
466
  const live = gate(doc, slot, active);
390
467
  let placeholder = placeholders.get(slot.name) ?? null;
391
468
  const base = live ? mixed?.get(slot.name) : void 0;
392
- let deform = base?.deform ?? null;
393
- let frame2 = null;
394
- const seen = base === void 0 ? [] : [...base.seen];
395
- const state = () => {
396
- if (placeholder === null) return null;
397
- const s = resolve(doc, { ...slot, setup: placeholder });
398
- if (s === null || "conflict" in s || s.record.geometry === void 0) return null;
399
- const g = s.record.geometry;
400
- const source = g.kind === "linkedmesh" ? doc.skins.find((k) => k.name === g.skin)?.attachments[g.slot]?.[g.source] : s.record;
401
- const sg = source?.geometry;
402
- const vertices = sg !== void 0 && sg.kind !== "region" && sg.kind !== "linkedmesh" ? sg.vertices : null;
403
- return { s, identity: timelineIdentity(slot.name, s), vertices, count: source?.sequenceCount };
404
- };
405
- const apply = (timelines, t, blend) => {
406
- const now2 = state();
407
- if (now2 === null || now2.identity === null) return;
408
- for (const a of timelines) {
409
- if (nameOf(a) !== now2.identity) continue;
410
- if (!seen.includes(now2.identity)) seen.push(now2.identity);
411
- if (a.deform !== null && now2.vertices !== null) {
412
- const target = evalDeform(now2.vertices, a.deform, t);
413
- if (target !== null) deform = blend === null ? target : blendOf2(now2.vertices, deform, target, blend.alpha, blend.additive);
414
- }
415
- if (a.sequence !== null && now2.count !== void 0) {
416
- const f = evalFrame(a.sequence, now2.count, t);
417
- if (f !== null) frame2 = f;
418
- }
419
- }
420
- };
421
- if (sample !== null && live) apply(sample.timelines.attachments, sample.t, null);
422
- for (const app of live ? sliders : []) {
423
- for (const target of app.timelines.slots) {
424
- if (target.name !== slot.name) continue;
425
- for (const tl of target.timelines) {
426
- if (tl.kind !== "attachment" || tl.keys === void 0) continue;
427
- const i = keyIndexAt(tl.keys, app.at);
428
- if (i < 0) continue;
429
- const next = tl.keys[i].name ?? null;
430
- if (next !== placeholder) {
431
- deform = null;
432
- frame2 = null;
469
+ let values = base === void 0 || base.deform === null && base.seen.length === 0 ? null : { deform: base.deform, frame: null, seen: [...base.seen] };
470
+ let now = identityOf(doc, resolve, slot, placeholder);
471
+ if (sample !== null && live) values = applyAttachment(now, values, sample.timelines.attachments, sample.t, null, evalDeform, blendOf2, evalFrame);
472
+ if (live) for (const app of sliders) {
473
+ const switches = slotSwitches(app.timelines.slots, slot.name);
474
+ if (switches !== void 0) for (const keys of switches) {
475
+ const i = keyIndexAt(keys, app.at);
476
+ if (i < 0) continue;
477
+ const next = keys[i].name ?? null;
478
+ if (next !== placeholder) {
479
+ if (values !== null) {
480
+ values.deform = null;
481
+ values.frame = null;
433
482
  }
434
483
  placeholder = next;
484
+ now = identityOf(doc, resolve, slot, placeholder);
435
485
  }
436
486
  }
437
- apply(app.timelines.attachments, app.at, { alpha: app.alpha, additive: app.additive });
487
+ values = applyAttachment(now, values, app.timelines.attachments, app.at, app, evalDeform, blendOf2, evalFrame);
438
488
  }
439
- const now = state();
440
489
  if (now === null) continue;
441
- const entry = { slot: slot.name, bone: slot.bone, name: shownRow(now.s).name, placeholder: now.s.placeholder, skin: now.s.skin, geometry: now.s.record.geometry };
490
+ const entry = values === null || values.deform === null && values.frame === null ? now.entry : { ...now.entry };
442
491
  shown.push(entry);
443
- const fillers = fillingSkins(doc, slot.name, now.s.placeholder).map((k) => `"${k}"`);
444
- if (seen.length > 0 && fillers.length > 1) {
445
- why.push(`slot "${slot.name}" shows placeholder "${now.s.placeholder}", which skins ${fillers.join(", ")} fill, and ${seen.map((x) => `"${x}"`).join(", ")} key(s) it — which record --skin all shows, and so whether the timeline moves it, is the Spine file's skin order, not the model's`);
492
+ if (values !== null && values.seen.length > 0 && now.fillers.length > 1) {
493
+ why.push(`slot "${slot.name}" shows placeholder "${now.entry.placeholder}", which skins ${now.fillers.join(", ")} fill, and ${values.seen.map((x) => `"${x}"`).join(", ")} key(s) it — which record --skin all shows, and so whether the timeline moves it, is the Spine file's skin order, not the model's`);
446
494
  continue;
447
495
  }
448
- if (deform !== null) entry.deform = deform;
449
- if (frame2 !== null) entry.frame = frame2;
496
+ if (values !== null) {
497
+ if (values.deform !== null) entry.deform = values.deform;
498
+ if (values.frame !== null) entry.frame = values.frame;
499
+ }
450
500
  }
451
501
  return { shown, why };
452
502
  }
@@ -712,8 +762,8 @@ function clipTriangles(polygon, vertices, triangles, uvs, reading = {}) {
712
762
  }
713
763
  return { clipped, vertices: outV, uvs: outUv, triangles: outT, sources };
714
764
  }
715
- const REGION_UVS = [0, 1, 0, 0, 1, 0, 1, 1];
716
- const REGION_TRIANGLES = [0, 1, 2, 2, 3, 0];
765
+ const REGION_UVS = Object.freeze([0, 1, 0, 0, 1, 0, 1, 1]);
766
+ const REGION_TRIANGLES = Object.freeze([0, 1, 2, 2, 3, 0]);
717
767
  function poseClipped(walk, round, clip = clipTriangles, through = (shape, v, t, uv) => clipThrough(shape, v, t, uv, clip)) {
718
768
  const rows = [];
719
769
  const refused = [];
@@ -968,6 +1018,15 @@ function clipThrough(shape, vertices, triangles, uvs, clip = clipTriangles, read
968
1018
  return { clipped, vertices: outV, uvs: outUv, triangles: outT, sources };
969
1019
  }
970
1020
  const RAD$2 = RUNTIME_PI / 180;
1021
+ const meshArraysByGeometry = /* @__PURE__ */ new WeakMap();
1022
+ function meshArraysOf(mesh) {
1023
+ let kept = meshArraysByGeometry.get(mesh);
1024
+ if (kept === void 0) {
1025
+ kept = Object.freeze({ uvs: Object.freeze([...mesh.uvs]), triangles: Object.freeze([...mesh.triangles]) });
1026
+ meshArraysByGeometry.set(mesh, kept);
1027
+ }
1028
+ return kept;
1029
+ }
971
1030
  const EXACT_COORDS = (v) => v;
972
1031
  function regionCorners(region, bone2) {
973
1032
  const { width: W, height: H } = region;
@@ -1019,6 +1078,34 @@ function worldVertices(vertices, bone2, world, coords = Math.fround) {
1019
1078
  }
1020
1079
  return out;
1021
1080
  }
1081
+ function deformedWorldVertices(vertices, deform, bone2, world) {
1082
+ const out = [];
1083
+ if (!vertices.weighted) {
1084
+ for (let i = 0; i + 1 < deform.length; i += 2) {
1085
+ const x = deform[i];
1086
+ const y = deform[i + 1];
1087
+ out.push(x * bone2.a + y * bone2.b + bone2.worldX, x * bone2.c + y * bone2.d + bone2.worldY);
1088
+ }
1089
+ return out;
1090
+ }
1091
+ let k = 0;
1092
+ for (const influences of vertices.bindings) {
1093
+ let wx = 0;
1094
+ let wy = 0;
1095
+ for (const binding of influences) {
1096
+ const t = world.get(binding.bone);
1097
+ if (t === void 0) throw new Error(`a binding names bone "${binding.bone}", which has no world transform (readModel refuses it first)`);
1098
+ const x = Math.fround(binding.x) + deform[k];
1099
+ const y = Math.fround(binding.y) + deform[k + 1];
1100
+ k += 2;
1101
+ const weight = Math.fround(binding.weight);
1102
+ wx += (x * t.a + y * t.b + t.worldX) * weight;
1103
+ wy += (x * t.c + y * t.d + t.worldY) * weight;
1104
+ }
1105
+ out.push(wx, wy);
1106
+ }
1107
+ return out;
1108
+ }
1022
1109
  const isRecord$7 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
1023
1110
  const finite$1 = (v) => typeof v === "number" && Number.isFinite(v);
1024
1111
  const RECT_FIELDS = ["width", "height", "offsetX", "offsetY", "originalWidth", "originalHeight"];
@@ -1149,42 +1236,55 @@ function readPathGeometry(raw, vertices, where, problems) {
1149
1236
  if (problems.length !== before) return void 0;
1150
1237
  return { kind: "path", vertices, closed: raw.closed === true, constantSpeed: raw.constantSpeed !== false, lengths };
1151
1238
  }
1239
+ function roundedRow(values, round) {
1240
+ for (let i = 0; i < values.length; i++) {
1241
+ const r = round(values[i]);
1242
+ if (Object.is(r, values[i])) continue;
1243
+ const out = values.slice(0, i);
1244
+ out.push(r);
1245
+ for (let j = i + 1; j < values.length; j++) out.push(round(values[j]));
1246
+ return out;
1247
+ }
1248
+ return values;
1249
+ }
1152
1250
  function poseGeometry(shown, world, sourceOf, round, plant = {}, draw) {
1153
1251
  const region = plant.region ?? regionCorners;
1154
1252
  const vertices = plant.vertices ?? worldVertices;
1155
1253
  const attachments = [];
1156
1254
  const clips = [];
1157
1255
  const nulls = [];
1256
+ const walked = draw !== void 0 && shown.some((s) => s.geometry.kind === "clipping");
1158
1257
  const steps = /* @__PURE__ */ new Map();
1159
1258
  for (const s of shown) {
1160
1259
  const bone2 = world.get(s.bone);
1161
1260
  if (bone2 === void 0) throw new Error(`slot "${s.slot}": bone "${s.bone}" has no world transform`);
1162
1261
  const g = s.geometry;
1163
- const drawn = (v) => s.deform === void 0 ? vertices(v, bone2, world) : vertices(deformedVertices(v, s.deform), bone2, world, EXACT_COORDS);
1262
+ const deform = s.deform;
1263
+ const drawn = (v) => deform === void 0 ? vertices(v, bone2, world) : plant.vertices === void 0 ? deformedWorldVertices(v, deform, bone2, world) : vertices(deformedVertices(v, deform), bone2, world, EXACT_COORDS);
1164
1264
  if (g.kind === "region") {
1165
1265
  const rect = s.frame === void 0 ? g.region.atlas : g.region.frames?.[s.frame] ?? g.region.atlas;
1166
1266
  if (rect === null) {
1167
1267
  nulls.push(`slot "${s.slot}" shows region "${s.name}" (skin "${s.skin}", placeholder "${s.placeholder}")`);
1168
1268
  continue;
1169
1269
  }
1170
- const corners = region({ ...g.region, atlas: rect }, bone2);
1171
- attachments.push([s.slot, s.name, "region", corners.map(round)]);
1172
- steps.set(s.slot, { slot: s.slot, kind: "draw", attachment: s.name, vertices: corners, triangles: [...REGION_TRIANGLES], uvs: [...REGION_UVS] });
1270
+ const corners = region(rect === g.region.atlas ? g.region : { ...g.region, atlas: rect }, bone2);
1271
+ attachments.push([s.slot, s.name, "region", roundedRow(corners, round)]);
1272
+ if (walked) steps.set(s.slot, { slot: s.slot, kind: "draw", attachment: s.name, vertices: corners, triangles: REGION_TRIANGLES, uvs: REGION_UVS });
1173
1273
  } else if (g.kind === "mesh") {
1174
1274
  const world2 = drawn(g.vertices);
1175
- attachments.push([s.slot, s.name, "mesh", world2.map(round)]);
1176
- steps.set(s.slot, { slot: s.slot, kind: "draw", attachment: s.name, vertices: world2, triangles: g.triangles, uvs: g.uvs });
1275
+ attachments.push([s.slot, s.name, "mesh", roundedRow(world2, round)]);
1276
+ if (walked) steps.set(s.slot, { slot: s.slot, kind: "draw", attachment: s.name, vertices: world2, triangles: g.triangles, uvs: g.uvs });
1177
1277
  } else if (g.kind === "linkedmesh") {
1178
1278
  const source = sourceOf(g.skin, g.slot, g.source);
1179
1279
  if (typeof source === "string") throw new Error(`slot "${s.slot}": ${source} (readModel refuses it first)`);
1180
1280
  const world2 = drawn(source);
1181
- attachments.push([s.slot, s.name, "mesh", world2.map(round)]);
1281
+ attachments.push([s.slot, s.name, "mesh", roundedRow(world2, round)]);
1182
1282
  const mesh = draw?.meshOf(g.skin, g.slot, g.source);
1183
1283
  if (draw !== void 0 && mesh === void 0) throw new Error(`slot "${s.slot}": the linked mesh's source "${g.source}" carries no triangles (readModel refuses it first)`);
1184
- if (mesh !== void 0) steps.set(s.slot, { slot: s.slot, kind: "draw", attachment: s.name, vertices: world2, triangles: mesh.triangles, uvs: mesh.uvs });
1284
+ if (walked && mesh !== void 0) steps.set(s.slot, { slot: s.slot, kind: "draw", attachment: s.name, vertices: world2, triangles: mesh.triangles, uvs: mesh.uvs });
1185
1285
  } else if (g.kind === "clipping") {
1186
1286
  const polygon = drawn(g.vertices);
1187
- clips.push([s.slot, s.name, g.end, polygon.map(round)]);
1287
+ clips.push([s.slot, s.name, g.end, roundedRow(polygon, round)]);
1188
1288
  steps.set(s.slot, { slot: s.slot, kind: "clip", attachment: s.name, active: draw?.active.has(s.bone) ?? false, end: g.end, polygon, inverse: g.inverse, convex: g.convex });
1189
1289
  }
1190
1290
  }
@@ -1193,7 +1293,12 @@ function poseGeometry(shown, world, sourceOf, round, plant = {}, draw) {
1193
1293
  let clippedWhy = draw === void 0 ? "the draw order was not given" : attachmentsWhy;
1194
1294
  let drawnClipped = null;
1195
1295
  let drawnClippedWhy = clippedWhy;
1196
- if (draw !== void 0 && attachmentsWhy === null) {
1296
+ if (draw !== void 0 && attachmentsWhy === null && !walked) {
1297
+ clipped = [];
1298
+ clippedWhy = null;
1299
+ drawnClipped = [];
1300
+ drawnClippedWhy = null;
1301
+ } else if (draw !== void 0 && attachmentsWhy === null) {
1197
1302
  const walk = draw.order.map((slot) => steps.get(slot) ?? { slot, kind: "none" });
1198
1303
  const posed = poseClipped(walk, round, draw.clip, draw.through);
1199
1304
  clipped = posed.rows;
@@ -2157,6 +2262,15 @@ function stepPhysics(r, w, length, ctx) {
2157
2262
  const k = 1 + (s.scaleOffset - s.scaleLag * z) * mix * r.scaleX;
2158
2263
  w.a *= k;
2159
2264
  w.c *= k;
2265
+ if (r.scaleYMode === "Uniform") {
2266
+ w.b *= k;
2267
+ w.d *= k;
2268
+ } else if (r.scaleYMode === "Volume") {
2269
+ const magnitude = Math.abs(k);
2270
+ const transverse = magnitude >= 0.7 ? 1 / magnitude : 4 - 3.67347 * magnitude;
2271
+ w.b *= transverse;
2272
+ w.d *= transverse;
2273
+ }
2160
2274
  }
2161
2275
  s.tx = l * w.a;
2162
2276
  s.ty = l * w.c;
@@ -4702,8 +4816,8 @@ function shownRow(shown) {
4702
4816
  return { name, path: CORE_REGION_KINDS.has(shown.record.kind) ? shown.record.path ?? name : null };
4703
4817
  }
4704
4818
  const shownOfView = /* @__PURE__ */ new WeakMap();
4705
- function shownAttachment(doc, slot) {
4706
- if (slot.setup === null) return null;
4819
+ function shownAttachment(doc, slot, placeholder = slot.setup) {
4820
+ if (placeholder === null) return null;
4707
4821
  let bySlot = shownOfView.get(doc);
4708
4822
  if (bySlot === void 0) {
4709
4823
  bySlot = /* @__PURE__ */ new Map();
@@ -4714,9 +4828,9 @@ function shownAttachment(doc, slot) {
4714
4828
  byPlaceholder = /* @__PURE__ */ new Map();
4715
4829
  bySlot.set(slot.name, byPlaceholder);
4716
4830
  }
4717
- if (byPlaceholder.has(slot.setup)) return byPlaceholder.get(slot.setup);
4718
- const resolved = resolveShown(doc, slot.name, slot.setup);
4719
- byPlaceholder.set(slot.setup, resolved);
4831
+ if (byPlaceholder.has(placeholder)) return byPlaceholder.get(placeholder);
4832
+ const resolved = resolveShown(doc, slot.name, placeholder);
4833
+ byPlaceholder.set(placeholder, resolved);
4720
4834
  return resolved;
4721
4835
  }
4722
4836
  function resolveShown(doc, slotName, placeholder) {
@@ -4907,6 +5021,13 @@ function drawnGeometry(doc, shown, world, order, plant, mode) {
4907
5021
  if (geometry.drawnClipped === null) throw new CoreInputError(`the raw pose leaves the clipped triangles out: ${geometry.drawnClippedWhy}`);
4908
5022
  return { rows: geometry.attachments, clips: geometry.clips, drawnClipped: geometry.drawnClipped };
4909
5023
  }
5024
+ function numbersOf(values, what, mode) {
5025
+ return allNumbers(values) ? values : values.map((v) => numberOf(v, what, mode));
5026
+ }
5027
+ function allNumbers(values) {
5028
+ for (const v of values) if (v === null) return false;
5029
+ return true;
5030
+ }
4910
5031
  function rawDrawn(doc, shown, world, order, plant, mode = RAW_MODE) {
4911
5032
  const geometry = drawnGeometry(doc, shown, world, order, plant, mode);
4912
5033
  const rows = geometry.rows;
@@ -4923,25 +5044,56 @@ function rawDrawn(doc, shown, world, order, plant, mode = RAW_MODE) {
4923
5044
  let triangles;
4924
5045
  let hull = null;
4925
5046
  if (g.kind === "region") {
4926
- uvs = [...REGION_UVS];
4927
- triangles = [...REGION_TRIANGLES];
5047
+ uvs = REGION_UVS;
5048
+ triangles = REGION_TRIANGLES;
4928
5049
  } else {
4929
5050
  const source = g.kind === "mesh" ? g : doc.skins.find((x) => x.name === g.skin)?.attachments[g.slot]?.[g.source]?.geometry;
4930
5051
  if (source === void 0 || source.kind !== "mesh") throw new CoreInputError(`slot "${s.slot}": the linked mesh's source carries no mesh`);
4931
- uvs = [...source.uvs];
4932
- triangles = [...source.triangles];
5052
+ ({ uvs, triangles } = (plant.meshArrays ?? meshArraysOf)(source));
4933
5053
  hull = source.hull ?? null;
4934
5054
  }
4935
5055
  const sequenceIndex = s.frame ?? record?.sequenceSetup ?? 0;
4936
- drawn.push({ slot: s.slot, attachment: row[1], kind: row[2], vertices: row[3].map((v) => numberOf(v, `vertex of slot "${s.slot}"`, mode)), uvs, triangles, hull, sequenceIndex, colour });
5056
+ drawn.push({ slot: s.slot, attachment: row[1], kind: row[2], vertices: numbersOf(row[3], `vertex of slot "${s.slot}"`, mode), uvs, triangles, hull, sequenceIndex, colour });
4937
5057
  }
4938
- const clips = geometry.clips.map((c) => [c[0], c[1], c[2], c[3].map((v) => numberOf(v, `clip vertex of slot "${c[0]}"`, mode))]);
4939
- const clipped = geometry.drawnClipped.map((c) => [c[0], c[1], c[2], c[3].map((v) => numberOf(v, `clipped vertex of slot "${c[0]}"`, mode)), c[4].map((v) => numberOf(v, `clipped uv of slot "${c[0]}"`, mode)), c[5]]);
5058
+ const clips = geometry.clips.map((c) => [c[0], c[1], c[2], numbersOf(c[3], `clip vertex of slot "${c[0]}"`, mode)]);
5059
+ const clipped = geometry.drawnClipped.map((c) => [c[0], c[1], c[2], numbersOf(c[3], `clipped vertex of slot "${c[0]}"`, mode), numbersOf(c[4], `clipped uv of slot "${c[0]}"`, mode), c[5]]);
4940
5060
  return { drawn, clips, clipped };
4941
5061
  }
4942
5062
  function poseRawSetup(doc, plant = {}) {
4943
5063
  return setupPoseIn(doc, plant, RAW_MODE);
4944
5064
  }
5065
+ function sameSetupMeshDefinition(a, b) {
5066
+ if (Object.is(a, b)) return true;
5067
+ if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) return false;
5068
+ if (Array.isArray(a) !== Array.isArray(b)) return false;
5069
+ if (Array.isArray(a) && Array.isArray(b) && a.length !== b.length) return false;
5070
+ const left = a;
5071
+ const right = b;
5072
+ const keys = Object.keys(left);
5073
+ return keys.length === Object.keys(right).length && keys.every((key) => Object.prototype.hasOwnProperty.call(right, key) && sameSetupMeshDefinition(left[key], right[key]));
5074
+ }
5075
+ function readSetupMesh$1(doc, target) {
5076
+ const view = target.skin === void 0 ? doc : target.skin === null ? underNoSkin(doc) : underSkin(doc, target.skin);
5077
+ const where = `setup mesh slot ${JSON.stringify(target.slot)} attachment ${JSON.stringify(target.attachment)} under skin ${JSON.stringify(view.skin)}`;
5078
+ const slot = view.slots.find((s) => s.name === target.slot);
5079
+ if (slot === void 0) throw new CoreInputError(`${where}: no such slot`);
5080
+ const resolved = shownAttachment(view, slot, target.attachment);
5081
+ if (resolved === null) throw new CoreInputError(`${where}: no such attachment`);
5082
+ const fills = fillingSkins(view, slot.name, target.attachment);
5083
+ const definitions = fills.map((name) => view.skins.find((k) => k.name === name)?.attachments[slot.name]?.[target.attachment]);
5084
+ if ("conflict" in resolved || definitions.some((r) => !sameSetupMeshDefinition(r, definitions[0]))) {
5085
+ throw new CoreInputError(`${where}: skins [${fills.join(", ")}] fill this attachment differently; choose a named skin`);
5086
+ }
5087
+ const geometry = resolved.record.geometry;
5088
+ const mesh = geometry?.kind === "linkedmesh" ? view.skins.find((s) => s.name === geometry.skin)?.attachments[geometry.slot]?.[geometry.source]?.geometry : geometry;
5089
+ if (mesh?.kind !== "mesh") throw new CoreInputError(`${where}: ${resolved.record.kind} is not a mesh`);
5090
+ const { world, shown, drawOrder } = setupPosed(view, {}, RAW_MODE);
5091
+ const selected = shown.find((s) => s.slot === slot.name && s.placeholder === target.attachment && s.skin === resolved.skin);
5092
+ const bone2 = world.get(slot.bone);
5093
+ if (bone2 === void 0) throw new CoreInputError(`${where}: no setup transform for bone ${JSON.stringify(slot.bone)}`);
5094
+ const vertices = selected === void 0 ? numbersOf(worldVertices(mesh.vertices, bone2, world).map(rawNumber), `${where} vertex`, RAW_MODE) : rawDrawn(view, [selected], world, drawOrder, {}).drawn[0].vertices;
5095
+ return { slot: slot.name, attachment: target.attachment, skin: resolved.skin, vertices: [...vertices], triangles: [...mesh.triangles], hull: mesh.hull ?? null };
5096
+ }
4945
5097
  function setupPoseIn(doc, plant, mode) {
4946
5098
  const { world, shown, slots, drawOrder } = setupPosed(doc, plant, mode);
4947
5099
  const drawn = rawDrawn(doc, shown, world, drawOrder, plant, mode);
@@ -5015,7 +5167,7 @@ function animationOf(doc, animation) {
5015
5167
  if (anim === void 0) throw new CoreInputError(`animation "${animation}" is not one of this document's [${doc.animations.map((a) => a.name).join(", ")}]`);
5016
5168
  return anim;
5017
5169
  }
5018
- function openWalk(doc, anim, plant, reset, mode, shape, adjust) {
5170
+ function openWalk(doc, anim, plant, reset, mode, shape, adjust, before) {
5019
5171
  const why = constraintsAbsentWhy(doc) ?? pathAnimationsWhy(doc) ?? steppedPreviousPassWhy(doc);
5020
5172
  if (why !== null) throw new CoreInputError(`the raw walk leaves the bones out: ${why}`);
5021
5173
  const ctx = freshStepContext(plant.physicsStep);
@@ -8343,6 +8495,9 @@ function affine(u0, v0, u1, v1, u2, v2, x0, y0, x1, y1, x2, y2) {
8343
8495
  const d = (eu1 * ey2 - eu2 * ey1) * inv;
8344
8496
  return [a, b, c, d, x0 - a * u0 - c * v0, y0 - b * u0 - d * v0];
8345
8497
  }
8498
+ function readSetupMesh(assets, target) {
8499
+ return readSetupMesh$1(underNoSkin(assets.document.doc), target);
8500
+ }
8346
8501
  const SETS_SPECS = ["spine-parts-sets/1", "rig-parts-sets/1"];
8347
8502
  function isRecord(value) {
8348
8503
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -9867,6 +10022,7 @@ export {
9867
10022
  DomRenderer,
9868
10023
  RigPlayer,
9869
10024
  loadModel,
10025
+ readSetupMesh,
9870
10026
  revokeRegions,
9871
10027
  unpackRegions
9872
10028
  };