@rig-c/play 1.19.0 → 1.21.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/dist/index.js CHANGED
@@ -1556,19 +1556,19 @@ function readPathTimelines(value, label, declared, problems) {
1556
1556
  function pathWorld(state, c, g) {
1557
1557
  return worldVertices(g.vertices, state.world.get(c.slotBone), state.world, g.exact === true ? EXACT_COORDS : Math.fround);
1558
1558
  }
1559
- function onLineBefore(p, x1, y1, x2, y2, out, o) {
1559
+ function onLineBefore$1(p, x1, y1, x2, y2, out, o) {
1560
1560
  const r = Math.atan2(y2 - y1, x2 - x1);
1561
1561
  out[o] = x1 + p * Math.cos(r);
1562
1562
  out[o + 1] = y1 + p * Math.sin(r);
1563
1563
  out[o + 2] = r;
1564
1564
  }
1565
- function onLinePast(p, x0, y0, x1, y1, out, o) {
1565
+ function onLinePast$1(p, x0, y0, x1, y1, out, o) {
1566
1566
  const r = Math.atan2(y1 - y0, x1 - x0);
1567
1567
  out[o] = x1 + p * Math.cos(r);
1568
1568
  out[o + 1] = y1 + p * Math.sin(r);
1569
1569
  out[o + 2] = r;
1570
1570
  }
1571
- function onCurve(p, x1, y1, cx1, cy1, cx2, cy2, x2, y2, out, o, wantsTangent) {
1571
+ function onCurve$1(p, x1, y1, cx1, cy1, cx2, cy2, x2, y2, out, o, wantsTangent) {
1572
1572
  if (Number.isNaN(p)) {
1573
1573
  out[o] = x1;
1574
1574
  out[o + 1] = y1;
@@ -1619,12 +1619,12 @@ function positionsOf(state, c, g, spaces, wantsTangent) {
1619
1619
  } else if (p < 0) {
1620
1620
  const [x13, y13] = P(1);
1621
1621
  const [x23, y23] = P(2);
1622
- onLineBefore(p, x13, y13, x23, y23, out, o);
1622
+ onLineBefore$1(p, x13, y13, x23, y23, out, o);
1623
1623
  continue;
1624
1624
  } else if (p > total2) {
1625
1625
  const [x0, y0] = P(n - 3);
1626
1626
  const [x13, y13] = P(n - 2);
1627
- onLinePast(p - total2, x0, y0, x13, y13, out, o);
1627
+ onLinePast$1(p - total2, x0, y0, x13, y13, out, o);
1628
1628
  continue;
1629
1629
  }
1630
1630
  for (; ; curve++) {
@@ -1643,7 +1643,7 @@ function positionsOf(state, c, g, spaces, wantsTangent) {
1643
1643
  const [cx12, cy12] = q(1);
1644
1644
  const [cx22, cy22] = q(2);
1645
1645
  const [x22, y22] = q(3);
1646
- onCurve(p, x12, y12, cx12, cy12, cx22, cy22, x22, y22, out, o, wantsTangent || i > 0 && space === 0);
1646
+ onCurve$1(p, x12, y12, cx12, cy12, cx22, cy22, x22, y22, out, o, wantsTangent || i > 0 && space === 0);
1647
1647
  }
1648
1648
  return out;
1649
1649
  }
@@ -1671,11 +1671,11 @@ function positionsOf(state, c, g, spaces, wantsTangent) {
1671
1671
  curve = 0;
1672
1672
  segment = 0;
1673
1673
  } else if (p < 0) {
1674
- onLineBefore(p, chain[0], chain[1], chain[2], chain[3], out, o);
1674
+ onLineBefore$1(p, chain[0], chain[1], chain[2], chain[3], out, o);
1675
1675
  continue;
1676
1676
  } else if (p > total) {
1677
1677
  const e = chain.length;
1678
- onLinePast(p - total, chain[e - 4], chain[e - 3], chain[e - 2], chain[e - 1], out, o);
1678
+ onLinePast$1(p - total, chain[e - 4], chain[e - 3], chain[e - 2], chain[e - 1], out, o);
1679
1679
  continue;
1680
1680
  }
1681
1681
  for (; ; curve++) {
@@ -1738,7 +1738,7 @@ function positionsOf(state, c, g, spaces, wantsTangent) {
1738
1738
  }
1739
1739
  break;
1740
1740
  }
1741
- onCurve(p * 0.1, x1, y1, cx1, cy1, cx2, cy2, x2, y2, out, o, wantsTangent || i > 0 && space === 0);
1741
+ onCurve$1(p * 0.1, x1, y1, cx1, cy1, cx2, cy2, x2, y2, out, o, wantsTangent || i > 0 && space === 0);
1742
1742
  }
1743
1743
  return out;
1744
1744
  }
@@ -2114,10 +2114,10 @@ function posedPhysics(records, timelines, t, last, active, plant = {}, wrapped =
2114
2114
  function resetCrossed(keys, last, t, wrapped = false) {
2115
2115
  return wrapped ? keys.some((k) => k.time > last || k.time <= t) : keys.some((k) => last < k.time && k.time <= t);
2116
2116
  }
2117
- function freshPhysicsState() {
2117
+ function freshPhysicsState$1() {
2118
2118
  return { pending: true, lastTime: 0, remaining: 0, ux: 0, uy: 0, cx: 0, cy: 0, tx: 0, ty: 0, xOffset: 0, xLag: 0, xVelocity: 0, yOffset: 0, yLag: 0, yVelocity: 0, rotateOffset: 0, rotateLag: 0, rotateVelocity: 0, scaleOffset: 0, scaleLag: 0, scaleVelocity: 0 };
2119
2119
  }
2120
- function resetPhysicsState(s, time) {
2120
+ function resetPhysicsState$1(s, time) {
2121
2121
  s.remaining = 0;
2122
2122
  s.lastTime = time;
2123
2123
  s.pending = true;
@@ -2127,19 +2127,21 @@ function resetPhysicsState(s, time) {
2127
2127
  s.scaleOffset = s.scaleLag = s.scaleVelocity = 0;
2128
2128
  }
2129
2129
  const DEFAULT_PHYSICS_ENVIRONMENT = Object.freeze({ wind: Object.freeze([1, 0]), gravity: Object.freeze([0, 1]) });
2130
- function translatePhysics(ctx, x, y) {
2131
- for (const s of ctx.states.values()) {
2130
+ function translatePhysics(ctx, x, y, scope) {
2131
+ for (const [name, s] of ctx.states) {
2132
+ if (scope !== void 0 && !scope.has(name)) continue;
2132
2133
  s.ux -= x;
2133
2134
  s.uy -= y;
2134
2135
  s.cx -= x;
2135
2136
  s.cy -= y;
2136
2137
  }
2137
2138
  }
2138
- function rotatePhysics(ctx, x, y, degrees) {
2139
+ function rotatePhysics(ctx, x, y, degrees, scope) {
2139
2140
  const r = degrees * RUNTIME_RAD;
2140
2141
  const cos = Math.cos(r);
2141
2142
  const sin = Math.sin(r);
2142
- for (const s of ctx.states.values()) {
2143
+ for (const [name, s] of ctx.states) {
2144
+ if (scope !== void 0 && !scope.has(name)) continue;
2143
2145
  const dx = s.cx - x;
2144
2146
  const dy = s.cy - y;
2145
2147
  const tx = dx * cos - dy * sin - dx;
@@ -2153,13 +2155,13 @@ function rotatePhysics(ctx, x, y, degrees) {
2153
2155
  function physicsState(ctx, name) {
2154
2156
  let s = ctx.states.get(name);
2155
2157
  if (s === void 0) {
2156
- s = freshPhysicsState();
2158
+ s = freshPhysicsState$1();
2157
2159
  ctx.states.set(name, s);
2158
2160
  }
2159
2161
  return s;
2160
2162
  }
2161
- const PI2 = RUNTIME_PI * 2;
2162
- const INV_PI2 = 1 / PI2;
2163
+ const PI2$1 = RUNTIME_PI * 2;
2164
+ const INV_PI2$1 = 1 / PI2$1;
2163
2165
  function physicsDrives(r) {
2164
2166
  return { x: r.x > 0, y: r.y > 0, rotateOrShearX: r.rotate > 0 || r.shearX > 0, scaleX: r.scaleX > 0 };
2165
2167
  }
@@ -2169,7 +2171,7 @@ function stepPhysics(r, w, length, ctx) {
2169
2171
  const s = physicsState(ctx, r.name);
2170
2172
  const { x, y, rotateOrShearX, scaleX } = physicsDrives(r);
2171
2173
  const l = length;
2172
- if (ctx.phase === "reset") resetPhysicsState(s, ctx.time);
2174
+ if (ctx.phase === "reset") resetPhysicsState$1(s, ctx.time);
2173
2175
  const delta = Math.max(ctx.time - s.lastTime, 0);
2174
2176
  const shown = Math.max(0, 1 - s.remaining / r.step);
2175
2177
  s.remaining += delta;
@@ -2246,7 +2248,7 @@ function stepPhysics(r, w, length, ctx) {
2246
2248
  mr = (r.rotate + r.shearX) * mix;
2247
2249
  const lagged = s.rotateLag * shown;
2248
2250
  let rr = Math.atan2(dy + s.ty, dx + s.tx) - ca - (s.rotateOffset - lagged) * mr;
2249
- s.rotateOffset += (rr - Math.ceil(rr * INV_PI2 - 0.5) * PI2) * i;
2251
+ s.rotateOffset += (rr - Math.ceil(rr * INV_PI2$1 - 0.5) * PI2$1) * i;
2250
2252
  rr = (s.rotateOffset - lagged) * mr + ca;
2251
2253
  c = Math.cos(rr);
2252
2254
  sn = Math.sin(rr);
@@ -2353,7 +2355,7 @@ function stepPhysicsRecords(records, keyed, t, active, ctx, before) {
2353
2355
  ctx.last = t;
2354
2356
  ctx.setupRecords = new Map(physics.map((r) => [r.name, r]));
2355
2357
  ctx.keyedNow = new Set(keyed.flatMap((tl) => tl.kind === "reset" ? [] : (tl.name === EVERY_GLOBAL_PHYSICS ? unnamedPhysicsTargets(physics, tl.kind, (r) => physicsActive(r, active)).map((r) => r.name) : [tl.name]).map((n) => `${n}/${tl.kind === "mass" ? "massInverse" : tl.kind}`)));
2356
- for (const name of posed.reset) resetPhysicsState(physicsState(ctx, name), before);
2358
+ for (const name of posed.reset) resetPhysicsState$1(physicsState(ctx, name), before);
2357
2359
  return records.map((r) => r.kind === "physics" ? posed.records.get(r.name) : r);
2358
2360
  }
2359
2361
  function freshStepContext(step) {
@@ -2638,7 +2640,7 @@ function applySliderPhysics(r, at, alpha, target) {
2638
2640
  const named = target.records.get(tl.name);
2639
2641
  const targets = tl.name === EVERY_GLOBAL_PHYSICS ? unnamedPhysicsTargets([...target.records.values()], tl.kind, target.active) : named !== void 0 && target.active(named) ? [named] : [];
2640
2642
  if (tl.kind === "reset") {
2641
- if (!rules.sliderPhysicsResetIsDead && tl.keys.some((k) => k.time <= at)) for (const p of targets) resetPhysicsState(physicsState(target.ctx, p.name), target.ctx.time);
2643
+ if (!rules.sliderPhysicsResetIsDead && tl.keys.some((k) => k.time <= at)) for (const p of targets) resetPhysicsState$1(physicsState(target.ctx, p.name), target.ctx.time);
2642
2644
  continue;
2643
2645
  }
2644
2646
  const i = keyIndexAt(tl.keys, at);
@@ -5248,17 +5250,30 @@ function setEnvironmentOn(ctx, env, who) {
5248
5250
  function environmentOf(ctx) {
5249
5251
  return ctx.env ?? DEFAULT_PHYSICS_ENVIRONMENT;
5250
5252
  }
5251
- function translateOn(ctx, x, y, who) {
5253
+ function checkedPhysicsScope(doc, scope, who) {
5254
+ if (scope === void 0) return void 0;
5255
+ if (!Array.isArray(scope)) throw new CoreInputError(`${who} physics movement scope is ${String(scope)}, not an array of physics constraint names`);
5256
+ const selected = /* @__PURE__ */ new Set();
5257
+ for (const name of scope) {
5258
+ if (typeof name !== "string") throw new CoreInputError(`${who} physics movement scope name ${String(name)} is not a string`);
5259
+ const constraint = doc.constraints.find((c) => c.kind === "physics" && c.name === name) ?? doc.constraints.find((c) => c.name === name);
5260
+ if (constraint === void 0) throw new CoreInputError(`${who} physics movement scope: no constraint "${name}"; this document declares [${doc.constraints.map((c) => c.name).join(", ")}]`);
5261
+ if (constraint.kind !== "physics") throw new CoreInputError(`${who} physics movement scope: constraint "${name}" is ${constraint.kind}, not physics`);
5262
+ selected.add(name);
5263
+ }
5264
+ return selected;
5265
+ }
5266
+ function translateOn(ctx, x, y, who, scope) {
5252
5267
  for (const [k, v] of [["x", x], ["y", y]]) {
5253
5268
  if (typeof v !== "number" || !Number.isFinite(v)) throw new CoreInputError(`${who} physicsTranslate's ${k} is ${String(v)}, not a finite distance in the skeleton's world`);
5254
5269
  }
5255
- translatePhysics(ctx, x, y);
5270
+ translatePhysics(ctx, x, y, scope);
5256
5271
  }
5257
- function rotateOn(ctx, x, y, degrees, who) {
5272
+ function rotateOn(ctx, x, y, degrees, who, scope) {
5258
5273
  for (const [k, v] of [["x", x], ["y", y], ["degrees", degrees]]) {
5259
5274
  if (typeof v !== "number" || !Number.isFinite(v)) throw new CoreInputError(`${who} physicsRotate's ${k} is ${String(v)}, not a finite number`);
5260
5275
  }
5261
- rotatePhysics(ctx, x, y, degrees);
5276
+ rotatePhysics(ctx, x, y, degrees, scope);
5262
5277
  }
5263
5278
  function loopedTime(trackTime, duration) {
5264
5279
  return duration === 0 ? 0 : trackTime % duration;
@@ -5468,13 +5483,13 @@ class Track {
5468
5483
  this.live("clearPhysicsParameters");
5469
5484
  this.parameters.clear(name);
5470
5485
  }
5471
- physicsTranslate(x, y) {
5486
+ physicsTranslate(x, y, scope) {
5472
5487
  this.live("physicsTranslate");
5473
- translateOn(this.track.ctx, x, y, "the live track's");
5488
+ translateOn(this.track.ctx, x, y, "the live track's", checkedPhysicsScope(this.track.doc, scope, "the live track's"));
5474
5489
  }
5475
- physicsRotate(x, y, degrees) {
5490
+ physicsRotate(x, y, degrees, scope) {
5476
5491
  this.live("physicsRotate");
5477
- rotateOn(this.track.ctx, x, y, degrees, "the live track's");
5492
+ rotateOn(this.track.ctx, x, y, degrees, "the live track's", checkedPhysicsScope(this.track.doc, scope, "the live track's"));
5478
5493
  }
5479
5494
  /** A refused track refuses every call that would move it, naming the step it was refused at. */
5480
5495
  live(call) {
@@ -6426,13 +6441,13 @@ class Tracks {
6426
6441
  this.live("clearPhysicsParameters");
6427
6442
  this.parameters.clear(name);
6428
6443
  }
6429
- physicsTranslate(x, y) {
6444
+ physicsTranslate(x, y, scope) {
6430
6445
  this.live("physicsTranslate");
6431
- translateOn(this.path.ctx, x, y, "the live tracks'");
6446
+ translateOn(this.path.ctx, x, y, "the live tracks'", checkedPhysicsScope(this.doc, scope, "the live tracks'"));
6432
6447
  }
6433
- physicsRotate(x, y, degrees) {
6448
+ physicsRotate(x, y, degrees, scope) {
6434
6449
  this.live("physicsRotate");
6435
- rotateOn(this.path.ctx, x, y, degrees, "the live tracks'");
6450
+ rotateOn(this.path.ctx, x, y, degrees, "the live tracks'", checkedPhysicsScope(this.doc, scope, "the live tracks'"));
6436
6451
  }
6437
6452
  live(call) {
6438
6453
  if (this.refused !== null) throw new CoreInputError(`the live tracks were refused at ${this.refused}, after their entries and physics had moved; open them again (${call} not made)`);
@@ -6704,6 +6719,19 @@ function drawnRegions(doc, shown, order, source) {
6704
6719
  if (unmeasured.length > 0) return { drawn: null, why: `${unmeasured.join("; ")} — which of the two counts the runtime steps it over was not measured` };
6705
6720
  return { drawn, why: null };
6706
6721
  }
6722
+ function createAtlasRegions(pages, images) {
6723
+ const result = /* @__PURE__ */ new Map();
6724
+ for (const page of pages) {
6725
+ const image = images.get(page.name);
6726
+ if (!image) throw new Error(`Missing page image: ${page.name}`);
6727
+ for (const region of page.regions) result.set(region.name, { page, region, image });
6728
+ }
6729
+ return result;
6730
+ }
6731
+ function drawAtlasRegion(canvas, entry, ratio) {
6732
+ const plan = planCut(entry, entry.image);
6733
+ paintRegion(canvas, entry, entry.image, plan, ratio);
6734
+ }
6707
6735
  const ownedUrls = /* @__PURE__ */ new Set();
6708
6736
  function revokeOwned(url) {
6709
6737
  if (!ownedUrls.delete(url)) return;
@@ -6795,16 +6823,7 @@ async function cutRegion(placed, pageImages, encoding) {
6795
6823
  const plan = planCut(placed, image);
6796
6824
  if (plan.passThrough) return { url: image.src, width: w, height: h };
6797
6825
  const canvas = document.createElement("canvas");
6798
- canvas.width = plan.cw;
6799
- canvas.height = plan.ch;
6800
- const ctx = canvas.getContext("2d");
6801
- if (!ctx) throw new Error("2d context unavailable");
6802
- if (region.degrees === 90) {
6803
- ctx.translate(plan.cw, 0);
6804
- ctx.rotate(Math.PI / 2);
6805
- }
6806
- const source = straightAlphaSource(image, page.pma === true);
6807
- ctx.drawImage(source, plan.sx, plan.sy, plan.sw, plan.sh, 0, 0, plan.sw, plan.sh);
6826
+ paintRegion(canvas, placed, image, plan);
6808
6827
  if (encoding === "data") return { url: canvas.toDataURL("image/png"), width: w, height: h };
6809
6828
  const blob = await new Promise((resolve, reject) => {
6810
6829
  canvas.toBlob((b) => b ? resolve(b) : reject(new Error("toBlob failed")), "image/png");
@@ -6813,6 +6832,22 @@ async function cutRegion(placed, pageImages, encoding) {
6813
6832
  ownedUrls.add(url);
6814
6833
  return { url, width: w, height: h };
6815
6834
  }
6835
+ function paintRegion(canvas, placed, image, plan, ratio) {
6836
+ canvas.width = ratio === void 0 ? plan.cw : Math.min(plan.cw, Math.max(1, Math.ceil(placed.region.width * ratio)));
6837
+ canvas.height = ratio === void 0 ? plan.ch : Math.min(plan.ch, Math.max(1, Math.ceil(placed.region.height * ratio)));
6838
+ const ctx = canvas.getContext("2d");
6839
+ if (!ctx) throw new Error("2d context unavailable");
6840
+ if (ratio !== void 0) {
6841
+ ctx.imageSmoothingQuality = "high";
6842
+ ctx.scale(canvas.width / plan.cw, canvas.height / plan.ch);
6843
+ }
6844
+ if (placed.region.degrees === 90) {
6845
+ ctx.translate(plan.cw, 0);
6846
+ ctx.rotate(Math.PI / 2);
6847
+ }
6848
+ const source = straightAlphaSource(image, placed.page.pma === true);
6849
+ ctx.drawImage(source, plan.sx, plan.sy, plan.sw, plan.sh, 0, 0, plan.sw, plan.sh);
6850
+ }
6816
6851
  async function unpackRegions(pages, pageImages, options = {}) {
6817
6852
  const encoding = options.encoding ?? "blob";
6818
6853
  if (encoding !== "blob" && encoding !== "data") throw new Error(`encoding must be 'blob' or 'data', got ${String(options.encoding)}`);
@@ -7404,14 +7439,17 @@ class DomRenderer {
7404
7439
  * and scaling of the root belong to the caller.
7405
7440
  * @param regionImages Unpacked per-region bitmaps (for the rigid tier),
7406
7441
  * keyed by region name — `PosedRegion.region`.
7442
+ * @param atlasRegions Optional page-backed rigid sources, from createAtlasRegions
7443
+ * or ModelAssets.atlasRegions. A matching entry takes precedence over regionImages.
7407
7444
  * @param pages Page images (for the mesh tier), keyed by page name —
7408
7445
  * `PosedMesh.page`. Read on every mesh draw, so a caller may fill it after
7409
7446
  * construction.
7410
7447
  */
7411
- constructor(root, regionImages, pages = /* @__PURE__ */ new Map()) {
7448
+ constructor(root, regionImages, pages = /* @__PURE__ */ new Map(), atlasRegions = /* @__PURE__ */ new Map()) {
7412
7449
  this.root = root;
7413
7450
  this.regionImages = regionImages;
7414
7451
  this.pages = pages;
7452
+ this.atlasRegions = atlasRegions;
7415
7453
  }
7416
7454
  /**
7417
7455
  * Apply the clips the frame carries (default). Each element drawn inside a
@@ -7436,6 +7474,8 @@ class DomRenderer {
7436
7474
  clipping = true;
7437
7475
  /** Clips applied last frame — distinct clip polygons over the drawn slots. */
7438
7476
  clipCount = 0;
7477
+ /** Atlas rigid canvases rasterized in the last frame; never includes meshes. */
7478
+ rigidRasterCount = 0;
7439
7479
  /**
7440
7480
  * Clips in the frame NOT applied last frame: `clipping` is false, or the
7441
7481
  * polygon has fewer than three points. A clip the runtime itself ignores (a
@@ -7605,6 +7645,7 @@ class DomRenderer {
7605
7645
  render(input) {
7606
7646
  const frame2 = renderFrameOf(input);
7607
7647
  this.clipCount = 0;
7648
+ this.rigidRasterCount = 0;
7608
7649
  this.clipSkipCount = 0;
7609
7650
  this.clipWriteCount = 0;
7610
7651
  this.meshCount = 0;
@@ -7691,16 +7732,10 @@ class DomRenderer {
7691
7732
  }
7692
7733
  // --- rigid tier -----------------------------------------------------------
7693
7734
  renderRegion(slot) {
7694
- const regionImage = this.regionImages.get(slot.region);
7735
+ const atlas = this.atlasRegions.get(slot.region);
7736
+ const regionImage = atlas ? atlas.region : this.regionImages.get(slot.region);
7695
7737
  if (!regionImage) return null;
7696
- const view = this.view(slot.slot, "image");
7697
- const img = view.el;
7698
- if (view.regionName !== slot.region) {
7699
- view.regionName = slot.region;
7700
- img.src = regionImage.url;
7701
- img.width = regionImage.width;
7702
- img.height = regionImage.height;
7703
- }
7738
+ const view = this.view(slot.slot, atlas ? "region-canvas" : "image");
7704
7739
  const corners = slot.corners;
7705
7740
  for (let v = 0; v < 8; v++) regionVertices[v] = corners[v];
7706
7741
  const blx = regionVertices[0], bly = -regionVertices[1];
@@ -7712,6 +7747,23 @@ class DomRenderer {
7712
7747
  const b = (ury - uly) / w;
7713
7748
  const c = (blx - ulx) / h;
7714
7749
  const d = (bly - uly) / h;
7750
+ const effective = this.pixelRatio * Math.max(Math.hypot(a, b), Math.hypot(c, d));
7751
+ const band = effective > 0 && effective < 1 ? 1.25 ** Math.ceil(Math.log(effective) / Math.log(1.25)) : 1;
7752
+ if (view.regionName !== slot.region || atlas && view.regionRatio !== band) {
7753
+ view.regionName = slot.region;
7754
+ view.regionRatio = band;
7755
+ if (atlas) {
7756
+ drawAtlasRegion(view.el, atlas, band < 1 ? band : void 0);
7757
+ view.el.style.width = `${w}px`;
7758
+ view.el.style.height = `${h}px`;
7759
+ this.rigidRasterCount++;
7760
+ } else {
7761
+ const img = view.el;
7762
+ img.src = this.regionImages.get(slot.region).url;
7763
+ img.width = w;
7764
+ img.height = h;
7765
+ }
7766
+ }
7715
7767
  this.setTransform(view, `matrix(${a},${b},${c},${d},${ulx},${uly})`);
7716
7768
  this.applyClip(view, a, b, c, d, ulx, uly, w, h);
7717
7769
  this.applyCommon(view, slot);
@@ -8136,6 +8188,7 @@ class DomRenderer {
8136
8188
  kind,
8137
8189
  el,
8138
8190
  regionName: "",
8191
+ regionRatio: -1,
8139
8192
  visible: false,
8140
8193
  zIndex: -1,
8141
8194
  opacity: 1,
@@ -8945,8 +8998,8 @@ async function loadModel(options) {
8945
8998
  );
8946
8999
  }
8947
9000
  const regions = options.regions ?? "blob";
8948
- if (regions !== "blob" && regions !== "data" && regions !== "none") {
8949
- throw new Error(`regions must be 'blob', 'data' or 'none', got ${String(options.regions)}`);
9001
+ if (regions !== "atlas" && regions !== "blob" && regions !== "data" && regions !== "none") {
9002
+ throw new Error(`regions must be 'atlas', 'blob', 'data' or 'none', got ${String(options.regions)}`);
8950
9003
  }
8951
9004
  const fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
8952
9005
  const resolvePage = options.resolvePage ?? resolveAgainstModel;
@@ -8959,7 +9012,7 @@ async function loadModel(options) {
8959
9012
  })
8960
9013
  );
8961
9014
  const sets = await loadSets(fetchImpl, modelUrl, options.setsUrl, read.doc);
8962
- const regionImages = regions === "none" ? /* @__PURE__ */ new Map() : await unpackRegions(pages, pageImages, { encoding: regions });
9015
+ const regionImages = regions === "none" || regions === "atlas" ? /* @__PURE__ */ new Map() : await unpackRegions(pages, pageImages, { encoding: regions });
8963
9016
  let disposed = false;
8964
9017
  return {
8965
9018
  spec: read.spec,
@@ -8969,6 +9022,7 @@ async function loadModel(options) {
8969
9022
  stage: stageOf(read.doc),
8970
9023
  regions,
8971
9024
  regionImages,
9025
+ atlasRegions: regions === "atlas" ? createAtlasRegions(pages, pageImages) : /* @__PURE__ */ new Map(),
8972
9026
  pageImages,
8973
9027
  document: read,
8974
9028
  dispose() {
@@ -8991,26 +9045,30 @@ function compileCurve(keys, channels) {
8991
9045
  for (let i = 0; i < n; i++) {
8992
9046
  const a = keys[i];
8993
9047
  times2[i] = a.time;
8994
- for (let c = 0; c < channels; c++) values2[i * channels + c] = a.values[c];
8995
- const curve = a.curve;
9048
+ const av = a.values;
9049
+ if (av !== void 0) for (let c = 0; c < channels; c++) values2[i * channels + c] = av[c];
9050
+ const curve = a.curve ?? "stepped";
8996
9051
  kinds[i] = curve === "linear" ? CURVE_LINEAR : curve === "stepped" ? CURVE_STEPPED : CURVE_BEZIER;
8997
9052
  if (typeof curve === "string" || i + 1 >= n) continue;
8998
9053
  const b = keys[i + 1];
9054
+ const aStated = a.stated;
9055
+ const bStated = b.stated;
9056
+ const bValues = b.values;
8999
9057
  for (let c = 0; c < channels; c++) {
9000
- const inner = bezierPolyline(a.stated.time, a.stated.values[c], curve[c * 4], curve[c * 4 + 1], curve[c * 4 + 2], curve[c * 4 + 3], b.stated.time, b.stated.values[c]);
9058
+ const inner = bezierPolyline(aStated.time, aStated.values[c], curve[c * 4], curve[c * 4 + 1], curve[c * 4 + 2], curve[c * 4 + 3], bStated.time, bStated.values[c]);
9001
9059
  const o = (i * channels + c) * POLYLINE;
9002
9060
  polylines[o] = a.time;
9003
- polylines[o + 1] = a.values[c];
9061
+ polylines[o + 1] = av[c];
9004
9062
  for (let q = 0; q < inner.length; q++) polylines[o + 2 + q] = inner[q];
9005
9063
  polylines[o + POLYLINE - 2] = b.time;
9006
- polylines[o + POLYLINE - 1] = b.values[c];
9064
+ polylines[o + POLYLINE - 1] = bValues[c];
9007
9065
  }
9008
9066
  }
9009
9067
  return { keys: n, channels, times: times2, values: values2, kinds, polylines };
9010
9068
  }
9011
9069
  function keyAt(curve, cursors, slot, t) {
9012
9070
  const times2 = curve.times;
9013
- let i = cursors[slot];
9071
+ let i = Math.min(cursors[slot], curve.keys - 1);
9014
9072
  while (i >= 0 && times2[i] > t) i--;
9015
9073
  while (i + 1 < curve.keys && times2[i + 1] <= t) i++;
9016
9074
  cursors[slot] = i;
@@ -9065,7 +9123,7 @@ function deformFraction(curve, i, t) {
9065
9123
  if (q === POLYLINE - 2) return y0 + (1 - y0) * (t - x0) / (x1 - x0);
9066
9124
  return y0 + (t - x0) / (x1 - x0) * (y1 - y0);
9067
9125
  }
9068
- const RUNTIME_STAGE = 2;
9126
+ const RUNTIME_STAGE = 9;
9069
9127
  function missingText(m) {
9070
9128
  return `${m.feature}: ${m.detail}${m.animation === null ? "" : ` (animation "${m.animation}")`}`;
9071
9129
  }
@@ -9108,6 +9166,15 @@ const SCALE_Y_VOLUME = 2;
9108
9166
  const IK_BEND_POSITIVE = 1;
9109
9167
  const IK_COMPRESS = 2;
9110
9168
  const IK_STRETCH = 4;
9169
+ const PATH_POSITION_FIXED = 0;
9170
+ const PATH_POSITION_PERCENT = 1;
9171
+ const PATH_SPACING_LENGTH = 0;
9172
+ const PATH_SPACING_FIXED = 1;
9173
+ const PATH_SPACING_PERCENT = 2;
9174
+ const PATH_SPACING_PROPORTIONAL = 3;
9175
+ const PATH_ROTATE_TANGENT = 0;
9176
+ const PATH_ROTATE_CHAIN = 1;
9177
+ const PATH_ROTATE_CHAIN_SCALE = 2;
9111
9178
  const RAD = RUNTIME_PI / 180;
9112
9179
  const BONE_KINDS = {
9113
9180
  rotate: [[L_ROTATION, 0]],
@@ -9121,7 +9188,7 @@ const BONE_KINDS = {
9121
9188
  shearx: [[L_SHEAR_X, 0]],
9122
9189
  sheary: [[L_SHEAR_Y, 0]]
9123
9190
  };
9124
- const COLOUR_CHANNELS = { rgba: [0, 1, 2, 3], rgb: [0, 1, 2], alpha: [3] };
9191
+ const COLOUR_CHANNELS = { rgba: [0, 1, 2, 3], rgb: [0, 1, 2], alpha: [3], rgba2: [0, 1, 2, 3, 4, 5, 6], rgb2: [0, 1, 2, 4, 5, 6] };
9125
9192
  class Refusals {
9126
9193
  constructor(admitted) {
9127
9194
  this.admitted = admitted;
@@ -9143,7 +9210,7 @@ class Refusals {
9143
9210
  function compileRuntime(doc, options = {}, plant = {}) {
9144
9211
  const refusals = new Refusals(new Set(plant.admit ?? []));
9145
9212
  const ignoreEvents = (options.ignore ?? []).includes("events");
9146
- const absentWhy = constraintsAbsentWhy(doc);
9213
+ const absentWhy = constraintsAbsentWhy(doc) ?? pathAnimationsWhy(doc) ?? steppedPreviousPassWhy(doc);
9147
9214
  if (absentWhy !== null) refusals.note("oracle-absent", `the oracle leaves the bones out: ${absentWhy}`);
9148
9215
  const nb = doc.bones.length;
9149
9216
  const boneIndex2 = new Map(doc.bones.map((b, i) => [b.name, i]));
@@ -9152,6 +9219,12 @@ function compileRuntime(doc, options = {}, plant = {}) {
9152
9219
  if (i === void 0) throw new RuntimeInputError(`bone "${name}" is not a bone of this document (readModel refuses it first)`);
9153
9220
  return i;
9154
9221
  };
9222
+ const slotIndex = new Map(doc.slots.map((s, i) => [s.name, i]));
9223
+ const slotIndexOf = (name) => {
9224
+ const i = slotIndex.get(name);
9225
+ if (i === void 0) throw new RuntimeInputError(`slot "${name}" is not a slot of this document (readModel refuses it first)`);
9226
+ return i;
9227
+ };
9155
9228
  const active = activeBones(doc);
9156
9229
  const parents = new Int32Array(nb);
9157
9230
  const modes = new Uint8Array(nb);
@@ -9186,27 +9259,35 @@ function compileRuntime(doc, options = {}, plant = {}) {
9186
9259
  }
9187
9260
  subtreeStart[nb] = flat.length;
9188
9261
  const constraints = [];
9189
- for (const c of doc.constraints) if (c.kind === "path" || c.kind === "physics" || c.kind === "slider") refusals.note(`${c.kind}-constraint`, `${c.kind} constraint "${c.name}"`);
9190
- for (const r of constraintRecords(doc)) {
9191
- if (r.kind !== "ik" && r.kind !== "transform") continue;
9262
+ const records = constraintRecords(doc);
9263
+ const skipped = /* @__PURE__ */ new Set();
9264
+ records.forEach((r, i) => {
9265
+ if (constraintInactiveWhy(r, active) !== null) skipped.add(i);
9266
+ });
9267
+ const pathPlan = records.some((r) => r.kind === "path") ? slotBonePlan(doc.bones, active, records, skipped) : /* @__PURE__ */ new Map();
9268
+ for (const r of records) {
9192
9269
  const applied = constraintInactiveWhy(r, active) === null;
9193
- const bones = Int32Array.from(r.bones.map(indexOf));
9194
- if (applied && r.bones.some((b) => !active.has(b))) refusals.note("inactive-constrained-bone", `${r.kind} constraint "${r.name}" names bone(s) ${r.bones.filter((b) => !active.has(b)).map((b) => `"${b}"`).join(", ")}, which the view leaves inactive — what the oracle writes there depends on the pass before`);
9270
+ const bones = Int32Array.from((r.kind === "physics" ? [r.bone] : r.kind === "slider" ? r.timelines.bones.filter((t) => t.timelines.length > 0).map((t) => t.name) : r.bones).map(indexOf));
9195
9271
  const index = constraints.length;
9272
+ if (r.kind === "physics") {
9273
+ constraints.push({ kind: "physics", name: r.name, index, applied, bones, setup: r });
9274
+ continue;
9275
+ }
9276
+ if (r.kind === "slider") {
9277
+ constraints.push({ kind: "slider", name: r.name, index, applied, bones, setup: r, bone: r.bone === null ? -1 : indexOf(r.bone), property: TRANSFORM_PROPERTIES.indexOf(r.property), animation: null });
9278
+ continue;
9279
+ }
9280
+ if (applied && r.bones.some((b) => !active.has(b))) refusals.note("inactive-constrained-bone", `${r.kind} constraint "${r.name}" names bone(s) ${r.bones.filter((b) => !active.has(b)).map((b) => `"${b}"`).join(", ")}, which the view leaves inactive — what the oracle writes there depends on the pass before`);
9196
9281
  if (r.kind === "ik") constraints.push(compileIk(r, index, applied, bones, indexOf));
9197
- else {
9198
- if (r.localSource || r.localTarget) refusals.note("local-transform", `transform constraint "${r.name}" ${[r.localSource ? "reads local values (localSource)" : "", r.localTarget ? "writes local values (localTarget)" : ""].filter((x) => x !== "").join(" and ")}`);
9199
- if (r.additive) refusals.note("additive-transform", `transform constraint "${r.name}" is additive`);
9200
- const t = compileTransform(r, index, applied, bones, indexOf);
9201
- if (applied) {
9202
- for (let k = 0; k < bones.length; k++) {
9203
- const m = modes[bones[k]];
9204
- if (parents[bones[k]] >= 0 && (m === MODE_NO_SCALE || m === MODE_NO_SCALE_OR_REFLECTION)) refusals.note("noscale-world-target", `transform constraint "${r.name}" moves bone "${r.bones[k]}" in world space, and its local values are read back in a noScale mode`);
9205
- }
9206
- }
9282
+ else if (r.kind === "path") {
9283
+ constraints.push(compilePath(r, index, applied, bones, indexOf, slotIndexOf, pathPlan.get(index) ?? null));
9284
+ } else {
9285
+ const noScaleWorld = !r.localTarget && bones.some((b) => parents[b] >= 0 && (modes[b] === MODE_NO_SCALE || modes[b] === MODE_NO_SCALE_OR_REFLECTION));
9286
+ const t = compileTransform(r, index, applied && !(plant.omitLocalTransform && (r.localSource || r.localTarget)) && !(plant.omitNoScaleWorldTransform && noScaleWorld) && !(plant.omitAdditiveTransform && r.additive), bones, indexOf);
9207
9287
  constraints.push(t);
9208
9288
  }
9209
9289
  }
9290
+ const hasClipping = doc.skins.some((s) => Object.values(s.attachments).some((table) => Object.values(table).some((at) => at.geometry?.kind === "clipping")));
9210
9291
  const attachments = [];
9211
9292
  let vertexCount = 0;
9212
9293
  let deformCount = 0;
@@ -9233,8 +9314,9 @@ function compileRuntime(doc, options = {}, plant = {}) {
9233
9314
  const at = { timeline: timelineIdentity(slot.name, shown), deformOffset: deformCount, slot: si, name: row.name, skin: shown.skin, placeholder: shown.placeholder, path: CORE_REGION_KINDS.has(shown.record.kind) ? row.path : null, bone: indexOf(slot.bone), offset: vertexCount };
9234
9315
  let made = null;
9235
9316
  let missing = null;
9236
- if (g?.kind === "clipping") missing = ["clipping", `slot "${slot.name}" shows clipping attachment "${row.name}"`];
9237
- else if (g?.kind === "region") {
9317
+ if (g?.kind === "clipping") {
9318
+ made = { ...meshAttachment(at, g.vertices, indexOf, plant.bindingIndex, nb), clip: { end: g.end, inverse: g.inverse, convex: g.convex } };
9319
+ } else if (g?.kind === "region") {
9238
9320
  const r = g.region;
9239
9321
  if (r.atlas === null) missing = ["null-atlas-region", `slot "${slot.name}" shows region "${row.name}", whose atlas rectangle the model states as null`];
9240
9322
  else {
@@ -9268,8 +9350,18 @@ function compileRuntime(doc, options = {}, plant = {}) {
9268
9350
  const source = g.kind === "mesh" ? g.vertices : sourceOf(g.skin, g.slot, g.source);
9269
9351
  if (typeof source === "string") throw new RuntimeInputError(`slot "${slot.name}": ${source} (readModel refuses it first)`);
9270
9352
  made = meshAttachment(at, source, indexOf, plant.bindingIndex, nb);
9353
+ } else if (g?.kind === "path") {
9354
+ made = pathAttachment(at, g.vertices, indexOf, plant.bindingIndex, nb);
9271
9355
  }
9272
9356
  if (made === null) made = vertexAttachment(at, DRAW_NONE, new Float64Array(0), 0);
9357
+ if (hasClipping) {
9358
+ if (g?.kind === "region") made = { ...made, clipTopology: { triangles: new Int32Array([0, 1, 2, 2, 3, 0]), uvs: new Float64Array([0, 1, 0, 0, 1, 0, 1, 1]) } };
9359
+ else if (g?.kind === "mesh" || g?.kind === "linkedmesh") {
9360
+ const mesh = g.kind === "mesh" ? g : doc.skins.find((s) => s.name === g.skin)?.attachments[g.slot]?.[g.source]?.geometry;
9361
+ if (mesh?.kind !== "mesh") throw new RuntimeInputError("clipping topology: linked source is not a mesh (readModel refuses it first)");
9362
+ made = { ...made, clipTopology: { triangles: Int32Array.from(mesh.triangles), uvs: Float64Array.from(mesh.uvs) } };
9363
+ }
9364
+ }
9273
9365
  attachments.push(made);
9274
9366
  vertexCount += made.count;
9275
9367
  deformCount += made.deformCount;
@@ -9289,7 +9381,21 @@ function compileRuntime(doc, options = {}, plant = {}) {
9289
9381
  blend: readBlend(slot)
9290
9382
  };
9291
9383
  });
9292
- const slotIndex = new Map(doc.slots.map((s, i) => [s.name, i]));
9384
+ let hasPath = false;
9385
+ let maxPathSpaces = 0;
9386
+ let maxPathVertices = 0;
9387
+ for (const c of constraints) {
9388
+ if (c.kind !== "path") continue;
9389
+ hasPath = true;
9390
+ c.attachment = c.setup.path === null ? -1 : slots[c.slot].setupAttachment;
9391
+ const spaceCount = c.rotateMode === PATH_ROTATE_TANGENT ? c.bones.length : c.bones.length + 1;
9392
+ if (spaceCount > maxPathSpaces) maxPathSpaces = spaceCount;
9393
+ if (c.attachment >= 0) {
9394
+ const at = attachments[c.attachment];
9395
+ c.pathVertices = at.bindingCounts.length > 0 ? at.bindingCounts.length * 2 : at.local.length;
9396
+ if (c.pathVertices > maxPathVertices) maxPathVertices = c.pathVertices;
9397
+ }
9398
+ }
9293
9399
  const animations = /* @__PURE__ */ new Map();
9294
9400
  const refused = [];
9295
9401
  let cursorMax = 0;
@@ -9302,11 +9408,25 @@ function compileRuntime(doc, options = {}, plant = {}) {
9302
9408
  cursorMax = Math.max(cursorMax, compiled.cursors);
9303
9409
  }
9304
9410
  }
9411
+ for (const c of constraints) if (c.kind === "slider") {
9412
+ const a = animations.get(c.setup.animation);
9413
+ if (a !== void 0) c.animation = a;
9414
+ else for (const m of refusals.byAnimation.get(c.setup.animation) ?? []) refusals.note(m.feature, `slider "${c.name}" applies refused animation "${c.setup.animation}": ${m.detail}`);
9415
+ }
9305
9416
  if (refusals.model.length > 0) throw new RuntimeRefusal(refusals.model);
9306
9417
  const names = doc.animations.map((a) => a.name);
9307
9418
  return {
9419
+ omitPhysics: plant.omitPhysics === true,
9420
+ sliderAsTrack: plant.sliderAsTrack === true,
9308
9421
  omitDeform: plant.omitDeform === true,
9309
9422
  deformAfterWeights: plant.deformAfterWeights === true,
9423
+ omitPath: plant.omitPath === true,
9424
+ ignorePhysicsScope: plant.ignorePhysicsScope === true,
9425
+ omitDrawOrder: plant.omitDrawOrder === true,
9426
+ omitDarkColour: plant.omitDarkColour === true,
9427
+ hasPath,
9428
+ maxPathSpaces,
9429
+ maxPathVertices,
9310
9430
  deformCount,
9311
9431
  eventWrapOffByOne: plant.eventWrapOffByOne === true,
9312
9432
  restoreEventsFromStart: plant.restoreEventsFromStart === true,
@@ -9365,6 +9485,10 @@ function meshAttachment(at, vertices, indexOf, rewrite, bones) {
9365
9485
  }
9366
9486
  return { ...at, deformCount: ids.length * 2, draw: DRAW_WEIGHTED, local: NO_NUMBERS, count: counts.length * 2, bindingCounts: Int32Array.from(counts), bindingBones: Int32Array.from(ids), bindingX: Float64Array.from(xs), bindingY: Float64Array.from(ys), bindingWeights: Float64Array.from(ws) };
9367
9487
  }
9488
+ function pathAttachment(at, vertices, indexOf, rewrite, bones) {
9489
+ const m = meshAttachment(at, vertices, indexOf, rewrite, bones);
9490
+ return { ...m, draw: DRAW_NONE, count: 0 };
9491
+ }
9368
9492
  function compileIk(r, index, applied, bones, indexOf) {
9369
9493
  return {
9370
9494
  kind: "ik",
@@ -9382,6 +9506,34 @@ function compileIk(r, index, applied, bones, indexOf) {
9382
9506
  function ikFlags(bendPositive, compress, stretch) {
9383
9507
  return (bendPositive ? IK_BEND_POSITIVE : 0) | (compress ? IK_COMPRESS : 0) | (stretch ? IK_STRETCH : 0);
9384
9508
  }
9509
+ function compilePath(r, index, applied, bones, indexOf, slotIndexOf, ev) {
9510
+ return {
9511
+ kind: "path",
9512
+ name: r.name,
9513
+ index,
9514
+ applied,
9515
+ bones,
9516
+ slot: slotIndexOf(r.slot),
9517
+ slotBone: indexOf(r.slotBone),
9518
+ attachment: -1,
9519
+ pathVertices: 0,
9520
+ closed: r.path?.closed ?? false,
9521
+ constantSpeed: r.path?.constantSpeed ?? true,
9522
+ lengths: Float64Array.from(r.path?.lengths ?? []),
9523
+ positionMode: r.positionMode === "fixed" ? PATH_POSITION_FIXED : PATH_POSITION_PERCENT,
9524
+ spacingMode: r.spacingMode === "fixed" ? PATH_SPACING_FIXED : r.spacingMode === "percent" ? PATH_SPACING_PERCENT : r.spacingMode === "proportional" ? PATH_SPACING_PROPORTIONAL : PATH_SPACING_LENGTH,
9525
+ rotateMode: r.rotateMode === "chain" ? PATH_ROTATE_CHAIN : r.rotateMode === "chainScale" ? PATH_ROTATE_CHAIN_SCALE : PATH_ROTATE_TANGENT,
9526
+ offsetRotation: r.offsetRotation,
9527
+ position: r.position,
9528
+ spacing: r.spacing,
9529
+ mixRotate: r.mixRotate,
9530
+ mixX: r.mixX,
9531
+ mixY: r.mixY,
9532
+ slotEventAt: ev === null ? -1 : ev.at,
9533
+ slotEventAfter: ev?.when === "after",
9534
+ setup: r
9535
+ };
9536
+ }
9385
9537
  function compileTransform(r, index, applied, bones, indexOf) {
9386
9538
  const fromProperty = [];
9387
9539
  const fromOffset = [];
@@ -9420,7 +9572,10 @@ function compileTransform(r, index, applied, bones, indexOf) {
9420
9572
  toMax: Float64Array.from(toMax),
9421
9573
  offsets: Float64Array.from(TRANSFORM_PROPERTIES.map((p) => r.offsets[p])),
9422
9574
  mixes: Float64Array.from(TRANSFORM_PROPERTIES.map((p) => r.mixes[p])),
9423
- clamp: r.clamp
9575
+ clamp: r.clamp,
9576
+ localSource: r.localSource,
9577
+ localTarget: r.localTarget,
9578
+ additive: r.additive
9424
9579
  };
9425
9580
  }
9426
9581
  function compileAnimation(anim, refusals, ignoreEvents, boneIndex2, slotIndex, slots, constraints, doc, resolve) {
@@ -9430,21 +9585,52 @@ function compileAnimation(anim, refusals, ignoreEvents, boneIndex2, slotIndex, s
9430
9585
  const deform = tl.attachments.filter((a) => a.deform !== null);
9431
9586
  const sequence = tl.attachments.filter((a) => a.sequence !== null);
9432
9587
  if (sequence.length > 0) refusals.note("sequence-timeline", `${sequence.length} sequence timeline(s): ${sequence.slice(0, 3).map((a) => `${a.skin}/${a.slot}/${a.attachment}`).join(", ")}${sequence.length > 3 ? ", …" : ""}`, name);
9433
- if (tl.drawOrder.length > 0) refusals.note("draw-order-timeline", `${tl.drawOrder.length} draw-order key(s)`, name);
9588
+ let drawOrder = null;
9589
+ if (tl.drawOrder.length > 0) {
9590
+ const ns = slots.length;
9591
+ const orders = new Int32Array(tl.drawOrder.length * ns).fill(-1);
9592
+ const moved = new Uint8Array(ns);
9593
+ for (let i = 0; i < tl.drawOrder.length; i++) {
9594
+ const base = i * ns;
9595
+ moved.fill(0);
9596
+ for (const m of tl.drawOrder[i].moves) {
9597
+ orders[base + m.slot + m.offset] = m.slot;
9598
+ moved[m.slot] = 1;
9599
+ }
9600
+ for (let s = 0, d = 0; s < ns; s++) {
9601
+ if (moved[s] !== 0) continue;
9602
+ while (orders[base + d] !== -1) d++;
9603
+ orders[base + d] = s;
9604
+ d++;
9605
+ }
9606
+ }
9607
+ drawOrder = { curve: compileCurve(tl.drawOrder, 0), cursor: cursors++, orders };
9608
+ }
9434
9609
  const lastBy = (targets) => new Map(targets.map((t) => [t.name, t]));
9435
9610
  const bones = [];
9436
- for (const target of lastBy(tl.bones).values()) {
9611
+ const byTarget = /* @__PURE__ */ new Map();
9612
+ const compileBones = (target, forSlider) => {
9613
+ const cached = byTarget.get(target);
9614
+ if (cached !== void 0) return cached;
9615
+ const keyed = [];
9616
+ byTarget.set(target, keyed);
9437
9617
  const bone2 = boneIndex2.get(target.name);
9438
- if (bone2 === void 0) continue;
9618
+ if (bone2 === void 0) return keyed;
9439
9619
  for (const t of target.timelines) {
9440
9620
  if (t.kind === "inherit") {
9441
- refusals.note("inherit-timeline", `bone "${target.name}" keys its inherit mode`, name);
9621
+ if (!forSlider) refusals.note("inherit-timeline", `bone "${target.name}" keys its inherit mode`, name);
9622
+ else for (const c of constraints) if (c.kind === "slider" && c.setup.animation === name) {
9623
+ refusals.note("inherit-timeline", `slider "${c.name}" applies animation "${name}" whose bone "${target.name}" keys its inherit mode`);
9624
+ }
9442
9625
  continue;
9443
9626
  }
9444
9627
  const spec = BONE_KINDS[t.kind];
9445
- bones.push({ bone: bone2, curve: compileCurve(t.keys, spec.length), cursor: cursors++, fields: Int32Array.from(spec.map((s) => s[0])), multiply: Uint8Array.from(spec.map((s) => s[1])) });
9628
+ keyed.push({ bone: bone2, curve: compileCurve(t.keys, spec.length), cursor: cursors++, fields: Int32Array.from(spec.map((s) => s[0])), multiply: Uint8Array.from(spec.map((s) => s[1])) });
9446
9629
  }
9447
- }
9630
+ return keyed;
9631
+ };
9632
+ for (const target of lastBy(tl.bones).values()) bones.push(...compileBones(target, false));
9633
+ const sliderBones = constraints.some((c) => c.kind === "slider" && c.setup.animation === name) ? tl.bones.flatMap((target) => compileBones(target, true)) : [];
9448
9634
  const slotTimelines = [];
9449
9635
  const keyedSlots = [...lastBy(tl.slots).values()].flatMap((target) => {
9450
9636
  const si = slotIndex.get(target.name);
@@ -9456,8 +9642,6 @@ function compileAnimation(anim, refusals, ignoreEvents, boneIndex2, slotIndex, s
9456
9642
  if (t.kind === "attachment") {
9457
9643
  const ids = Int32Array.from(t.keys.map((k) => resolve(si, k.name ?? null)));
9458
9644
  slotTimelines.push({ slot: si, curve: compileCurve(t.keys, 0), cursor: cursors++, attachments: ids, channels: null });
9459
- } else if (t.kind === "rgba2" || t.kind === "rgb2") {
9460
- refusals.note("dark-colour-timeline", `slot "${target.name}" keys an ${t.kind} timeline`, name);
9461
9645
  } else {
9462
9646
  const channels = COLOUR_CHANNELS[t.kind];
9463
9647
  slotTimelines.push({ slot: si, curve: compileCurve(t.keys, channels.length), cursor: cursors++, attachments: null, channels: Int32Array.from(channels) });
@@ -9470,12 +9654,37 @@ function compileAnimation(anim, refusals, ignoreEvents, boneIndex2, slotIndex, s
9470
9654
  const keyed = anim.constraints.ik.find((x) => x.name === c.name);
9471
9655
  if (keyed === void 0) continue;
9472
9656
  constraintTimelines[c.index] = { constraint: c.index, curve: compileCurve(keyed.keys, 2), cursor: cursors++, flags: Uint8Array.from(keyed.keys.map((k) => ikFlags(k.flags.bendPositive, k.flags.compress, k.flags.stretch))) };
9473
- } else {
9657
+ } else if (c.kind === "transform") {
9474
9658
  const keyed = anim.constraints.transform.find((x) => x.name === c.name);
9475
9659
  if (keyed === void 0) continue;
9476
9660
  constraintTimelines[c.index] = { constraint: c.index, curve: compileCurve(keyed.keys, TRANSFORM_PROPERTIES.length), cursor: cursors++, flags: null };
9477
9661
  }
9478
9662
  }
9663
+ const sliders = constraints.map((c) => {
9664
+ if (c.kind !== "slider") return null;
9665
+ const keyed = anim.constraints.slider.find((t) => t.name === c.name);
9666
+ if (keyed === void 0) return null;
9667
+ return { time: keyed.time === null ? null : compileCurve(keyed.time, 1), mix: keyed.mix === null ? null : compileCurve(keyed.mix, 1), timeCursor: cursors++, mixCursor: cursors++ };
9668
+ });
9669
+ const paths = constraints.map((c) => {
9670
+ if (c.kind !== "path") return null;
9671
+ const keyed = anim.constraints.path.find((t) => t.name === c.name);
9672
+ if (keyed === void 0) return null;
9673
+ return {
9674
+ position: keyed.position === void 0 ? null : compileCurve(keyed.position, 1),
9675
+ spacing: keyed.spacing === void 0 ? null : compileCurve(keyed.spacing, 1),
9676
+ mix: keyed.mix === void 0 ? null : compileCurve(keyed.mix, 3),
9677
+ positionCursor: cursors++,
9678
+ spacingCursor: cursors++,
9679
+ mixCursor: cursors++
9680
+ };
9681
+ });
9682
+ const physics = (anim.constraints.physicsKeyed ?? []).map((t) => ({
9683
+ kind: t.kind,
9684
+ cursor: cursors++,
9685
+ curve: compileCurve(t.keys, t.kind === "reset" ? 0 : 1),
9686
+ targets: Int32Array.from(constraints.filter((c) => c.kind === "physics" && c.applied && (t.name === "*" ? t.kind === "reset" || c.setup.global[t.kind] : c.name === t.name)).map((c) => c.index))
9687
+ }));
9479
9688
  const deforms = deform.map((a) => {
9480
9689
  const g = doc.skins.find((s) => s.name === a.skin)?.attachments[a.slot]?.[a.attachment]?.geometry;
9481
9690
  if (g === void 0 || g.kind === "region" || g.kind === "linkedmesh") throw new RuntimeInputError(`deform "${a.skin}/${a.slot}/${a.attachment}" has no vertex geometry (readModel refuses it first)`);
@@ -9483,157 +9692,806 @@ function compileAnimation(anim, refusals, ignoreEvents, boneIndex2, slotIndex, s
9483
9692
  const arrays = keys.map((k) => heldArray(g.vertices, k));
9484
9693
  return { identity: `${a.skin}/${a.slot}/${a.attachment}`, cursor: cursors++, curve: compileDeformCurve(keys), count: arrays[0].length, held: Float64Array.from(arrays.flat()) };
9485
9694
  });
9486
- return { name, duration: tl.duration, deforms, bones, slots: slotTimelines, constraints: constraintTimelines, cursors, events: tl.events.length, eventKeys: Object.freeze(ignoreEvents ? [] : tl.events.map((key) => Object.freeze({ name: key.name, time: key.time, int: key.int, float: key.float, string: key.string, volume: key.volume ?? 0, balance: key.balance ?? 0 }))) };
9487
- }
9488
- const DEG = RUNTIME_DEG;
9489
- const TWO_PI = 2 * RUNTIME_PI;
9490
- const P_ROTATE = 0;
9491
- const P_X = 1;
9492
- const P_Y = 2;
9493
- const P_SCALE_X = 3;
9494
- const P_SCALE_Y = 4;
9495
- function wrap180(r) {
9496
- return r > 180 ? r - 360 : r <= -180 ? r + 360 : r;
9695
+ return { name, duration: tl.duration, physics, sliders, paths, deforms, bones, sliderBones, slots: slotTimelines, drawOrder, constraints: constraintTimelines, cursors, events: tl.events.length, eventKeys: Object.freeze(ignoreEvents ? [] : tl.events.map((key) => Object.freeze({ name: key.name, time: key.time, int: key.int, float: key.float, string: key.string, volume: key.volume ?? 0, balance: key.balance ?? 0 }))) };
9497
9696
  }
9498
- function volumeScaleY(scaleY, s) {
9499
- return scaleY / (s < 0.7 ? 0.25 + s * 0.642857 : s);
9500
- }
9501
- const clamp01 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
9502
- class RuntimePose {
9503
- constructor(model) {
9504
- this.trackTime = 0;
9505
- this.time = 0;
9506
- this.fired = [];
9507
- this.eventTime = -1;
9508
- this.current = null;
9509
- this.w = new Float64Array(WORLDS);
9510
- this.model = model;
9511
- const nb = model.boneNames.length;
9512
- const ns = model.slots.length;
9513
- const nc = model.constraints.length;
9514
- this.locals = new Float64Array(nb * LOCALS);
9515
- this.world = new Float64Array(nb * WORLDS);
9516
- this.light = new Float64Array(ns * 4);
9517
- this.shown = new Int32Array(ns);
9518
- this.vertices = new Float64Array(model.vertexCount);
9519
- this.deform = new Float64Array(model.deformCount);
9520
- this.deformed = new Uint8Array(model.attachments.length);
9521
- this.cursors = new Int32Array(Math.max(1, model.cursors)).fill(-1);
9522
- this.ikMix = new Float64Array(nc);
9523
- this.ikSoftness = new Float64Array(nc);
9524
- this.ikFlags = new Uint8Array(nc);
9525
- this.mixes = new Float64Array(nc * 6);
9526
- this.changed = new Int32Array(model.scratch);
9527
- this.inWorld = new Int32Array(model.scratch);
9528
- this.changedMark = new Uint8Array(nb);
9529
- this.inWorldMark = new Uint8Array(nb);
9530
- this.below = new Uint8Array(nb);
9531
- this.visit = new Uint8Array(nb);
9532
- }
9533
- /** Borrowed immutable event records, in delivery order, until the next apply/step. */
9534
- get events() {
9535
- return this.fired;
9536
- }
9537
- /** The animation this pose plays (a covered one: `model.animation` throws a `RuntimeRefusal` for a refused one), from track time 0. */
9538
- setAnimation(name) {
9539
- this.current = this.model.animation(name);
9540
- this.cursors.fill(-1);
9541
- this.trackTime = 0;
9542
- this.time = 0;
9543
- this.eventTime = -1;
9544
- this.fired.length = 0;
9545
- }
9546
- /**
9547
- * Restore one animation's two clocks without delivering keys already passed.
9548
- * A skin/view replacement uses the old pose's trackTime and wrapped time.
9549
- * Unlike setAnimation + apply, restoration never replays the initial interval.
9550
- */
9551
- restore(name, trackTime, animationTime) {
9552
- if (!Number.isFinite(trackTime)) throw new RuntimeInputError(`restore: track time ${trackTime} is not finite`);
9553
- if (!Number.isFinite(animationTime)) throw new RuntimeInputError(`restore: animation time ${animationTime} is not finite`);
9554
- this.setAnimation(name);
9555
- this.trackTime = trackTime;
9556
- if (!this.model.restoreEventsFromStart) this.eventTime = animationTime;
9557
- this.apply(animationTime);
9558
- }
9559
- /** The setup pose: every constraint applied with its own values, every slot as it is set up. */
9560
- setupPose() {
9561
- this.current = null;
9562
- this.time = 0;
9563
- this.eventTime = -1;
9564
- this.fired.length = 0;
9565
- this.locals.set(this.model.setup);
9566
- this.hierarchy();
9567
- this.constraintValues(null, 0);
9568
- this.solveConstraints();
9569
- this.slotValues(null, 0);
9570
- this.deformValues(null, 0);
9571
- }
9572
- /** The animation posed at its time `t`, from the setup pose (the header). */
9573
- apply(t) {
9574
- const anim = this.current;
9575
- if (anim === null) throw new RuntimeInputError("apply: no animation is set (setAnimation first)");
9576
- if (!Number.isFinite(t)) throw new RuntimeInputError(`apply: the time ${t} is not finite`);
9577
- this.collectEvents(anim, t);
9578
- this.time = t;
9579
- this.boneTimelines(anim, t);
9580
- this.hierarchy();
9581
- this.constraintValues(anim, t);
9582
- this.solveConstraints();
9583
- this.slotValues(anim, t);
9584
- this.deformValues(anim, t);
9585
- }
9586
- /** One step of a looping track: the track time moved by `dt`, the animation posed at it wrapped by the duration (`loopedTime` in src/core/raw.ts). */
9587
- step(dt) {
9588
- const anim = this.current;
9589
- if (anim === null) throw new RuntimeInputError("step: no animation is set (setAnimation first)");
9590
- this.trackTime += dt;
9591
- this.apply(anim.duration === 0 ? 0 : this.trackTime % anim.duration);
9697
+ class RuntimeClipBuffer {
9698
+ constructor(triangles, polygonVertices) {
9699
+ this.vertexCount = 0;
9700
+ this.triangleCount = 0;
9701
+ this.clipped = false;
9702
+ const points = triangles * Math.max(3, polygonVertices * (polygonVertices + 3));
9703
+ if (!Number.isSafeInteger(points) || points < 0) throw new Error("runtime clip: output capacity is not a safe non-negative integer");
9704
+ this.vertices = new Float32Array(points * 2);
9705
+ this.uvs = new Float32Array(points * 2);
9706
+ this.triangles = new Int32Array(points * 3);
9707
+ this.sources = new Int32Array(points);
9708
+ }
9709
+ }
9710
+ class RuntimeClipper {
9711
+ constructor(maxPolygonVertices) {
9712
+ this.maxPolygonVertices = maxPolygonVertices;
9713
+ this.nativeRows = false;
9714
+ this.tri = new Float64Array(6);
9715
+ this.uv = new Float64Array(6);
9716
+ this.regionsCount = 0;
9717
+ this.inverse = false;
9718
+ this.cut = false;
9719
+ this.n = 0;
9720
+ const n = Math.max(3, maxPolygonVertices);
9721
+ this.points = new Float64Array(n * 2);
9722
+ this.ring = new Int32Array(n);
9723
+ this.work = new Int32Array(n);
9724
+ this.sorted = new Int32Array(n);
9725
+ this.hull = new Int32Array(n * 2);
9726
+ this.pieces = new Int32Array(n * n);
9727
+ this.sizes = new Int32Array(n);
9728
+ this.merged = new Int32Array(n * 2);
9729
+ this.regions = new Float64Array(n * n * 2);
9730
+ this.regionSizes = new Int32Array(n);
9731
+ this.a = new Float64Array((n * 2 + 16) * 2);
9732
+ this.b = new Float64Array(this.a.length);
9733
+ this.outside = new Float64Array(this.a.length);
9734
+ }
9735
+ cross(a, b, c) {
9736
+ const p = this.points;
9737
+ return (p[b * 2] - p[a * 2]) * (p[c * 2 + 1] - p[a * 2 + 1]) - (p[b * 2 + 1] - p[a * 2 + 1]) * (p[c * 2] - p[a * 2]);
9738
+ }
9739
+ area(indices, n) {
9740
+ const p = this.points;
9741
+ let area = 0;
9742
+ for (let i = 0; i < n; i++) {
9743
+ const a = indices[i] * 2, b = indices[(i + 1) % n] * 2;
9744
+ area += p[a] * p[b + 1] - p[b] * p[a + 1];
9745
+ }
9746
+ return area;
9747
+ }
9748
+ convex(indices, n, start = 0) {
9749
+ let sign = 0;
9750
+ for (let i = 0; i < n; i++) {
9751
+ const p = this.points, a = indices[start + i] * 2, b = indices[start + (i + 1) % n] * 2, c = indices[start + (i + 2) % n] * 2;
9752
+ const cross = (p[b] - p[a]) * (p[c + 1] - p[b + 1]) - (p[b + 1] - p[a + 1]) * (p[c] - p[b]);
9753
+ if (cross === 0) return false;
9754
+ const next = Math.sign(cross);
9755
+ if (sign === 0) sign = next;
9756
+ else if (sign !== next) return false;
9757
+ }
9758
+ return true;
9592
9759
  }
9593
- /** The oracle interval (last,t], tail then head on a wrap; never copies a payload. */
9594
- collectEvents(anim, t) {
9595
- const last = this.eventTime;
9596
- const wrapped = t < last;
9597
- const keys = anim.eventKeys;
9598
- let count = 0;
9599
- if (wrapped) {
9600
- for (let i = 0; i < keys.length; i++) if (keys[i].time > last) this.fired[count++] = keys[i];
9760
+ on(a, b, r) {
9761
+ const p = this.points;
9762
+ return Math.min(p[a * 2], p[b * 2]) <= p[r * 2] && p[r * 2] <= Math.max(p[a * 2], p[b * 2]) && Math.min(p[a * 2 + 1], p[b * 2 + 1]) <= p[r * 2 + 1] && p[r * 2 + 1] <= Math.max(p[a * 2 + 1], p[b * 2 + 1]);
9763
+ }
9764
+ meet(a, b, c, d) {
9765
+ const d1 = this.cross(c, d, a), d2 = this.cross(c, d, b), d3 = this.cross(a, b, c), d4 = this.cross(a, b, d);
9766
+ if ((d1 > 0 && d2 < 0 || d1 < 0 && d2 > 0) && (d3 > 0 && d4 < 0 || d3 < 0 && d4 > 0)) return true;
9767
+ return d1 === 0 && this.on(c, d, a) || d2 === 0 && this.on(c, d, b) || d3 === 0 && this.on(a, b, c) || d4 === 0 && this.on(a, b, d);
9768
+ }
9769
+ region(indices, n, r, start = 0) {
9770
+ const p = this.points;
9771
+ let area = 0;
9772
+ for (let i = 0; i < n; i++) {
9773
+ const a = indices[start + i] * 2, b = indices[start + (i + 1) % n] * 2;
9774
+ area += p[a] * p[b + 1] - p[b] * p[a + 1];
9775
+ }
9776
+ const base = r * this.maxPolygonVertices * 2;
9777
+ for (let i = 0; i < n; i++) {
9778
+ const j = indices[start + (area > 0 ? n - 1 - i : i)] * 2;
9779
+ this.regions[base + i * 2] = p[j];
9780
+ this.regions[base + i * 2 + 1] = p[j + 1];
9781
+ }
9782
+ this.regionSizes[r] = n;
9783
+ }
9784
+ makeHull(count) {
9785
+ const idx = this.sorted, p = this.points, h = this.hull;
9786
+ for (let i = 0; i < count; i++) {
9787
+ const v = this.ring[i];
9788
+ let j = i;
9789
+ while (j > 0) {
9790
+ const u = idx[j - 1];
9791
+ if (p[u * 2] < p[v * 2] || p[u * 2] === p[v * 2] && p[u * 2 + 1] <= p[v * 2 + 1]) break;
9792
+ idx[j] = u;
9793
+ j--;
9794
+ }
9795
+ idx[j] = v;
9796
+ }
9797
+ let used = 0;
9798
+ for (let i = 0; i < count; i++) {
9799
+ const v = idx[i];
9800
+ while (used >= 2 && this.cross(h[used - 2], h[used - 1], v) <= 0) used--;
9801
+ h[used++] = v;
9802
+ }
9803
+ used--;
9804
+ const lower = used;
9805
+ for (let i = count - 1; i >= 0; i--) {
9806
+ const v = idx[i];
9807
+ while (used >= lower + 2 && this.cross(h[used - 2], h[used - 1], v) <= 0) used--;
9808
+ h[used++] = v;
9809
+ }
9810
+ return used - 1;
9811
+ }
9812
+ plan(inverse, convex) {
9813
+ this.nativeRows = false;
9814
+ const n = this.n, p = this.points, ring = this.ring;
9815
+ let repeats = 0, distinct = 0;
9816
+ for (let i = 0; i < n; i++) {
9817
+ ring[i] = i;
9818
+ for (let j2 = i + 1; j2 < n; j2++) if (p[i * 2] === p[j2 * 2] && p[i * 2 + 1] === p[j2 * 2 + 1]) repeats++;
9819
+ const j = (i + 1) % n;
9820
+ if (p[i * 2] !== p[j * 2] || p[i * 2 + 1] !== p[j * 2 + 1]) this.work[distinct++] = i;
9821
+ }
9822
+ if (repeats > 0) {
9823
+ if (!inverse && distinct === n - repeats && distinct >= 3 && this.convex(this.work, distinct)) {
9824
+ this.regionsCount = 1;
9825
+ this.inverse = false;
9826
+ this.region(ring, n, 0);
9827
+ return false;
9828
+ }
9829
+ throw new Error("runtime clip: polygon has repeated vertices unsupported by clipShapeOf");
9830
+ }
9831
+ for (let i = 0; i < n; i++) {
9832
+ const j = (i + 1) % n, k = (i + 2) % n;
9833
+ if (this.cross(i, j, k) === 0 && (p[k * 2] - p[j * 2]) * (p[i * 2] - p[j * 2]) + (p[k * 2 + 1] - p[j * 2 + 1]) * (p[i * 2 + 1] - p[j * 2 + 1]) > 0) throw new Error("runtime clip: neighbouring polygon edges fold back");
9834
+ for (let q = i + 2; q < n; q++) if (!(i === 0 && q === n - 1) && this.meet(i, j, q, (q + 1) % n)) throw new Error("runtime clip: polygon edges cross or touch");
9835
+ }
9836
+ const originalConvex = this.convex(ring, n);
9837
+ this.nativeRows = !inverse && originalConvex;
9838
+ let count = n;
9839
+ for (let changed = true; changed && count > 3; ) {
9840
+ changed = false;
9841
+ for (let i = 0; i < count; i++) if (this.cross(ring[(i + count - 1) % count], ring[i], ring[(i + 1) % count]) === 0) {
9842
+ ring.copyWithin(i, i + 1, count);
9843
+ count--;
9844
+ changed = true;
9845
+ break;
9846
+ }
9601
9847
  }
9602
- for (let i = 0; i < keys.length; i++) {
9603
- const key = keys[i];
9604
- if (key.time <= t && (wrapped ? !this.model.eventWrapOffByOne || key.time > 0 : key.time > last)) this.fired[count++] = key;
9848
+ if (count === 3 && this.cross(ring[0], ring[1], ring[2]) === 0) throw new Error("runtime clip: every polygon vertex lies on one line");
9849
+ this.inverse = inverse;
9850
+ if (inverse || !originalConvex && convex) {
9851
+ this.regionsCount = 1;
9852
+ this.region(this.hull, this.makeHull(count), 0);
9853
+ return false;
9605
9854
  }
9606
- this.fired.length = count;
9607
- this.eventTime = t;
9608
- }
9609
- // ---------------------------------------------------------------------------
9610
- // the bones
9611
- // ---------------------------------------------------------------------------
9612
- /** The setup locals with the bone timelines' values at `t` (`posedBones`): added, or multiplied for a scale; a channel before its timeline's first key at setup plus 0, or times 1. */
9613
- boneTimelines(anim, t) {
9614
- const L = this.locals;
9615
- const S = this.model.setup;
9616
- L.set(S);
9617
- const timelines = anim.bones;
9618
- for (let k = 0; k < timelines.length; k++) {
9619
- const tl = timelines[k];
9620
- const i = keyAt(tl.curve, this.cursors, tl.cursor, t);
9621
- const o = tl.bone * LOCALS;
9622
- const fields = tl.fields;
9623
- for (let c = 0; c < fields.length; c++) {
9624
- const at = o + fields[c];
9625
- if (tl.multiply[c] === 1) L[at] = S[at] * (i < 0 ? 1 : channelValue(tl.curve, i, c, t));
9626
- else L[at] = S[at] + (i < 0 ? 0 : channelValue(tl.curve, i, c, t));
9855
+ if (originalConvex) {
9856
+ for (let i = 0; i < n; i++) ring[i] = i;
9857
+ this.regionsCount = 1;
9858
+ this.region(ring, n, 0);
9859
+ return false;
9860
+ }
9861
+ if (this.area(ring, count) < 0) for (let i = 0; i < count / 2; i++) {
9862
+ const j = count - 1 - i, v = ring[i];
9863
+ ring[i] = ring[j];
9864
+ ring[j] = v;
9865
+ }
9866
+ this.work.set(ring);
9867
+ let left = count, pieces = 0;
9868
+ while (left > 3) {
9869
+ let found = false;
9870
+ for (let k = 0; k < left; k++) {
9871
+ const a = this.work[(k + left - 1) % left], b = this.work[k], c = this.work[(k + 1) % left];
9872
+ if (this.cross(a, b, c) <= 0) continue;
9873
+ let inside = false;
9874
+ for (let j = 0; j < left; j++) {
9875
+ const v = this.work[j];
9876
+ if (v !== a && v !== b && v !== c && this.cross(a, b, v) >= 0 && this.cross(b, c, v) >= 0 && this.cross(c, a, v) >= 0) {
9877
+ inside = true;
9878
+ break;
9879
+ }
9880
+ }
9881
+ if (inside) continue;
9882
+ const base = pieces * this.maxPolygonVertices;
9883
+ this.pieces[base] = a;
9884
+ this.pieces[base + 1] = b;
9885
+ this.pieces[base + 2] = c;
9886
+ this.sizes[pieces++] = 3;
9887
+ this.work.copyWithin(k, k + 1, left);
9888
+ left--;
9889
+ found = true;
9890
+ break;
9891
+ }
9892
+ if (!found) throw new Error("runtime clip: no ear left in a polygon held simple");
9893
+ }
9894
+ for (let i = 0; i < 3; i++) this.pieces[pieces * this.maxPolygonVertices + i] = this.work[i];
9895
+ this.sizes[pieces++] = 3;
9896
+ for (let changed = true; changed; ) {
9897
+ changed = false;
9898
+ search: for (let a = 0; a < pieces; a++) for (let b = a + 1; b < pieces; b++) {
9899
+ const an = this.sizes[a], bn = this.sizes[b], ab = a * this.maxPolygonVertices, bb = b * this.maxPolygonVertices;
9900
+ for (let i = 0; i < an; i++) {
9901
+ const u = this.pieces[ab + i], v = this.pieces[ab + (i + 1) % an];
9902
+ let j = -1;
9903
+ for (let k = 0; k < bn; k++) if (this.pieces[bb + k] === v) {
9904
+ j = k;
9905
+ break;
9906
+ }
9907
+ if (j < 0 || this.pieces[bb + (j + 1) % bn] !== u) continue;
9908
+ let size = 0;
9909
+ for (let k = 0; k <= i; k++) this.merged[size++] = this.pieces[ab + k];
9910
+ for (let k = (j + 2) % bn; k !== j; k = (k + 1) % bn) this.merged[size++] = this.pieces[bb + k];
9911
+ for (let k = i + 1; k < an; k++) this.merged[size++] = this.pieces[ab + k];
9912
+ let strict = true;
9913
+ for (let k = 0; k < size; k++) if (this.cross(this.merged[(k + size - 1) % size], this.merged[k], this.merged[(k + 1) % size]) <= 0) {
9914
+ strict = false;
9915
+ break;
9916
+ }
9917
+ if (!strict) continue;
9918
+ for (let k = 0; k < size; k++) this.pieces[ab + k] = this.merged[k];
9919
+ this.sizes[a] = size;
9920
+ this.pieces.copyWithin(bb, bb + this.maxPolygonVertices, pieces * this.maxPolygonVertices);
9921
+ this.sizes.copyWithin(b, b + 1, pieces);
9922
+ pieces--;
9923
+ changed = true;
9924
+ break search;
9925
+ }
9926
+ }
9927
+ }
9928
+ this.regionsCount = pieces;
9929
+ for (let i = 0; i < pieces; i++) this.region(this.pieces, this.sizes[i], i, i * this.maxPolygonVertices);
9930
+ return pieces > 1;
9931
+ }
9932
+ /** Plan again after float32 conversion exactly when clipThrough first finds pieces. */
9933
+ prepare(polygon, offset, count, inverse, convex) {
9934
+ this.n = count / 2;
9935
+ if (!Number.isInteger(this.n) || this.n < 3 || this.n > this.maxPolygonVertices) throw new Error("runtime clip: polygon vertex count exceeds its load-time capacity");
9936
+ for (let i = 0; i < count; i++) {
9937
+ const value = polygon[offset + i];
9938
+ if (!Number.isFinite(value)) throw new Error("runtime clip: polygon contains a non-finite coordinate");
9939
+ this.points[i] = value;
9940
+ }
9941
+ const replan = this.plan(inverse, convex), nativeRows = this.nativeRows;
9942
+ if (replan) {
9943
+ for (let i = 0; i < count; i++) this.points[i] = Math.fround(polygon[offset + i]);
9944
+ this.plan(inverse, convex);
9945
+ }
9946
+ this.nativeRows = nativeRows;
9947
+ }
9948
+ half(input, n, px, py, qx, qy, out) {
9949
+ let size = 0;
9950
+ this.cut = false;
9951
+ const ex = qx - px, ey = qy - py;
9952
+ for (let i = 0; i < n; i++) {
9953
+ const j = (i + 1) % n, sx = input[i * 2], sy = input[i * 2 + 1], tx = input[j * 2], ty = input[j * 2 + 1];
9954
+ const si = ex * (sy - py) - ey * (sx - px) < 0, ei = ex * (ty - py) - ey * (tx - px) < 0;
9955
+ if (!si || !ei) this.cut = true;
9956
+ if (si !== ei) {
9957
+ const dx = tx - sx, dy = ty - sy, t = (ex * (sy - py) - ey * (sx - px)) / (ey * dx - ex * dy);
9958
+ if (size * 2 + 2 > out.length) throw new Error("runtime clip: scratch polygon exceeds its load-time capacity");
9959
+ out[size * 2] = sx + t * dx;
9960
+ out[size * 2 + 1] = sy + t * dy;
9961
+ size++;
9962
+ }
9963
+ if (ei) {
9964
+ if (size * 2 + 2 > out.length) throw new Error("runtime clip: scratch polygon exceeds its load-time capacity");
9965
+ out[size * 2] = tx;
9966
+ out[size * 2 + 1] = ty;
9967
+ size++;
9968
+ }
9969
+ }
9970
+ return size;
9971
+ }
9972
+ emit(points, count, cut, source, out) {
9973
+ if (count < 3) return;
9974
+ if ((out.vertexCount + count) * 2 > out.vertices.length || out.triangleCount + count - 2 > out.sources.length) throw new Error("runtime clip: output exceeds its load-time capacity");
9975
+ const tri = this.tri, uv = this.uv, base = out.vertexCount;
9976
+ const d0 = tri[3] - tri[5], d1 = tri[4] - tri[2], d2 = tri[0] - tri[4], d4 = tri[5] - tri[1], d = 1 / (d0 * d2 + d1 * (tri[1] - tri[5]));
9977
+ for (let i = 0; i < count; i++) {
9978
+ const x = cut ? points[i * 2] : tri[i * 2], y = cut ? points[i * 2 + 1] : tri[i * 2 + 1], at = out.vertexCount++ * 2;
9979
+ out.vertices[at] = x;
9980
+ out.vertices[at + 1] = y;
9981
+ if (!cut) {
9982
+ out.uvs[at] = uv[i * 2];
9983
+ out.uvs[at + 1] = uv[i * 2 + 1];
9984
+ } else {
9985
+ const a = (d0 * (x - tri[4]) + d1 * (y - tri[5])) * d, b = (d4 * (x - tri[4]) + d2 * (y - tri[5])) * d, c = 1 - a - b;
9986
+ out.uvs[at] = uv[0] * a + uv[2] * b + uv[4] * c;
9987
+ out.uvs[at + 1] = uv[1] * a + uv[3] * b + uv[5] * c;
9988
+ }
9989
+ }
9990
+ for (let i = base * 2; i < out.vertexCount * 2; i++) if (!Number.isFinite(out.vertices[i]) || !Number.isFinite(out.uvs[i])) throw new Error(`runtime clip: triangle ${source} produced a non-finite clipped coordinate or UV`);
9991
+ for (let i = 1; i + 1 < count; i++) {
9992
+ const at = out.triangleCount * 3;
9993
+ out.triangles[at] = base;
9994
+ out.triangles[at + 1] = base + i;
9995
+ out.triangles[at + 2] = base + i + 1;
9996
+ out.sources[out.triangleCount++] = source;
9997
+ }
9998
+ }
9999
+ clip(vertices, offset, triangles, uvs, out) {
10000
+ out.vertexCount = 0;
10001
+ out.triangleCount = 0;
10002
+ out.clipped = this.inverse;
10003
+ for (let k = 0; k < triangles.length; k += 3) {
10004
+ for (let j = 0; j < 3; j++) {
10005
+ const n = triangles[k + j] * 2;
10006
+ this.tri[j * 2] = vertices[offset + n];
10007
+ this.tri[j * 2 + 1] = vertices[offset + n + 1];
10008
+ this.uv[j * 2] = Math.fround(uvs[n]);
10009
+ this.uv[j * 2 + 1] = Math.fround(uvs[n + 1]);
10010
+ }
10011
+ if (!this.inverse) for (let r = 0; r < this.regionsCount; r++) {
10012
+ let input = this.a, output = this.b, count = 3, cut = false;
10013
+ for (let i = 0; i < 6; i++) input[i] = this.tri[i];
10014
+ const n = this.regionSizes[r], base = r * this.maxPolygonVertices * 2;
10015
+ for (let e = 0; e < n && count > 0; e++) {
10016
+ const p = base + e * 2, q = base + (e + 1) % n * 2;
10017
+ count = this.half(input, count, this.regions[p], this.regions[p + 1], this.regions[q], this.regions[q + 1], output);
10018
+ cut ||= this.cut;
10019
+ const swap = input;
10020
+ input = output;
10021
+ output = swap;
10022
+ }
10023
+ out.clipped ||= cut;
10024
+ this.emit(input, count, cut, k / 3, out);
10025
+ }
10026
+ else {
10027
+ let input = this.a, output = this.b, count = 3, cutSoFar = false;
10028
+ for (let i = 0; i < 6; i++) input[i] = this.tri[i];
10029
+ const n = this.regionSizes[0];
10030
+ for (let e = 0; e < n && count > 0; e++) {
10031
+ const p = e * 2, q = (e + 1) % n * 2, px = this.regions[p], py = this.regions[p + 1], qx = this.regions[q], qy = this.regions[q + 1];
10032
+ const outside = this.half(input, count, qx, qy, px, py, this.outside), cut = this.cut || cutSoFar;
10033
+ this.emit(this.outside, outside, cut, k / 3, out);
10034
+ count = this.half(input, count, px, py, qx, qy, output);
10035
+ cutSoFar ||= this.cut;
10036
+ const swap = input;
10037
+ input = output;
10038
+ output = swap;
10039
+ }
9627
10040
  }
9628
10041
  }
9629
10042
  }
9630
- /** Every bone's world transform from its locals, parents first (`worldTransforms`): an inactive bone as zeros. */
9631
- hierarchy() {
9632
- const nb = this.model.boneNames.length;
9633
- for (let i = 0; i < nb; i++) this.poseBone(i, true);
10043
+ }
10044
+ class RuntimeDrawClipper {
10045
+ constructor(model) {
10046
+ this.model = model;
10047
+ this.nativeRows = true;
10048
+ this.clipCount = 0;
10049
+ this.drawCount = 0;
10050
+ const triangles = [], uvs = [], maxTriangles = new Int32Array(model.slots.length);
10051
+ let maxPolygon = 3;
10052
+ for (const at of model.attachments) {
10053
+ const t = at.clipTopology?.triangles ?? null, uv = at.clipTopology?.uvs ?? null;
10054
+ if (at.clip !== void 0) maxPolygon = Math.max(maxPolygon, at.count / 2);
10055
+ triangles.push(t);
10056
+ uvs.push(uv);
10057
+ if (t !== null) maxTriangles[at.slot] = Math.max(maxTriangles[at.slot], t.length / 3);
10058
+ }
10059
+ this.triangles = triangles;
10060
+ this.uvs = uvs;
10061
+ this.clipper = new RuntimeClipper(maxPolygon);
10062
+ this.clipSlots = new Int32Array(model.slots.length);
10063
+ this.drawSlots = new Int32Array(model.slots.length);
10064
+ this.outputs = Array.from(maxTriangles, (n) => new RuntimeClipBuffer(n, maxPolygon));
10065
+ }
10066
+ /** pose.computeVertices() must precede this walk. Omission is a diagnostic plant only. */
10067
+ frame(pose, omit = false) {
10068
+ if (pose.model !== this.model) throw new Error("runtime clip draw: pose differs from the prepared model");
10069
+ this.clipCount = 0;
10070
+ this.drawCount = 0;
10071
+ this.nativeRows = true;
10072
+ let active = false, end = null;
10073
+ for (let i = 0; i < pose.drawOrder.length; i++) {
10074
+ const s = pose.drawOrder[i], slot = this.model.slots[s], shown = pose.shown[s], at = shown < 0 ? null : this.model.attachments[shown];
10075
+ if (at?.clip !== void 0) {
10076
+ this.clipSlots[this.clipCount++] = s;
10077
+ if (active && end === slot.name) active = false;
10078
+ if (!omit && !active && this.model.active[at.bone] === 1) {
10079
+ this.clipper.prepare(pose.vertices, at.offset, at.count, at.clip.inverse, at.clip.convex);
10080
+ this.nativeRows &&= this.clipper.nativeRows;
10081
+ active = true;
10082
+ end = at.clip.end;
10083
+ }
10084
+ continue;
10085
+ }
10086
+ if (active && at !== null) {
10087
+ const triangles = this.triangles[shown], uvs = this.uvs[shown];
10088
+ if (triangles !== null && uvs !== null) {
10089
+ this.clipper.clip(pose.vertices, at.offset, triangles, uvs, this.outputs[s]);
10090
+ this.drawSlots[this.drawCount++] = s;
10091
+ }
10092
+ }
10093
+ if (active && end === slot.name) active = false;
10094
+ }
9634
10095
  }
9635
- /**
9636
- * One bone's world transform from its locals under its parent's current
10096
+ }
10097
+ const PI2 = RUNTIME_PI * 2;
10098
+ const INV_PI2 = 1 / PI2;
10099
+ function freshPhysicsState() {
10100
+ return { pending: true, lastTime: 0, remaining: 0, ux: 0, uy: 0, cx: 0, cy: 0, tx: 0, ty: 0, xOffset: 0, xLag: 0, xVelocity: 0, yOffset: 0, yLag: 0, yVelocity: 0, rotateOffset: 0, rotateLag: 0, rotateVelocity: 0, scaleOffset: 0, scaleLag: 0, scaleVelocity: 0 };
10101
+ }
10102
+ function resetPhysicsState(s, time) {
10103
+ s.remaining = 0;
10104
+ s.lastTime = time;
10105
+ s.pending = true;
10106
+ s.xOffset = s.xLag = s.xVelocity = 0;
10107
+ s.yOffset = s.yLag = s.yVelocity = 0;
10108
+ s.rotateOffset = s.rotateLag = s.rotateVelocity = 0;
10109
+ s.scaleOffset = s.scaleLag = s.scaleVelocity = 0;
10110
+ }
10111
+ function stepRuntimePhysics(r, s, w, boneOffset, length, time, reset, env) {
10112
+ const mix = r.mix;
10113
+ if (mix === 0) return false;
10114
+ const x = r.x > 0, y = r.y > 0, rotateOrShearX = r.rotate > 0 || r.shearX > 0, scaleX = r.scaleX > 0;
10115
+ const l = length;
10116
+ if (reset) resetPhysicsState(s, time);
10117
+ const delta = Math.max(time - s.lastTime, 0);
10118
+ const shown = Math.max(0, 1 - s.remaining / r.step);
10119
+ s.remaining += delta;
10120
+ s.lastTime = time;
10121
+ const bx = w[boneOffset + 4];
10122
+ const by = w[boneOffset + 5];
10123
+ let z = 0;
10124
+ if (s.pending) {
10125
+ s.pending = false;
10126
+ s.ux = bx;
10127
+ s.uy = by;
10128
+ } else {
10129
+ let a = s.remaining;
10130
+ const i = r.inertia;
10131
+ const t = r.step;
10132
+ const f = r.referenceScale;
10133
+ let d = -1;
10134
+ const qx = r.limit * delta;
10135
+ const qy = qx;
10136
+ if (x || y) {
10137
+ if (x) {
10138
+ const u = (s.ux - bx) * i;
10139
+ s.xOffset += u > qx ? qx : u < -qx ? -qx : u;
10140
+ s.ux = bx;
10141
+ }
10142
+ if (y) {
10143
+ const u = (s.uy - by) * i;
10144
+ s.yOffset += u > qy ? qy : u < -qy ? -qy : u;
10145
+ s.uy = by;
10146
+ }
10147
+ if (a >= t) {
10148
+ d = Math.pow(r.damping, 60 * t);
10149
+ const m = r.massInverse * t;
10150
+ const e = r.strength;
10151
+ const wind = r.wind * f;
10152
+ const g = r.gravity * f;
10153
+ const ax = wind * env[0] + g * env[2];
10154
+ const ay = wind * env[1] + g * env[3];
10155
+ const xs = s.xOffset;
10156
+ const ys = s.yOffset;
10157
+ do {
10158
+ if (x) {
10159
+ s.xVelocity += (ax - s.xOffset * e) * m;
10160
+ s.xOffset += s.xVelocity * t;
10161
+ s.xVelocity *= d;
10162
+ }
10163
+ if (y) {
10164
+ s.yVelocity -= (ay + s.yOffset * e) * m;
10165
+ s.yOffset += s.yVelocity * t;
10166
+ s.yVelocity *= d;
10167
+ }
10168
+ a -= t;
10169
+ } while (a >= t);
10170
+ s.xLag = s.xOffset - xs;
10171
+ s.yLag = s.yOffset - ys;
10172
+ }
10173
+ z = Math.max(0, 1 - a / t);
10174
+ if (x) w[boneOffset + 4] += (s.xOffset - s.xLag * z) * mix * r.x;
10175
+ if (y) w[boneOffset + 5] += (s.yOffset - s.yLag * z) * mix * r.y;
10176
+ }
10177
+ if (rotateOrShearX || scaleX) {
10178
+ const ca = Math.atan2(w[boneOffset + 2], w[boneOffset + 0]);
10179
+ let c = 0;
10180
+ let sn = 0;
10181
+ let mr = 0;
10182
+ let dx = s.cx - w[boneOffset + 4];
10183
+ let dy = s.cy - w[boneOffset + 5];
10184
+ if (dx > qx) dx = qx;
10185
+ else if (dx < -qx) dx = -qx;
10186
+ if (dy > qy) dy = qy;
10187
+ else if (dy < -qy) dy = -qy;
10188
+ if (rotateOrShearX) {
10189
+ mr = (r.rotate + r.shearX) * mix;
10190
+ const lagged = s.rotateLag * shown;
10191
+ let rr = Math.atan2(dy + s.ty, dx + s.tx) - ca - (s.rotateOffset - lagged) * mr;
10192
+ s.rotateOffset += (rr - Math.ceil(rr * INV_PI2 - 0.5) * PI2) * i;
10193
+ rr = (s.rotateOffset - lagged) * mr + ca;
10194
+ c = Math.cos(rr);
10195
+ sn = Math.sin(rr);
10196
+ if (scaleX) {
10197
+ rr = l * Math.sqrt(w[boneOffset + 0] * w[boneOffset + 0] + w[boneOffset + 2] * w[boneOffset + 2]);
10198
+ if (rr > 0) s.scaleOffset += (dx * c + dy * sn) * i / rr;
10199
+ }
10200
+ } else {
10201
+ c = Math.cos(ca);
10202
+ sn = Math.sin(ca);
10203
+ const rr = l * Math.sqrt(w[boneOffset + 0] * w[boneOffset + 0] + w[boneOffset + 2] * w[boneOffset + 2]) - s.scaleLag * shown;
10204
+ if (rr > 0) s.scaleOffset += (dx * c + dy * sn) * i / rr;
10205
+ }
10206
+ a = s.remaining;
10207
+ if (a >= t) {
10208
+ if (d === -1) d = Math.pow(r.damping, 60 * t);
10209
+ const m = r.massInverse * t;
10210
+ const e = r.strength;
10211
+ const ax = r.wind * env[0] + r.gravity * env[2];
10212
+ const ay = r.wind * env[1] + r.gravity * env[3];
10213
+ const h = l / f;
10214
+ const rs = s.rotateOffset;
10215
+ const ss = s.scaleOffset;
10216
+ for (; ; ) {
10217
+ a -= t;
10218
+ if (scaleX) {
10219
+ s.scaleVelocity += (ax * c - ay * sn - s.scaleOffset * e) * m;
10220
+ s.scaleOffset += s.scaleVelocity * t;
10221
+ s.scaleVelocity *= d;
10222
+ }
10223
+ if (rotateOrShearX) {
10224
+ s.rotateVelocity -= ((ax * sn + ay * c) * h + s.rotateOffset * e) * m;
10225
+ s.rotateOffset += s.rotateVelocity * t;
10226
+ s.rotateVelocity *= d;
10227
+ if (a < t) break;
10228
+ const rr = s.rotateOffset * mr + ca;
10229
+ c = Math.cos(rr);
10230
+ sn = Math.sin(rr);
10231
+ } else if (a < t) break;
10232
+ }
10233
+ s.rotateLag = s.rotateOffset - rs;
10234
+ s.scaleLag = s.scaleOffset - ss;
10235
+ }
10236
+ z = Math.max(0, 1 - a / t);
10237
+ }
10238
+ s.remaining = a;
10239
+ }
10240
+ s.cx = w[boneOffset + 4];
10241
+ s.cy = w[boneOffset + 5];
10242
+ if (rotateOrShearX) {
10243
+ let angle = (s.rotateOffset - s.rotateLag * z) * mix;
10244
+ let sn = 0;
10245
+ let c = 0;
10246
+ let a = 0;
10247
+ if (r.shearX > 0) {
10248
+ let rr = 0;
10249
+ if (r.rotate > 0) {
10250
+ rr = angle * r.rotate;
10251
+ sn = Math.sin(rr);
10252
+ c = Math.cos(rr);
10253
+ a = w[boneOffset + 1];
10254
+ w[boneOffset + 1] = c * a - sn * w[boneOffset + 3];
10255
+ w[boneOffset + 3] = sn * a + c * w[boneOffset + 3];
10256
+ }
10257
+ rr += angle * r.shearX;
10258
+ sn = Math.sin(rr);
10259
+ c = Math.cos(rr);
10260
+ a = w[boneOffset + 0];
10261
+ w[boneOffset + 0] = c * a - sn * w[boneOffset + 2];
10262
+ w[boneOffset + 2] = sn * a + c * w[boneOffset + 2];
10263
+ } else {
10264
+ angle *= r.rotate;
10265
+ sn = Math.sin(angle);
10266
+ c = Math.cos(angle);
10267
+ a = w[boneOffset + 0];
10268
+ w[boneOffset + 0] = c * a - sn * w[boneOffset + 2];
10269
+ w[boneOffset + 2] = sn * a + c * w[boneOffset + 2];
10270
+ a = w[boneOffset + 1];
10271
+ w[boneOffset + 1] = c * a - sn * w[boneOffset + 3];
10272
+ w[boneOffset + 3] = sn * a + c * w[boneOffset + 3];
10273
+ }
10274
+ }
10275
+ if (scaleX) {
10276
+ const k = 1 + (s.scaleOffset - s.scaleLag * z) * mix * r.scaleX;
10277
+ w[boneOffset + 0] *= k;
10278
+ w[boneOffset + 2] *= k;
10279
+ if (r.scaleYMode === "Uniform") {
10280
+ w[boneOffset + 1] *= k;
10281
+ w[boneOffset + 3] *= k;
10282
+ } else if (r.scaleYMode === "Volume") {
10283
+ const magnitude = Math.abs(k);
10284
+ const transverse = magnitude >= 0.7 ? 1 / magnitude : 4 - 3.67347 * magnitude;
10285
+ w[boneOffset + 1] *= transverse;
10286
+ w[boneOffset + 3] *= transverse;
10287
+ }
10288
+ }
10289
+ s.tx = l * w[boneOffset + 0];
10290
+ s.ty = l * w[boneOffset + 2];
10291
+ return true;
10292
+ }
10293
+ const DEG = RUNTIME_DEG;
10294
+ const TWO_PI = 2 * RUNTIME_PI;
10295
+ const PATH_EPSILON = 1e-5;
10296
+ const P_ROTATE = 0;
10297
+ const P_X = 1;
10298
+ const P_Y = 2;
10299
+ const P_SCALE_X = 3;
10300
+ const P_SCALE_Y = 4;
10301
+ const PROPERTY_LOCAL = new Int32Array([L_ROTATION, L_X, L_Y, L_SCALE_X, L_SCALE_Y, L_SHEAR_Y]);
10302
+ function wrap180(r) {
10303
+ return r > 180 ? r - 360 : r <= -180 ? r + 360 : r;
10304
+ }
10305
+ function volumeScaleY(scaleY, s) {
10306
+ return scaleY / (s < 0.7 ? 0.25 + s * 0.642857 : s);
10307
+ }
10308
+ const clamp01 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
10309
+ class RuntimePose {
10310
+ constructor(model, options = {}) {
10311
+ this.trackTime = 0;
10312
+ this.time = 0;
10313
+ this.fired = [];
10314
+ this.eventTime = -1;
10315
+ this.current = null;
10316
+ this.pathSegmentEnds = new Float64Array(10);
10317
+ this.w = new Float64Array(WORLDS);
10318
+ this.physicsTime = 0;
10319
+ this.physicsLast = -1;
10320
+ this.physicsPhase = "none";
10321
+ this.physicsReady = false;
10322
+ this.environment = new Float64Array([1, 0, 0, 1]);
10323
+ this.model = model;
10324
+ this.clipping = model.attachments.some((at) => at.clip !== void 0) ? new RuntimeDrawClipper(model) : null;
10325
+ this.clock = options.clock;
10326
+ this.hasPhysics = model.constraints.some((c) => c.kind === "physics");
10327
+ this.physicsStates = model.constraints.map(() => freshPhysicsState());
10328
+ this.physicsRecords = model.constraints.map((c) => c.kind === "physics" ? { ...c.setup } : null);
10329
+ this.sliderTime = new Float64Array(model.constraints.length);
10330
+ this.sliderMix = new Float64Array(model.constraints.length);
10331
+ this.sliderAt = new Float64Array(model.constraints.length);
10332
+ const nb = model.boneNames.length;
10333
+ const ns = model.slots.length;
10334
+ const nc = model.constraints.length;
10335
+ this.physicsScope = new Uint8Array(nc);
10336
+ this.locals = new Float64Array(nb * LOCALS);
10337
+ this.world = new Float64Array(nb * WORLDS);
10338
+ this.light = new Float64Array(ns * 4);
10339
+ this.dark = new Float64Array(ns * 3);
10340
+ this.shown = new Int32Array(ns);
10341
+ this.drawOrder = new Int32Array(ns);
10342
+ for (let s = 0; s < ns; s++) this.drawOrder[s] = s;
10343
+ this.vertices = new Float64Array(model.vertexCount);
10344
+ this.deform = new Float64Array(model.deformCount);
10345
+ this.deformed = new Uint8Array(model.attachments.length);
10346
+ this.cursors = new Int32Array(Math.max(1, model.cursors)).fill(-1);
10347
+ this.ikMix = new Float64Array(nc);
10348
+ this.ikSoftness = new Float64Array(nc);
10349
+ this.ikFlags = new Uint8Array(nc);
10350
+ this.mixes = new Float64Array(nc * 6);
10351
+ this.pathValues = new Float64Array(nc * 5);
10352
+ this.pathSlotSign = new Int8Array(nc);
10353
+ const ms = model.maxPathSpaces;
10354
+ const mv = model.maxPathVertices;
10355
+ this.pathSpaces = new Float64Array(ms);
10356
+ this.pathLengths = new Float64Array(ms);
10357
+ this.pathPositions = new Float64Array(ms > 0 ? ms * 3 + 2 : 0);
10358
+ this.pathWorldPts = new Float64Array(mv);
10359
+ this.pathChain = new Float64Array(mv > 0 ? mv + 4 : 0);
10360
+ this.pathCurveEnds = new Float64Array(mv > 0 ? Math.ceil(mv / 6) + 1 : 0);
10361
+ this.changed = new Int32Array(model.scratch);
10362
+ this.inWorld = new Int32Array(model.scratch);
10363
+ this.changedMark = new Uint8Array(nb);
10364
+ this.inWorldMark = new Uint8Array(nb);
10365
+ this.below = new Uint8Array(nb);
10366
+ this.visit = new Uint8Array(nb);
10367
+ }
10368
+ /** Borrowed immutable event records, in delivery order, until the next apply/step. */
10369
+ get events() {
10370
+ return this.fired;
10371
+ }
10372
+ /** The animation this pose plays (a covered one: `model.animation` throws a `RuntimeRefusal` for a refused one), from track time 0. */
10373
+ setAnimation(name) {
10374
+ this.current = this.model.animation(name);
10375
+ this.physicsTime = 0;
10376
+ this.physicsLast = -1;
10377
+ this.physicsReady = false;
10378
+ for (let k = 0; k < this.physicsStates.length; k++) resetPhysicsState(this.physicsStates[k], 0);
10379
+ this.cursors.fill(-1);
10380
+ this.trackTime = 0;
10381
+ this.time = 0;
10382
+ this.eventTime = -1;
10383
+ this.fired.length = 0;
10384
+ }
10385
+ /**
10386
+ * Restore one animation's two clocks without delivering keys already passed.
10387
+ * A skin/view replacement uses the old pose's trackTime and wrapped time.
10388
+ * Unlike setAnimation + apply, restoration never replays the initial interval.
10389
+ */
10390
+ restore(name, trackTime, animationTime) {
10391
+ if (!Number.isFinite(trackTime)) throw new RuntimeInputError(`restore: track time ${trackTime} is not finite`);
10392
+ if (!Number.isFinite(animationTime)) throw new RuntimeInputError(`restore: animation time ${animationTime} is not finite`);
10393
+ this.setAnimation(name);
10394
+ this.trackTime = trackTime;
10395
+ if (!this.model.restoreEventsFromStart) this.eventTime = animationTime;
10396
+ this.apply(animationTime);
10397
+ }
10398
+ /** The setup pose: every constraint applied with its own values, every slot as it is set up. */
10399
+ setupPose(physics = "none") {
10400
+ this.physicsPhase = physics;
10401
+ this.current = null;
10402
+ this.time = 0;
10403
+ this.eventTime = -1;
10404
+ this.fired.length = 0;
10405
+ this.physicsValues(null, 0, false);
10406
+ this.locals.set(this.model.setup);
10407
+ this.hierarchy();
10408
+ this.slotValues(null, 0);
10409
+ this.deformValues(null, 0);
10410
+ this.constraintValues(null, 0);
10411
+ this.solveConstraints();
10412
+ this.sliderAttachments();
10413
+ }
10414
+ /** The animation posed at its time `t`, from the setup pose (the header). */
10415
+ apply(t, physics = "none", delta = 0) {
10416
+ const anim = this.current;
10417
+ if (anim === null) throw new RuntimeInputError("apply: no animation is set (setAnimation first)");
10418
+ if (!Number.isFinite(t)) throw new RuntimeInputError(`apply: the time ${t} is not finite`);
10419
+ if (!Number.isFinite(delta) || delta < 0) throw new RuntimeInputError(`apply: physics delta ${delta} is not finite and at or above 0`);
10420
+ this.physicsPhase = physics;
10421
+ this.physicsValues(anim, t, physics !== "none");
10422
+ if (physics !== "none") {
10423
+ this.physicsTime += delta;
10424
+ this.physicsReady = true;
10425
+ }
10426
+ this.collectEvents(anim, t);
10427
+ this.time = t;
10428
+ this.boneTimelines(anim, t);
10429
+ this.hierarchy();
10430
+ this.slotValues(anim, t);
10431
+ this.deformValues(anim, t);
10432
+ this.constraintValues(anim, t);
10433
+ this.solveConstraints();
10434
+ this.sliderAttachments();
10435
+ }
10436
+ /** One step of a looping track: the track time moved by `dt`, the animation posed at it wrapped by the duration (`loopedTime` in src/core/raw.ts). */
10437
+ step(dt) {
10438
+ const anim = this.current;
10439
+ if (anim === null) throw new RuntimeInputError("step: no animation is set (setAnimation first)");
10440
+ if (!Number.isFinite(dt) || dt < 0) throw new RuntimeInputError(`step: delta ${dt} is not finite and at or above 0`);
10441
+ if (!this.physicsReady && this.hasPhysics) {
10442
+ const lastEventTime = this.eventTime;
10443
+ this.apply(this.time, "reset", 0);
10444
+ this.eventTime = lastEventTime;
10445
+ }
10446
+ const moved = this.clock === void 0 ? dt : this.clock(dt);
10447
+ if (!Number.isFinite(moved) || moved < 0) throw new RuntimeInputError(`step: physics clock moved by ${moved}, not finite and at or above 0`);
10448
+ this.trackTime += dt;
10449
+ this.apply(anim.duration === 0 ? 0 : this.trackTime % anim.duration, "update", moved);
10450
+ }
10451
+ /** The oracle interval (last,t], tail then head on a wrap; never copies a payload. */
10452
+ collectEvents(anim, t) {
10453
+ const last = this.eventTime;
10454
+ const wrapped = t < last;
10455
+ const keys = anim.eventKeys;
10456
+ let count = 0;
10457
+ if (wrapped) {
10458
+ for (let i = 0; i < keys.length; i++) if (keys[i].time > last) this.fired[count++] = keys[i];
10459
+ }
10460
+ for (let i = 0; i < keys.length; i++) {
10461
+ const key = keys[i];
10462
+ if (key.time <= t && (wrapped ? !this.model.eventWrapOffByOne || key.time > 0 : key.time > last)) this.fired[count++] = key;
10463
+ }
10464
+ this.fired.length = count;
10465
+ this.eventTime = t;
10466
+ }
10467
+ // ---------------------------------------------------------------------------
10468
+ // the bones
10469
+ // ---------------------------------------------------------------------------
10470
+ /** The setup locals with the bone timelines' values at `t` (`posedBones`): added, or multiplied for a scale; a channel before its timeline's first key at setup plus 0, or times 1. */
10471
+ boneTimelines(anim, t) {
10472
+ const L = this.locals;
10473
+ const S = this.model.setup;
10474
+ L.set(S);
10475
+ const timelines = anim.bones;
10476
+ for (let k = 0; k < timelines.length; k++) {
10477
+ const tl = timelines[k];
10478
+ const i = keyAt(tl.curve, this.cursors, tl.cursor, t);
10479
+ const o = tl.bone * LOCALS;
10480
+ const fields = tl.fields;
10481
+ for (let c = 0; c < fields.length; c++) {
10482
+ const at = o + fields[c];
10483
+ if (tl.multiply[c] === 1) L[at] = S[at] * (i < 0 ? 1 : channelValue(tl.curve, i, c, t));
10484
+ else L[at] = S[at] + (i < 0 ? 0 : channelValue(tl.curve, i, c, t));
10485
+ }
10486
+ }
10487
+ }
10488
+ /** Every bone's world transform from its locals, parents first (`worldTransforms`): an inactive bone as zeros. */
10489
+ hierarchy() {
10490
+ const nb = this.model.boneNames.length;
10491
+ for (let i = 0; i < nb; i++) this.poseBone(i, true);
10492
+ }
10493
+ /**
10494
+ * One bone's world transform from its locals under its parent's current
9637
10495
  * world (`worldTransforms` and the update order's `poseBone`). An inactive
9638
10496
  * bone is zeroed by the hierarchy (`fresh`) and left as it is by a repose.
9639
10497
  */
@@ -9785,20 +10643,738 @@ class RuntimePose {
9785
10643
  this.ikSoftness[k] = c.softness + (channelValue(tl.curve, i, 1, t) - c.softness) * 1;
9786
10644
  this.ikFlags[k] = tl.flags[i];
9787
10645
  }
9788
- } else {
10646
+ } else if (c.kind === "transform") {
9789
10647
  const o = k * 6;
9790
10648
  for (let p = 0; p < 6; p++) this.mixes[o + p] = tl === null || i < 0 ? c.mixes[p] : c.mixes[p] + (channelValue(tl.curve, i, p, t) - c.mixes[p]) * 1;
10649
+ } else if (c.kind === "slider") {
10650
+ const keyed = anim === null ? null : anim.sliders[k];
10651
+ let time = c.setup.time, mix = c.setup.mix;
10652
+ if (keyed !== null) {
10653
+ if (keyed.time !== null) {
10654
+ const q = keyAt(keyed.time, this.cursors, keyed.timeCursor, t);
10655
+ if (q >= 0) time += (channelValue(keyed.time, q, 0, t) - time) * 1;
10656
+ }
10657
+ if (keyed.mix !== null) {
10658
+ const q = keyAt(keyed.mix, this.cursors, keyed.mixCursor, t);
10659
+ if (q >= 0) mix += (channelValue(keyed.mix, q, 0, t) - mix) * 1;
10660
+ }
10661
+ }
10662
+ this.sliderTime[k] = time;
10663
+ this.sliderMix[k] = mix;
10664
+ } else if (c.kind === "path") {
10665
+ const keyed = anim === null ? null : anim.paths[k];
10666
+ let position = c.position;
10667
+ let spacing = c.spacing;
10668
+ let mixRotate = c.mixRotate;
10669
+ let mixX = c.mixX;
10670
+ let mixY = c.mixY;
10671
+ if (keyed !== null) {
10672
+ if (keyed.position !== null) {
10673
+ const q = keyAt(keyed.position, this.cursors, keyed.positionCursor, t);
10674
+ if (q >= 0) position = c.position + (channelValue(keyed.position, q, 0, t) - c.position) * 1;
10675
+ }
10676
+ if (keyed.spacing !== null) {
10677
+ const q = keyAt(keyed.spacing, this.cursors, keyed.spacingCursor, t);
10678
+ if (q >= 0) spacing = c.spacing + (channelValue(keyed.spacing, q, 0, t) - c.spacing) * 1;
10679
+ }
10680
+ if (keyed.mix !== null) {
10681
+ const q = keyAt(keyed.mix, this.cursors, keyed.mixCursor, t);
10682
+ if (q >= 0) {
10683
+ mixRotate = c.mixRotate + (channelValue(keyed.mix, q, 0, t) - c.mixRotate) * 1;
10684
+ mixX = c.mixX + (channelValue(keyed.mix, q, 1, t) - c.mixX) * 1;
10685
+ mixY = c.mixY + (channelValue(keyed.mix, q, 2, t) - c.mixY) * 1;
10686
+ }
10687
+ }
10688
+ }
10689
+ const o = k * 5;
10690
+ this.pathValues[o] = position;
10691
+ this.pathValues[o + 1] = spacing;
10692
+ this.pathValues[o + 2] = mixRotate;
10693
+ this.pathValues[o + 3] = mixX;
10694
+ this.pathValues[o + 4] = mixY;
9791
10695
  }
9792
10696
  }
9793
10697
  }
9794
10698
  /** Every applied constraint in the document's order, each followed by the update order's repose (`applyConstraints`). */
9795
10699
  solveConstraints() {
9796
10700
  const cs = this.model.constraints;
10701
+ const hasPath = this.model.hasPath;
9797
10702
  for (let k = 0; k < cs.length; k++) {
10703
+ if (hasPath) this.snapSlotBones(k, false);
9798
10704
  const c = cs[k];
9799
10705
  if (!c.applied) continue;
9800
10706
  if (c.kind === "ik") this.solveIk(c, k);
9801
- else this.solveTransform(c, k);
10707
+ else if (c.kind === "transform") this.solveTransform(c, k);
10708
+ else if (c.kind === "slider") this.solveSlider(c, k);
10709
+ else if (c.kind === "path") this.solvePath(c, k);
10710
+ else if (this.physicsPhase !== "none" && !this.model.omitPhysics) {
10711
+ const b = c.bones[0];
10712
+ if (stepRuntimePhysics(this.physicsRecords[k], this.physicsStates[k], this.world, b * WORLDS, this.model.lengths[b], this.physicsTime, this.physicsPhase === "reset", this.environment)) {
10713
+ this.changed[0] = this.inWorld[0] = b;
10714
+ this.repose(1, 1);
10715
+ }
10716
+ }
10717
+ if (hasPath) this.snapSlotBones(k, true);
10718
+ }
10719
+ }
10720
+ snapSlotBones(at, after) {
10721
+ const cs = this.model.constraints;
10722
+ const W = this.world;
10723
+ for (let k = 0; k < cs.length; k++) {
10724
+ const c = cs[k];
10725
+ if (c.kind !== "path" || c.slotEventAt !== at || c.slotEventAfter !== after || k === at && !after) continue;
10726
+ const sb = c.slotBone * WORLDS;
10727
+ this.pathSlotSign[k] = W[sb + W_A] * W[sb + W_D] - W[sb + W_B] * W[sb + W_C] > 0 ? 1 : -1;
10728
+ }
10729
+ }
10730
+ /** Reset where the consumer starts: the setup pose or animation pose zero. */
10731
+ resetPhysics(at = "animation") {
10732
+ this.physicsTime = 0;
10733
+ this.physicsLast = -1;
10734
+ for (let k = 0; k < this.physicsStates.length; k++) resetPhysicsState(this.physicsStates[k], 0);
10735
+ if (at === "animation") this.apply(0, "reset", 0);
10736
+ else {
10737
+ const current = this.current;
10738
+ this.setupPose("reset");
10739
+ this.current = current;
10740
+ }
10741
+ this.physicsReady = true;
10742
+ }
10743
+ checkPhysicsScope(scope) {
10744
+ if (scope === void 0) return null;
10745
+ if (!Array.isArray(scope)) throw new RuntimeInputError(`physics movement scope is ${String(scope)}, not an array of physics constraint names`);
10746
+ const selected = this.physicsScope;
10747
+ selected.fill(0);
10748
+ const cs = this.model.constraints;
10749
+ for (let i = 0; i < scope.length; i++) {
10750
+ const name = scope[i];
10751
+ if (typeof name !== "string") throw new RuntimeInputError(`physics movement scope name ${String(name)} is not a string`);
10752
+ let found;
10753
+ for (let k = 0; k < cs.length; k++) {
10754
+ if (cs[k].kind === "physics" && cs[k].name === name) {
10755
+ found = cs[k];
10756
+ break;
10757
+ }
10758
+ }
10759
+ if (found === void 0) {
10760
+ for (let k = 0; k < cs.length; k++) {
10761
+ if (cs[k].name === name) {
10762
+ found = cs[k];
10763
+ break;
10764
+ }
10765
+ }
10766
+ }
10767
+ if (found === void 0) throw new RuntimeInputError(`physics movement scope: no constraint "${name}"; this document declares [${cs.map((c) => c.name).join(", ")}]`);
10768
+ if (found.kind !== "physics") throw new RuntimeInputError(`physics movement scope: constraint "${name}" is ${found.kind}, not physics`);
10769
+ selected[found.index] = 1;
10770
+ }
10771
+ return this.model.ignorePhysicsScope ? null : selected;
10772
+ }
10773
+ /** External motion changes the previous origin, never the authored locals. */
10774
+ physicsTranslate(x, y, scope) {
10775
+ const selected = this.checkPhysicsScope(scope);
10776
+ if (!Number.isFinite(x) || !Number.isFinite(y)) throw new RuntimeInputError("physicsTranslate: x and y must be finite");
10777
+ for (let k = 0; k < this.physicsStates.length; k++) {
10778
+ if (selected !== null && selected[k] === 0) continue;
10779
+ const s = this.physicsStates[k];
10780
+ s.ux -= x;
10781
+ s.uy -= y;
10782
+ s.cx -= x;
10783
+ s.cy -= y;
10784
+ }
10785
+ }
10786
+ physicsRotate(x, y, degrees, scope) {
10787
+ const selected = this.checkPhysicsScope(scope);
10788
+ if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(degrees)) throw new RuntimeInputError("physicsRotate: x, y and degrees must be finite");
10789
+ const r = degrees * RAD, cos = Math.cos(r), sin = Math.sin(r);
10790
+ for (let k = 0; k < this.physicsStates.length; k++) {
10791
+ if (selected !== null && selected[k] === 0) continue;
10792
+ const s = this.physicsStates[k];
10793
+ const dx = s.cx - x, dy = s.cy - y;
10794
+ const tx = dx * cos - dy * sin - dx, ty = dx * sin + dy * cos - dy;
10795
+ s.ux -= tx;
10796
+ s.uy -= ty;
10797
+ s.cx -= tx;
10798
+ s.cy -= ty;
10799
+ }
10800
+ }
10801
+ setEnvironment(env) {
10802
+ if (!Number.isFinite(env.wind[0]) || !Number.isFinite(env.wind[1]) || !Number.isFinite(env.gravity[0]) || !Number.isFinite(env.gravity[1])) throw new RuntimeInputError("setEnvironment: wind and gravity must be finite");
10803
+ this.environment[0] = env.wind[0];
10804
+ this.environment[1] = env.wind[1];
10805
+ this.environment[2] = env.gravity[0];
10806
+ this.environment[3] = env.gravity[1];
10807
+ }
10808
+ /** The per-pass values start at setup, before any slider writes. */
10809
+ physicsValues(anim, t, resets) {
10810
+ const cs = this.model.constraints;
10811
+ for (let k = 0; k < cs.length; k++) {
10812
+ const c = cs[k];
10813
+ if (c.kind !== "physics") continue;
10814
+ const r = this.physicsRecords[k];
10815
+ r.inertia = c.setup.inertia;
10816
+ r.strength = c.setup.strength;
10817
+ r.damping = c.setup.damping;
10818
+ r.massInverse = c.setup.massInverse;
10819
+ r.wind = c.setup.wind;
10820
+ r.gravity = c.setup.gravity;
10821
+ r.mix = c.setup.mix;
10822
+ }
10823
+ if (anim !== null) this.physicsTimelines(anim.physics, t, 1, false, true, resets);
10824
+ if (resets) this.physicsLast = t;
10825
+ }
10826
+ physicsTimelines(timelines, t, alpha, additive, setup, resets = false) {
10827
+ for (let q = 0; q < timelines.length; q++) {
10828
+ const tl = timelines[q];
10829
+ if (tl.kind === "reset") {
10830
+ if (!resets) continue;
10831
+ let crossed = false;
10832
+ const last = this.physicsLast;
10833
+ for (let i2 = 0; i2 < tl.curve.keys; i2++) {
10834
+ const time = tl.curve.times[i2];
10835
+ if (t < last ? time > last || time <= t : time > last && time <= t) crossed = true;
10836
+ }
10837
+ if (crossed) for (let j = 0; j < tl.targets.length; j++) resetPhysicsState(this.physicsStates[tl.targets[j]], this.physicsTime);
10838
+ continue;
10839
+ }
10840
+ const i = keyAt(tl.curve, this.cursors, tl.cursor, t);
10841
+ if (i < 0) {
10842
+ if (setup) for (let j = 0; j < tl.targets.length; j++) {
10843
+ const k = tl.targets[j], r = this.physicsRecords[k];
10844
+ const original = this.model.constraints[k].setup;
10845
+ if (tl.kind === "mass") r.massInverse = original.massInverse;
10846
+ else r[tl.kind] = original[tl.kind];
10847
+ }
10848
+ continue;
10849
+ }
10850
+ const v = channelValue(tl.curve, i, 0, t);
10851
+ for (let j = 0; j < tl.targets.length; j++) {
10852
+ const k = tl.targets[j], r = this.physicsRecords[k];
10853
+ const from = setup ? this.model.constraints[k].setup : r;
10854
+ if (tl.kind === "mass") {
10855
+ const mass = 1 / from.massInverse;
10856
+ r.massInverse = 1 / (mass + (v - mass) * alpha);
10857
+ } else {
10858
+ r[tl.kind] = additive && (tl.kind === "wind" || tl.kind === "gravity") ? r[tl.kind] + v * alpha : from[tl.kind] + (v - from[tl.kind]) * alpha;
10859
+ }
10860
+ }
10861
+ }
10862
+ }
10863
+ /** Read the dial where previous constraints left it; this value is never track time. */
10864
+ solveSlider(c, k) {
10865
+ const alpha = this.sliderMix[k];
10866
+ if (alpha === 0) return;
10867
+ let time = this.sliderTime[k];
10868
+ const r = c.setup, a = c.animation;
10869
+ if (c.bone >= 0) {
10870
+ const b = c.bone, w = b * WORLDS, l = b * LOCALS;
10871
+ let value;
10872
+ if (r.local) {
10873
+ const field = c.property === 0 ? L_ROTATION : c.property === 1 ? L_X : c.property === 2 ? L_Y : c.property === 3 ? L_SCALE_X : c.property === 4 ? L_SCALE_Y : L_SHEAR_Y;
10874
+ value = this.locals[l + field] + 0;
10875
+ } else {
10876
+ const W = this.world, x = W[w], y = W[w + 2], bx = W[w + 1], by = W[w + 3];
10877
+ switch (c.property) {
10878
+ case 0:
10879
+ value = Math.atan2(y, x) * DEG + (x * by - bx * y > 0 ? 0 : -0);
10880
+ if (value < 0) value += 360;
10881
+ break;
10882
+ case 1:
10883
+ value = x * 0 + bx * 0 + W[w + W_X];
10884
+ break;
10885
+ case 2:
10886
+ value = y * 0 + by * 0 + W[w + W_Y];
10887
+ break;
10888
+ case 3:
10889
+ value = Math.sqrt(x * x + y * y) + 0;
10890
+ break;
10891
+ case 4:
10892
+ value = Math.sqrt(bx * bx + by * by) + 0;
10893
+ break;
10894
+ default:
10895
+ value = (Math.atan2(by, bx) - Math.atan2(y, x)) * DEG - 90 + 0;
10896
+ }
10897
+ }
10898
+ time = r.to + (value - r.from) * r.scale;
10899
+ time = r.loop ? a.duration === 0 ? 0 : a.duration + time % a.duration : Math.max(0, time);
10900
+ }
10901
+ const at = this.model.sliderAsTrack ? this.time : r.loop && a.duration !== 0 ? time % a.duration : time;
10902
+ this.sliderAt[k] = at;
10903
+ if (this.physicsPhase !== "none") this.physicsTimelines(a.physics, at, alpha, r.additive, false);
10904
+ const L = this.locals, S = this.model.setup;
10905
+ for (let q = 0; q < a.sliderBones.length; q++) {
10906
+ const tl = a.sliderBones[q], i = keyAt(tl.curve, this.cursors, tl.cursor, at);
10907
+ if (i < 0) continue;
10908
+ const o = tl.bone * LOCALS;
10909
+ for (let f = 0; f < tl.fields.length; f++) {
10910
+ const n = o + tl.fields[f], v = channelValue(tl.curve, i, f, at), current = L[n], setup = S[n];
10911
+ if (tl.multiply[f]) {
10912
+ if (r.additive) L[n] = current + (v * setup - setup) * alpha;
10913
+ else {
10914
+ const target = setup * v, from = Math.abs(current) * Math.sign(target);
10915
+ L[n] = alpha === 1 ? target : from + (target - from) * alpha;
10916
+ }
10917
+ } else L[n] = r.additive ? current + v * alpha : current + (setup + v - current) * alpha;
10918
+ }
10919
+ }
10920
+ for (let q = 0; q < c.bones.length; q++) this.changed[q] = c.bones[q];
10921
+ if (c.bones.length > 0) this.repose(c.bones.length, 0);
10922
+ }
10923
+ /** A path constraint (`solvePath` in `src/core/constraints_path.ts`), every buffer overwritten in place. */
10924
+ solvePath(c, k) {
10925
+ if (c.attachment < 0 || this.model.omitPath) return;
10926
+ const o5 = k * 5;
10927
+ const position = this.pathValues[o5];
10928
+ const spacing = this.pathValues[o5 + 1];
10929
+ const mixRotate = this.pathValues[o5 + 2];
10930
+ const mixX = this.pathValues[o5 + 3];
10931
+ const mixY = this.pathValues[o5 + 4];
10932
+ if (mixRotate === 0 && mixX === 0 && mixY === 0) return;
10933
+ const wantsTangent = c.rotateMode === PATH_ROTATE_TANGENT;
10934
+ const scale = c.rotateMode === PATH_ROTATE_CHAIN_SCALE;
10935
+ const bones = c.bones;
10936
+ const boneCount = bones.length;
10937
+ const spaceCount = wantsTangent ? boneCount : boneCount + 1;
10938
+ const spaces = this.pathSpaces;
10939
+ const lengths = this.pathLengths;
10940
+ const W = this.world;
10941
+ const modelLengths = this.model.lengths;
10942
+ spaces[0] = 0;
10943
+ if (c.spacingMode === PATH_SPACING_PERCENT) {
10944
+ if (scale) {
10945
+ for (let i = 0; i < spaceCount - 1; i++) {
10946
+ const bi = bones[i];
10947
+ const boneLength = modelLengths[bi];
10948
+ const w = bi * WORLDS;
10949
+ const x = boneLength * W[w + W_A];
10950
+ const y = boneLength * W[w + W_C];
10951
+ lengths[i] = Math.sqrt(x * x + y * y);
10952
+ }
10953
+ }
10954
+ for (let i = 1; i < spaceCount; i++) spaces[i] = spacing;
10955
+ } else if (c.spacingMode === PATH_SPACING_PROPORTIONAL) {
10956
+ let sum = 0;
10957
+ for (let i = 0, n = spaceCount - 1; i < n; ) {
10958
+ const bi = bones[i];
10959
+ const boneLength = modelLengths[bi];
10960
+ if (boneLength < PATH_EPSILON) {
10961
+ if (scale) lengths[i] = 0;
10962
+ spaces[++i] = spacing;
10963
+ } else {
10964
+ const w = bi * WORLDS;
10965
+ const x = boneLength * W[w + W_A];
10966
+ const y = boneLength * W[w + W_C];
10967
+ const length = Math.sqrt(x * x + y * y);
10968
+ if (scale) lengths[i] = length;
10969
+ spaces[++i] = length;
10970
+ sum += length;
10971
+ }
10972
+ }
10973
+ if (sum > 0) {
10974
+ sum = spaceCount / sum * spacing;
10975
+ for (let i = 1; i < spaceCount; i++) spaces[i] *= sum;
10976
+ }
10977
+ } else {
10978
+ const addsLength = c.spacingMode === PATH_SPACING_LENGTH;
10979
+ for (let i = 0, n = spaceCount - 1; i < n; ) {
10980
+ const bi = bones[i];
10981
+ const boneLength = modelLengths[bi];
10982
+ if (boneLength < PATH_EPSILON) {
10983
+ if (scale) lengths[i] = 0;
10984
+ spaces[++i] = spacing;
10985
+ } else {
10986
+ const w = bi * WORLDS;
10987
+ const x = boneLength * W[w + W_A];
10988
+ const y = boneLength * W[w + W_C];
10989
+ const length = Math.sqrt(x * x + y * y);
10990
+ if (scale) lengths[i] = length;
10991
+ spaces[++i] = (addsLength ? Math.max(0, boneLength + spacing) : spacing) * length / boneLength;
10992
+ }
10993
+ }
10994
+ }
10995
+ this.positionsOfPath(c, position, spaceCount, wantsTangent);
10996
+ const positions = this.pathPositions;
10997
+ let atX = positions[0];
10998
+ let atY = positions[1];
10999
+ let offsetRotation = c.offsetRotation;
11000
+ let fromTip;
11001
+ if (offsetRotation === 0) fromTip = c.rotateMode === PATH_ROTATE_CHAIN;
11002
+ else {
11003
+ fromTip = false;
11004
+ let positive;
11005
+ if (c.slotEventAt === k && !c.slotEventAfter) {
11006
+ const sb = c.slotBone * WORLDS;
11007
+ positive = W[sb + W_A] * W[sb + W_D] - W[sb + W_B] * W[sb + W_C] > 0;
11008
+ } else {
11009
+ positive = c.slotEventAt >= 0 && this.pathSlotSign[k] > 0;
11010
+ }
11011
+ offsetRotation *= positive ? RAD : -RAD;
11012
+ }
11013
+ for (let i = 0, p = 3; i < boneCount; i++, p += 3) {
11014
+ const bi = bones[i];
11015
+ const o = bi * WORLDS;
11016
+ let wa = W[o + W_A];
11017
+ let wb = W[o + W_B];
11018
+ let wc = W[o + W_C];
11019
+ let wd = W[o + W_D];
11020
+ const wx = W[o + W_X] + (atX - W[o + W_X]) * mixX;
11021
+ const wy = W[o + W_Y] + (atY - W[o + W_Y]) * mixY;
11022
+ const x = positions[p];
11023
+ const y = positions[p + 1];
11024
+ const dx = x - atX;
11025
+ const dy = y - atY;
11026
+ if (scale) {
11027
+ const length = lengths[i];
11028
+ if (length >= PATH_EPSILON) {
11029
+ const s = (Math.sqrt(dx * dx + dy * dy) / length - 1) * mixRotate + 1;
11030
+ wa *= s;
11031
+ wc *= s;
11032
+ }
11033
+ }
11034
+ atX = x;
11035
+ atY = y;
11036
+ if (mixRotate > 0) {
11037
+ const a = wa;
11038
+ const bb = wb;
11039
+ const cc = wc;
11040
+ const d = wd;
11041
+ let r;
11042
+ if (wantsTangent) r = positions[p - 1];
11043
+ else if (spaces[i + 1] === 0) r = positions[p + 2];
11044
+ else r = Math.atan2(dy, dx);
11045
+ r -= Math.atan2(cc, a);
11046
+ if (fromTip) {
11047
+ const cos2 = Math.cos(r);
11048
+ const sin2 = Math.sin(r);
11049
+ const length = modelLengths[bi];
11050
+ atX += (length * (cos2 * a - sin2 * cc) - dx) * mixRotate;
11051
+ atY += (length * (sin2 * a + cos2 * cc) - dy) * mixRotate;
11052
+ } else r += offsetRotation;
11053
+ if (r > RUNTIME_PI) r -= TWO_PI;
11054
+ else if (r < -RUNTIME_PI) r += TWO_PI;
11055
+ r *= mixRotate;
11056
+ const cos = Math.cos(r);
11057
+ const sin = Math.sin(r);
11058
+ wa = cos * a - sin * cc;
11059
+ wb = cos * bb - sin * d;
11060
+ wc = sin * a + cos * cc;
11061
+ wd = sin * bb + cos * d;
11062
+ }
11063
+ W[o + W_A] = wa;
11064
+ W[o + W_B] = wb;
11065
+ W[o + W_C] = wc;
11066
+ W[o + W_D] = wd;
11067
+ W[o + W_X] = wx;
11068
+ W[o + W_Y] = wy;
11069
+ this.changed[i] = bi;
11070
+ this.inWorld[i] = bi;
11071
+ }
11072
+ if (boneCount > 0) this.repose(boneCount, boneCount);
11073
+ }
11074
+ computePathWorld(c) {
11075
+ const at = this.model.attachments[c.attachment];
11076
+ const W = this.world;
11077
+ const pts = this.pathWorldPts;
11078
+ const deformed = this.deformed[c.attachment] !== 0;
11079
+ if (at.bindingCounts.length === 0) {
11080
+ const b = c.slotBone * WORLDS;
11081
+ const a = W[b + W_A];
11082
+ const bb = W[b + W_B];
11083
+ const cc = W[b + W_C];
11084
+ const d = W[b + W_D];
11085
+ const wx = W[b + W_X];
11086
+ const wy = W[b + W_Y];
11087
+ const local = deformed ? this.deform : at.local;
11088
+ const lo = deformed ? at.deformOffset : 0;
11089
+ for (let q = 0; q < c.pathVertices; q += 2) {
11090
+ const x = local[lo + q];
11091
+ const y = local[lo + q + 1];
11092
+ pts[q] = x * a + y * bb + wx;
11093
+ pts[q + 1] = x * cc + y * d + wy;
11094
+ }
11095
+ return;
11096
+ }
11097
+ const counts = at.bindingCounts;
11098
+ const bones = at.bindingBones;
11099
+ const xs = at.bindingX;
11100
+ const ys = at.bindingY;
11101
+ const weights = at.bindingWeights;
11102
+ let n = 0;
11103
+ let o = 0;
11104
+ for (let v = 0; v < counts.length; v++) {
11105
+ let wx = 0;
11106
+ let wy = 0;
11107
+ const end = n + counts[v];
11108
+ for (; n < end; n++) {
11109
+ const b = bones[n] * WORLDS;
11110
+ const weight = weights[n];
11111
+ const x = deformed ? xs[n] + this.deform[at.deformOffset + n * 2] : xs[n];
11112
+ const y = deformed ? ys[n] + this.deform[at.deformOffset + n * 2 + 1] : ys[n];
11113
+ wx += (x * W[b + W_A] + y * W[b + W_B] + W[b + W_X]) * weight;
11114
+ wy += (x * W[b + W_C] + y * W[b + W_D] + W[b + W_Y]) * weight;
11115
+ }
11116
+ pts[o++] = wx;
11117
+ pts[o++] = wy;
11118
+ }
11119
+ }
11120
+ positionsOfPath(c, position, spaceCount, wantsTangent) {
11121
+ this.computePathWorld(c);
11122
+ const pts = this.pathWorldPts;
11123
+ const vc = c.pathVertices;
11124
+ const n = vc / 2;
11125
+ const spaces = this.pathSpaces;
11126
+ const out = this.pathPositions;
11127
+ const outLen = spaceCount * 3 + 2;
11128
+ for (let i = 0; i < outLen; i++) out[i] = 0;
11129
+ const closed = c.closed;
11130
+ if (!c.constantSpeed) {
11131
+ const lengths = c.lengths;
11132
+ const curveCount2 = n / 3 - (closed ? 1 : 2);
11133
+ const total2 = lengths[curveCount2];
11134
+ if (c.positionMode === PATH_POSITION_PERCENT) position *= total2;
11135
+ const spaceScale2 = c.spacingMode === PATH_SPACING_PERCENT ? total2 : c.spacingMode === PATH_SPACING_PROPORTIONAL ? total2 / spaceCount : 1;
11136
+ let curve = 0;
11137
+ for (let i = 0, o = 0; i < spaceCount; i++, o += 3) {
11138
+ const space = spaces[i] * spaceScale2;
11139
+ position += space;
11140
+ let p = position;
11141
+ if (closed) {
11142
+ p %= total2;
11143
+ if (p < 0) p += total2;
11144
+ curve = 0;
11145
+ } else if (p < 0) {
11146
+ onLineBefore(p, pts[2], pts[3], pts[4], pts[5], out, o);
11147
+ continue;
11148
+ } else if (p > total2) {
11149
+ const m0 = 2 * (n - 3);
11150
+ const m1 = 2 * (n - 2);
11151
+ onLinePast(p - total2, pts[m0], pts[m0 + 1], pts[m1], pts[m1 + 1], out, o);
11152
+ continue;
11153
+ }
11154
+ for (; ; curve++) {
11155
+ const length = lengths[curve];
11156
+ if (p > length) continue;
11157
+ if (curve === 0) p /= length;
11158
+ else {
11159
+ const prev = lengths[curve - 1];
11160
+ p = (p - prev) / (length - prev);
11161
+ }
11162
+ break;
11163
+ }
11164
+ const kk = 3 * curve + 1;
11165
+ const i0 = kk % n * 2;
11166
+ const i1 = (kk + 1) % n * 2;
11167
+ const i2 = (kk + 2) % n * 2;
11168
+ const i3 = (kk + 3) % n * 2;
11169
+ onCurve(p, pts[i0], pts[i0 + 1], pts[i1], pts[i1 + 1], pts[i2], pts[i2 + 1], pts[i3], pts[i3 + 1], out, o, wantsTangent || i > 0 && space === 0);
11170
+ }
11171
+ return;
11172
+ }
11173
+ const chain = this.pathChain;
11174
+ let chainLen = 0;
11175
+ if (closed) {
11176
+ for (let q = 2; q < vc; q++) chain[chainLen++] = pts[q];
11177
+ chain[chainLen++] = pts[0];
11178
+ chain[chainLen++] = pts[1];
11179
+ chain[chainLen++] = pts[2];
11180
+ chain[chainLen++] = pts[3];
11181
+ } else {
11182
+ for (let q = 2; q < vc - 2; q++) chain[chainLen++] = pts[q];
11183
+ }
11184
+ const curveCount = closed ? n / 3 : n / 3 - 1;
11185
+ const curveEnds = this.pathCurveEnds;
11186
+ let total = 0;
11187
+ let cx0 = chain[0];
11188
+ let cy0 = chain[1];
11189
+ for (let i = 0, w = 2; i < curveCount; i++, w += 6) {
11190
+ const cx12 = chain[w];
11191
+ const cy12 = chain[w + 1];
11192
+ const cx22 = chain[w + 2];
11193
+ const cy22 = chain[w + 3];
11194
+ const x22 = chain[w + 4];
11195
+ const y22 = chain[w + 5];
11196
+ const h2x = (cx0 - cx12 * 2 + cx22) * 0.1875;
11197
+ const h2y = (cy0 - cy12 * 2 + cy22) * 0.1875;
11198
+ const d3x = ((cx12 - cx22) * 3 - cx0 + x22) * 0.09375;
11199
+ const d3y = ((cy12 - cy22) * 3 - cy0 + y22) * 0.09375;
11200
+ let d2x = h2x * 2 + d3x;
11201
+ let d2y = h2y * 2 + d3y;
11202
+ let d1x = (cx12 - cx0) * 0.75 + h2x + d3x * 0.16666667;
11203
+ let d1y = (cy12 - cy0) * 0.75 + h2y + d3y * 0.16666667;
11204
+ total += Math.sqrt(d1x * d1x + d1y * d1y);
11205
+ d1x += d2x;
11206
+ d1y += d2y;
11207
+ d2x += d3x;
11208
+ d2y += d3y;
11209
+ total += Math.sqrt(d1x * d1x + d1y * d1y);
11210
+ d1x += d2x;
11211
+ d1y += d2y;
11212
+ total += Math.sqrt(d1x * d1x + d1y * d1y);
11213
+ d1x += d2x + d3x;
11214
+ d1y += d2y + d3y;
11215
+ total += Math.sqrt(d1x * d1x + d1y * d1y);
11216
+ curveEnds[i] = total;
11217
+ cx0 = x22;
11218
+ cy0 = y22;
11219
+ }
11220
+ const pathTotal = curveCount > 0 ? curveEnds[curveCount - 1] : 0;
11221
+ if (c.positionMode === PATH_POSITION_PERCENT) position *= pathTotal;
11222
+ const spaceScale = c.spacingMode === PATH_SPACING_PERCENT ? pathTotal : c.spacingMode === PATH_SPACING_PROPORTIONAL ? pathTotal / spaceCount : 1;
11223
+ const segmentEnds = this.pathSegmentEnds;
11224
+ for (let s = 0; s < 10; s++) segmentEnds[s] = 0;
11225
+ let curveTotal = 0;
11226
+ let lastCurve = -1;
11227
+ let x1 = 0, y1 = 0, cx1 = 0, cy1 = 0, cx2 = 0, cy2 = 0, x2 = 0, y2 = 0;
11228
+ for (let i = 0, o = 0, curve = 0, segment = 0; i < spaceCount; i++, o += 3) {
11229
+ const space = spaces[i] * spaceScale;
11230
+ position += space;
11231
+ let p = position;
11232
+ if (closed) {
11233
+ p %= pathTotal;
11234
+ if (p < 0) p += pathTotal;
11235
+ curve = 0;
11236
+ segment = 0;
11237
+ } else if (p < 0) {
11238
+ onLineBefore(p, chain[0], chain[1], chain[2], chain[3], out, o);
11239
+ continue;
11240
+ } else if (p > pathTotal) {
11241
+ onLinePast(p - pathTotal, chain[chainLen - 4], chain[chainLen - 3], chain[chainLen - 2], chain[chainLen - 1], out, o);
11242
+ continue;
11243
+ }
11244
+ for (; ; curve++) {
11245
+ const length = curveEnds[curve];
11246
+ if (p > length) continue;
11247
+ if (curve === 0) p /= length;
11248
+ else {
11249
+ const prev = curveEnds[curve - 1];
11250
+ p = (p - prev) / (length - prev);
11251
+ }
11252
+ break;
11253
+ }
11254
+ if (curve !== lastCurve) {
11255
+ lastCurve = curve;
11256
+ const ii = curve * 6;
11257
+ x1 = chain[ii];
11258
+ y1 = chain[ii + 1];
11259
+ cx1 = chain[ii + 2];
11260
+ cy1 = chain[ii + 3];
11261
+ cx2 = chain[ii + 4];
11262
+ cy2 = chain[ii + 5];
11263
+ x2 = chain[ii + 6];
11264
+ y2 = chain[ii + 7];
11265
+ const h2x = (x1 - cx1 * 2 + cx2) * 0.03;
11266
+ const h2y = (y1 - cy1 * 2 + cy2) * 0.03;
11267
+ const d3x = ((cx1 - cx2) * 3 - x1 + x2) * 6e-3;
11268
+ const d3y = ((cy1 - cy2) * 3 - y1 + y2) * 6e-3;
11269
+ let d2x = h2x * 2 + d3x;
11270
+ let d2y = h2y * 2 + d3y;
11271
+ let d1x = (cx1 - x1) * 0.3 + h2x + d3x * 0.16666667;
11272
+ let d1y = (cy1 - y1) * 0.3 + h2y + d3y * 0.16666667;
11273
+ curveTotal = Math.sqrt(d1x * d1x + d1y * d1y);
11274
+ segmentEnds[0] = curveTotal;
11275
+ for (let s = 1; s < 8; s++) {
11276
+ d1x += d2x;
11277
+ d1y += d2y;
11278
+ d2x += d3x;
11279
+ d2y += d3y;
11280
+ curveTotal += Math.sqrt(d1x * d1x + d1y * d1y);
11281
+ segmentEnds[s] = curveTotal;
11282
+ }
11283
+ d1x += d2x;
11284
+ d1y += d2y;
11285
+ curveTotal += Math.sqrt(d1x * d1x + d1y * d1y);
11286
+ segmentEnds[8] = curveTotal;
11287
+ d1x += d2x + d3x;
11288
+ d1y += d2y + d3y;
11289
+ curveTotal += Math.sqrt(d1x * d1x + d1y * d1y);
11290
+ segmentEnds[9] = curveTotal;
11291
+ segment = 0;
11292
+ }
11293
+ p *= curveTotal;
11294
+ for (; ; segment++) {
11295
+ const length = segmentEnds[segment];
11296
+ if (p > length) continue;
11297
+ if (segment === 0) p /= length;
11298
+ else {
11299
+ const prev = segmentEnds[segment - 1];
11300
+ p = segment + (p - prev) / (length - prev);
11301
+ }
11302
+ break;
11303
+ }
11304
+ onCurve(p * 0.1, x1, y1, cx1, cy1, cx2, cy2, x2, y2, out, o, wantsTangent || i > 0 && space === 0);
11305
+ }
11306
+ }
11307
+ sliderAttachments() {
11308
+ for (let k = 0; k < this.model.constraints.length; k++) {
11309
+ this.sliderSlots(k);
11310
+ this.sliderDeforms(k);
11311
+ }
11312
+ }
11313
+ sliderSlots(index) {
11314
+ const cs = this.model.constraints;
11315
+ {
11316
+ const k = index, c = cs[k];
11317
+ if (c.kind !== "slider" || !c.applied || this.sliderMix[k] === 0) return;
11318
+ const a = c.animation, t = this.sliderAt[k], alpha = this.sliderMix[k];
11319
+ if (!this.model.omitDrawOrder && a.drawOrder !== null) {
11320
+ const di = keyAt(a.drawOrder.curve, this.cursors, a.drawOrder.cursor, t);
11321
+ if (di >= 0) {
11322
+ const ns = this.drawOrder.length, orders = a.drawOrder.orders, base = di * ns;
11323
+ for (let s = 0; s < ns; s++) this.drawOrder[s] = orders[base + s];
11324
+ }
11325
+ }
11326
+ for (let q = 0; q < a.slots.length; q++) {
11327
+ const tl = a.slots[q], i = keyAt(tl.curve, this.cursors, tl.cursor, t);
11328
+ if (i < 0) continue;
11329
+ if (tl.attachments !== null) {
11330
+ const next = tl.attachments[i];
11331
+ if (next !== this.shown[tl.slot] && next >= 0) this.deformed[next] = 0;
11332
+ this.shown[tl.slot] = next;
11333
+ } else {
11334
+ const channels = tl.channels;
11335
+ const slot = this.model.slots[tl.slot];
11336
+ const o = tl.slot * 4, d = tl.slot * 3;
11337
+ for (let f = 0; f < channels.length; f++) {
11338
+ const at = channels[f], v = channelValue(tl.curve, i, f, t);
11339
+ if (at < 4) {
11340
+ const n = o + at;
11341
+ this.light[n] = clamp01(alpha === 1 ? v : this.light[n] + (v - this.light[n]) * alpha);
11342
+ } else if (slot.dark !== null && !this.model.omitDarkColour) {
11343
+ const n = d + (at - 4);
11344
+ this.dark[n] = clamp01(alpha === 1 ? v : this.dark[n] + (v - this.dark[n]) * alpha);
11345
+ }
11346
+ }
11347
+ }
11348
+ }
11349
+ }
11350
+ }
11351
+ sliderDeforms(index) {
11352
+ if (this.model.omitDeform) return;
11353
+ const cs = this.model.constraints;
11354
+ {
11355
+ const k = index, c = cs[k];
11356
+ if (c.kind !== "slider" || !c.applied || this.sliderMix[k] === 0) return;
11357
+ const a = c.animation, t = this.sliderAt[k], alpha = this.sliderMix[k];
11358
+ for (let q = 0; q < a.deforms.length; q++) {
11359
+ const tl = a.deforms[q], i = keyAt(tl.curve, this.cursors, tl.cursor, t);
11360
+ if (i < 0) continue;
11361
+ const held = i + 1 === tl.curve.keys || tl.curve.kinds[i] === CURVE_STEPPED;
11362
+ const p = held ? 0 : deformFraction(tl.curve, i, t), start = i * tl.count;
11363
+ for (let slot = 0; slot < this.shown.length; slot++) {
11364
+ const index2 = this.shown[slot];
11365
+ if (index2 < 0 || !this.model.slots[slot].live) continue;
11366
+ const at = this.model.attachments[index2];
11367
+ if (at.timeline !== tl.identity || at.deformCount === 0) continue;
11368
+ const current = this.deformed[index2] !== 0;
11369
+ for (let f = 0; f < tl.count; f++) {
11370
+ const v = tl.held[start + f], target = held ? v : v + (tl.held[start + tl.count + f] - v) * p;
11371
+ const setup = at.draw === DRAW_MESH ? at.local[f] : 0, n = at.deformOffset + f;
11372
+ const from = current ? this.deform[n] : setup;
11373
+ this.deform[n] = alpha === 1 && (!c.setup.additive || !current) ? target : c.setup.additive ? from + (target - setup) * alpha : from + (target - from) * alpha;
11374
+ }
11375
+ this.deformed[index2] = 1;
11376
+ }
11377
+ }
9802
11378
  }
9803
11379
  }
9804
11380
  solveIk(c, k) {
@@ -10055,7 +11631,10 @@ class RuntimePose {
10055
11631
  L[cl + L_ROTATION] = childRotation + wrap180(childTarget - childRotation) * mix;
10056
11632
  return true;
10057
11633
  }
10058
- /** A world-space, absolute transform (`solveTransform`): each bone's world edited in turn and written back, then the repose. */
11634
+ /** Absolute/additive transform, matching `solveTransform` in src/core/constraints.ts:
11635
+ * read the source per target/property, edit locals or the scratch world,
11636
+ * then repose the union in bone order (`repose` in that module). No read-back for a local target.
11637
+ */
10059
11638
  solveTransform(c, k) {
10060
11639
  const W = this.world;
10061
11640
  const w = this.w;
@@ -10085,24 +11664,31 @@ class RuntimePose {
10085
11664
  v = v < lo ? lo : v > hi ? hi : v;
10086
11665
  }
10087
11666
  applied = true;
10088
- applyWorld(w, property, v, mix);
11667
+ if (c.localTarget) {
11668
+ const at = bi * LOCALS + PROPERTY_LOCAL[property];
11669
+ const current = this.locals[at];
11670
+ this.locals[at] = c.additive ? property === P_SCALE_X || property === P_SCALE_Y ? current * (1 + (v - 1) * mix) : current + v * mix : current + (v - current) * mix;
11671
+ } else applyWorld(w, property, v, mix, c.additive);
10089
11672
  }
10090
11673
  }
10091
11674
  if (!applied) continue;
10092
- W[o + W_A] = w[W_A];
10093
- W[o + W_B] = w[W_B];
10094
- W[o + W_C] = w[W_C];
10095
- W[o + W_D] = w[W_D];
10096
- W[o + W_X] = w[W_X];
10097
- W[o + W_Y] = w[W_Y];
11675
+ if (!c.localTarget) {
11676
+ W[o + W_A] = w[W_A];
11677
+ W[o + W_B] = w[W_B];
11678
+ W[o + W_C] = w[W_C];
11679
+ W[o + W_D] = w[W_D];
11680
+ W[o + W_X] = w[W_X];
11681
+ W[o + W_Y] = w[W_Y];
11682
+ }
10098
11683
  this.changed[n] = bi;
10099
11684
  this.inWorld[n] = bi;
10100
11685
  n++;
10101
11686
  }
10102
- if (n > 0) this.repose(n, n);
11687
+ if (n > 0) this.repose(n, c.localTarget ? 0 : n);
10103
11688
  }
10104
- /** The source's world value of a property (`sourceValue`, world source). */
11689
+ /** The source's current local or world value (`sourceValue` in src/core/constraints.ts). */
10105
11690
  sourceValue(c, property) {
11691
+ if (c.localSource) return this.locals[c.source * LOCALS + PROPERTY_LOCAL[property]] + c.offsets[property];
10106
11692
  const W = this.world;
10107
11693
  const s = c.source * WORLDS;
10108
11694
  const o = c.offsets;
@@ -10182,7 +11768,10 @@ class RuntimePose {
10182
11768
  if (p >= 0 && below[p] === 1 && (active[p] === 1 || this.changedMark[p] === 1)) below[b] = 1;
10183
11769
  if (below[b] === 1 && this.inWorldMark[b] === 0) this.poseBone(b, false);
10184
11770
  }
10185
- /** A bone's local values read back from its world transform (`localFromWorld`) — `normal`, `onlyTranslation` and `noRotationOrReflection`; a world target in a `noScale` mode is refused at load. */
11771
+ /** A bone's locals read back from its world transform (`localFromWorld` in src/core/constraints.ts).
11772
+ * All five inherit modes: the no-scale forms read rotation first, rebuild
11773
+ * `noScaleDirection`, and retain the x-angle residual as shearX in `decompose`.
11774
+ */
10186
11775
  localFromWorld(bi) {
10187
11776
  const m = this.model;
10188
11777
  const W = this.world;
@@ -10216,6 +11805,21 @@ class RuntimePose {
10216
11805
  const wb = W[w + W_B];
10217
11806
  const wc = W[w + W_C];
10218
11807
  const wd = W[w + W_D];
11808
+ if (mode === MODE_NO_SCALE || mode === MODE_NO_SCALE_OR_REFLECTION) {
11809
+ const det = pa * pd - pb * pc;
11810
+ const flip = mode === MODE_NO_SCALE && det < 0 ? -1 : 1;
11811
+ const sign = det < 0 ? -1 : 1;
11812
+ const rotation = Math.atan2((pa * wc - pc * wa) * sign, (pd * wa - pb * wc) * sign) * DEG;
11813
+ const rr = rotation * RAD;
11814
+ const cos = Math.cos(rr), sin = Math.sin(rr);
11815
+ let ux = pa * cos + pb * sin, uy = pc * cos + pd * sin;
11816
+ const length = 1 / Math.sqrt(ux * ux + uy * uy);
11817
+ ux *= length;
11818
+ uy *= length;
11819
+ this.decompose(l, ux * wa + uy * wc, (ux * wc - uy * wa) * flip, ux * wb + uy * wd, (ux * wd - uy * wb) * flip, true);
11820
+ L[l + L_ROTATION] = rotation;
11821
+ return;
11822
+ }
10219
11823
  if (mode === MODE_ONLY_TRANSLATION) {
10220
11824
  this.decompose(l, wa, wc, wb, wd);
10221
11825
  return;
@@ -10234,7 +11838,7 @@ class RuntimePose {
10234
11838
  this.decompose(l, ia * wa - ib * wc, id * wc - ic * wa, ia * wb - ib * wd, id * wd - ic * wb);
10235
11839
  }
10236
11840
  /** A local matrix with columns `(la, lc)` and `(lb, ld)` as rotation, scales and shear (`decompose`). */
10237
- decompose(l, la, lc, lb, ld) {
11841
+ decompose(l, la, lc, lb, ld, shearX = false) {
10238
11842
  const L = this.locals;
10239
11843
  const sx = Math.sqrt(la * la + lc * lc);
10240
11844
  const yLength = Math.sqrt(lb * lb + ld * ld);
@@ -10248,11 +11852,11 @@ class RuntimePose {
10248
11852
  const xAngle = Math.atan2(lc, la) * DEG;
10249
11853
  const det = la * ld - lb * lc;
10250
11854
  const yAngle = Math.atan2(ld, lb) * DEG;
10251
- L[l + L_ROTATION] = xAngle;
11855
+ L[l + (shearX ? L_SHEAR_X : L_ROTATION)] = xAngle;
10252
11856
  L[l + L_SCALE_X] = sx;
10253
11857
  L[l + L_SCALE_Y] = det < 0 ? -yLength : yLength;
10254
11858
  const right = det < 0 ? -90 : 90;
10255
- let shear = yAngle - (xAngle + right);
11859
+ let shear = yAngle - ((shearX ? 0 : xAngle) + right);
10256
11860
  shear = shear > 180 ? shear - 360 : shear < -180 ? shear + 360 : shear;
10257
11861
  L[l + L_SHEAR_Y] = yLength > 1e-5 ? shear : 0;
10258
11862
  }
@@ -10263,16 +11867,32 @@ class RuntimePose {
10263
11867
  slotValues(anim, t) {
10264
11868
  const slots = this.model.slots;
10265
11869
  const light = this.light;
11870
+ const dark = this.dark;
10266
11871
  for (let s = 0; s < slots.length; s++) {
10267
11872
  const slot = slots[s];
10268
11873
  this.shown[s] = slot.setupAttachment;
11874
+ this.drawOrder[s] = s;
10269
11875
  const o = s * 4;
10270
11876
  light[o] = slot.light[0];
10271
11877
  light[o + 1] = slot.light[1];
10272
11878
  light[o + 2] = slot.light[2];
10273
11879
  light[o + 3] = slot.light[3];
11880
+ const setupDark = slot.dark;
11881
+ if (setupDark !== null) {
11882
+ const d = s * 3;
11883
+ dark[d] = setupDark[0];
11884
+ dark[d + 1] = setupDark[1];
11885
+ dark[d + 2] = setupDark[2];
11886
+ }
10274
11887
  }
10275
11888
  if (anim === null) return;
11889
+ if (!this.model.omitDrawOrder && anim.drawOrder !== null) {
11890
+ const di = keyAt(anim.drawOrder.curve, this.cursors, anim.drawOrder.cursor, t);
11891
+ if (di >= 0) {
11892
+ const ns = slots.length, orders = anim.drawOrder.orders, base = di * ns;
11893
+ for (let s = 0; s < ns; s++) this.drawOrder[s] = orders[base + s];
11894
+ }
11895
+ }
10276
11896
  const timelines = anim.slots;
10277
11897
  for (let k = 0; k < timelines.length; k++) {
10278
11898
  const tl = timelines[k];
@@ -10283,11 +11903,18 @@ class RuntimePose {
10283
11903
  continue;
10284
11904
  }
10285
11905
  const channels = tl.channels;
10286
- const setup = slots[tl.slot].light;
11906
+ const slot = slots[tl.slot];
11907
+ const setup = slot.light;
11908
+ const setupDark = slot.dark;
10287
11909
  const o = tl.slot * 4;
11910
+ const d = tl.slot * 3;
10288
11911
  for (let c = 0; c < channels.length; c++) {
10289
11912
  const at = channels[c];
10290
- light[o + at] = i < 0 ? setup[at] : clamp01(channelValue(tl.curve, i, c, t));
11913
+ if (at < 4) {
11914
+ light[o + at] = i < 0 ? setup[at] : clamp01(channelValue(tl.curve, i, c, t));
11915
+ } else if (setupDark !== null && !this.model.omitDarkColour) {
11916
+ dark[d + (at - 4)] = i < 0 ? setupDark[at - 4] : clamp01(channelValue(tl.curve, i, c, t));
11917
+ }
10291
11918
  }
10292
11919
  }
10293
11920
  }
@@ -10316,6 +11943,13 @@ class RuntimePose {
10316
11943
  }
10317
11944
  }
10318
11945
  }
11946
+ /** Recompute vertices and overwrite clipping output, without per-frame allocation.
11947
+ * Concave/inverse rows describe the core's drawn coverage, not Spine's native triangle list.
11948
+ */
11949
+ computeClipped() {
11950
+ this.computeVertices();
11951
+ this.clipping?.frame(this);
11952
+ }
10319
11953
  /** Every shown attachment's world vertices into `vertices` at its offset (`regionCorners`, `worldVertices`). */
10320
11954
  computeVertices() {
10321
11955
  const m = this.model;
@@ -10386,10 +12020,10 @@ class RuntimePose {
10386
12020
  }
10387
12021
  }
10388
12022
  }
10389
- function applyWorld(w, property, v, mix) {
12023
+ function applyWorld(w, property, v, mix, additive) {
10390
12024
  switch (property) {
10391
12025
  case P_ROTATE: {
10392
- let r = v * RAD - Math.atan2(w[W_C], w[W_A]);
12026
+ let r = additive ? v * RAD : v * RAD - Math.atan2(w[W_C], w[W_A]);
10393
12027
  if (r > RUNTIME_PI) r -= TWO_PI;
10394
12028
  else if (r < -RUNTIME_PI) r += TWO_PI;
10395
12029
  r *= mix;
@@ -10404,10 +12038,10 @@ function applyWorld(w, property, v, mix) {
10404
12038
  return;
10405
12039
  }
10406
12040
  case P_X:
10407
- w[W_X] = w[W_X] + (v - w[W_X]) * mix;
12041
+ w[W_X] = additive ? w[W_X] + v * mix : w[W_X] + (v - w[W_X]) * mix;
10408
12042
  return;
10409
12043
  case P_Y:
10410
- w[W_Y] = w[W_Y] + (v - w[W_Y]) * mix;
12044
+ w[W_Y] = additive ? w[W_Y] + v * mix : w[W_Y] + (v - w[W_Y]) * mix;
10411
12045
  return;
10412
12046
  case P_SCALE_X:
10413
12047
  case P_SCALE_Y: {
@@ -10415,7 +12049,7 @@ function applyWorld(w, property, v, mix) {
10415
12049
  const q = property === P_SCALE_X ? W_C : W_D;
10416
12050
  const s = Math.sqrt(w[p] * w[p] + w[q] * w[q]);
10417
12051
  if (s === 0) return;
10418
- const k = 1 + (v - s) * mix / s;
12052
+ const k = additive ? 1 + (v - 1) * mix : 1 + (v - s) * mix / s;
10419
12053
  w[p] *= k;
10420
12054
  w[q] *= k;
10421
12055
  return;
@@ -10423,8 +12057,8 @@ function applyWorld(w, property, v, mix) {
10423
12057
  default: {
10424
12058
  const yAngle = Math.atan2(w[W_D], w[W_B]);
10425
12059
  const xAngle = Math.atan2(w[W_C], w[W_A]);
10426
- let r = (v + 90) * RAD - (yAngle - xAngle);
10427
- r = r > RUNTIME_PI ? r - TWO_PI : r < -RUNTIME_PI ? r + TWO_PI : r;
12060
+ let r = (v + 90) * RAD - (additive ? 90 * RAD : yAngle - xAngle);
12061
+ if (!additive) r = r > RUNTIME_PI ? r - TWO_PI : r < -RUNTIME_PI ? r + TWO_PI : r;
10428
12062
  const angle = yAngle + r * mix;
10429
12063
  const s = Math.sqrt(w[W_B] * w[W_B] + w[W_D] * w[W_D]);
10430
12064
  w[W_B] = Math.cos(angle) * s;
@@ -10432,6 +12066,43 @@ function applyWorld(w, property, v, mix) {
10432
12066
  }
10433
12067
  }
10434
12068
  }
12069
+ function onLineBefore(p, x1, y1, x2, y2, out, o) {
12070
+ const r = Math.atan2(y2 - y1, x2 - x1);
12071
+ out[o] = x1 + p * Math.cos(r);
12072
+ out[o + 1] = y1 + p * Math.sin(r);
12073
+ out[o + 2] = r;
12074
+ }
12075
+ function onLinePast(p, x0, y0, x1, y1, out, o) {
12076
+ const r = Math.atan2(y1 - y0, x1 - x0);
12077
+ out[o] = x1 + p * Math.cos(r);
12078
+ out[o + 1] = y1 + p * Math.sin(r);
12079
+ out[o + 2] = r;
12080
+ }
12081
+ function onCurve(p, x1, y1, cx1, cy1, cx2, cy2, x2, y2, out, o, wantsTangent) {
12082
+ if (Number.isNaN(p)) {
12083
+ out[o] = x1;
12084
+ out[o + 1] = y1;
12085
+ out[o + 2] = Math.atan2(cy1 - y1, cx1 - x1);
12086
+ return;
12087
+ }
12088
+ const t2 = p * p;
12089
+ const t3 = t2 * p;
12090
+ const u = 1 - p;
12091
+ const u2 = u * u;
12092
+ const u3 = u2 * u;
12093
+ const ut = u * p;
12094
+ const ut3x = ut * 3;
12095
+ const w1 = u * ut3x;
12096
+ const w2 = ut3x * p;
12097
+ const x = x1 * u3 + cx1 * w1 + cx2 * w2 + x2 * t3;
12098
+ const y = y1 * u3 + cy1 * w1 + cy2 * w2 + y2 * t3;
12099
+ out[o] = x;
12100
+ out[o + 1] = y;
12101
+ if (wantsTangent) {
12102
+ if (p < 1e-3) out[o + 2] = Math.atan2(cy1 - y1, cx1 - x1);
12103
+ else out[o + 2] = Math.atan2(y - (y1 * u2 + cy1 * ut * 2 + cy2 * t2), x - (x1 * u2 + cx1 * ut * 2 + cx2 * t2));
12104
+ }
12105
+ }
10435
12106
  function frameSource(doc, parsed) {
10436
12107
  if (doc.pages === null) {
10437
12108
  throw new CoreInputError(
@@ -10695,6 +12366,10 @@ function checkedLiveFrame(frame2, current) {
10695
12366
  if (drawing) liveDrawings.set(lease, drawing);
10696
12367
  return lease;
10697
12368
  }
12369
+ function withLiveClip(metadata, template) {
12370
+ Object.defineProperty(metadata, "clip", { get: () => template?.clip ?? null });
12371
+ return metadata;
12372
+ }
10698
12373
  class RuntimeFrames {
10699
12374
  model;
10700
12375
  pose;
@@ -10703,6 +12378,7 @@ class RuntimeFrames {
10703
12378
  order;
10704
12379
  tint;
10705
12380
  templates;
12381
+ clips;
10706
12382
  colours;
10707
12383
  /** Incremented when a draw refresh changes colour without a pose step. */
10708
12384
  revision = 0;
@@ -10716,6 +12392,11 @@ class RuntimeFrames {
10716
12392
  this.tint = new Float64Array(this.model.slots.length * 4);
10717
12393
  const colours = [];
10718
12394
  const metadata = [];
12395
+ this.clips = this.model.attachments.map((attachment) => attachment.clip === void 0 ? null : {
12396
+ polygon: numbers(this.pose.vertices, attachment.offset, attachment.count),
12397
+ inverse: attachment.clip.inverse,
12398
+ convex: attachment.clip.convex
12399
+ });
10719
12400
  this.templates = this.model.attachments.map((attachment, index) => {
10720
12401
  const identity = attachment;
10721
12402
  if (identity.skin === void 0 || identity.placeholder === void 0) {
@@ -10733,8 +12414,9 @@ class RuntimeFrames {
10733
12414
  if (template.kind === "region") template.corners = vertices;
10734
12415
  else template.vertices = vertices;
10735
12416
  template.tint = numbers(this.tint, attachment.slot * 4, 4);
12417
+ if (slot.dark !== null) template.dark = numbers(this.pose.dark, attachment.slot * 3, 3);
10736
12418
  }
10737
- metadata.push(Object.freeze({
12419
+ const item = {
10738
12420
  slot: slot.name,
10739
12421
  slotIndex: attachment.slot,
10740
12422
  attachment: attachment.name,
@@ -10749,9 +12431,10 @@ class RuntimeFrames {
10749
12431
  count: attachment.count,
10750
12432
  uvs: Object.freeze(template?.uvs ?? []),
10751
12433
  triangles: Object.freeze(template?.kind === "mesh" ? template.triangles : []),
10752
- dark: template?.dark ? Object.freeze(template.dark) : null,
12434
+ dark: template?.dark ?? null,
10753
12435
  clip: null
10754
- }));
12436
+ };
12437
+ metadata.push(Object.freeze(this.pose.clipping === null ? item : withLiveClip(item, template)));
10755
12438
  return template;
10756
12439
  });
10757
12440
  this.colours = colours;
@@ -10779,32 +12462,76 @@ class RuntimeFrames {
10779
12462
  }
10780
12463
  if (colorChanged) this.revision++;
10781
12464
  let count = 0;
10782
- for (let si = 0; si < this.pose.shown.length; si++) {
12465
+ let clip = null;
12466
+ let end = null;
12467
+ for (let z = 0; z < this.pose.drawOrder.length; z++) {
12468
+ const si = this.pose.drawOrder[z];
10783
12469
  const ai = this.pose.shown[si];
12470
+ const slot = this.model.slots[si];
12471
+ const attachment = ai < 0 ? null : this.model.attachments[ai];
12472
+ if (attachment?.clip !== void 0) {
12473
+ if (clip !== null && end === slot.name) clip = null;
12474
+ if (clip === null && this.model.active[attachment.bone] && draws(slot.name)) {
12475
+ clip = this.clips[ai];
12476
+ end = attachment.clip.end;
12477
+ }
12478
+ continue;
12479
+ }
10784
12480
  const template = ai < 0 ? null : this.templates[ai];
10785
- if (!template || !this.model.active[this.model.slots[si].bone]) continue;
10786
- const offset = si * 4;
10787
- const colour = this.colours[ai];
10788
- for (let c = 0; c < 4; c++) this.tint[offset + c] = this.pose.light[offset + c] * colour[c] * color[c];
10789
- if (!draws(template.slot)) continue;
10790
- this.order[count] = ai;
10791
- this.renderFrame.order[count++] = template;
12481
+ if (template && this.model.active[slot.bone]) {
12482
+ template.z = z;
12483
+ if (clip === null) {
12484
+ if (template.clip !== void 0) delete template.clip;
12485
+ } else template.clip = clip;
12486
+ const offset = si * 4;
12487
+ const colour = this.colours[ai];
12488
+ for (let c = 0; c < 4; c++) this.tint[offset + c] = this.pose.light[offset + c] * colour[c] * color[c];
12489
+ if (draws(template.slot)) {
12490
+ this.order[count] = ai;
12491
+ this.renderFrame.order[count++] = template;
12492
+ }
12493
+ }
12494
+ if (clip !== null && end === slot.name) clip = null;
10792
12495
  }
10793
12496
  this.renderFrame.order.length = count;
10794
12497
  this.drawn = count;
10795
12498
  this.epoch = generation;
10796
12499
  }
10797
12500
  snapshot(draws) {
10798
- const selected = [];
10799
- for (let si = 0; si < this.pose.shown.length; si++) {
12501
+ const order = [];
12502
+ const copies = /* @__PURE__ */ new Map();
12503
+ let clip = null;
12504
+ let end = null;
12505
+ for (let z = 0; z < this.pose.drawOrder.length; z++) {
12506
+ const si = this.pose.drawOrder[z];
10800
12507
  const ai = this.pose.shown[si];
12508
+ const attachment = ai < 0 ? null : this.model.attachments[ai];
12509
+ const name = this.model.slots[si].name;
12510
+ if (attachment?.clip !== void 0) {
12511
+ if (clip !== null && end === name) clip = null;
12512
+ if (clip === null && this.model.active[attachment.bone] && (draws === void 0 || draws(name))) {
12513
+ clip = this.clips[ai];
12514
+ end = attachment.clip.end;
12515
+ }
12516
+ continue;
12517
+ }
10801
12518
  const slot = ai < 0 ? null : this.templates[ai];
10802
- if (slot && this.model.active[this.model.slots[si].bone] && (draws === void 0 || draws(slot.slot))) selected.push(slot);
12519
+ if (slot && this.model.active[this.model.slots[si].bone] && (draws === void 0 || draws(name))) {
12520
+ const common = { ...slot, z, tint: slot.tint.slice() };
12521
+ if (slot.dark) common.dark = slot.dark.slice();
12522
+ delete common.clip;
12523
+ if (clip !== null) {
12524
+ let copy = copies.get(clip);
12525
+ if (!copy) {
12526
+ copy = { ...clip, polygon: clip.polygon.slice() };
12527
+ copies.set(clip, copy);
12528
+ }
12529
+ common.clip = copy;
12530
+ }
12531
+ order.push(slot.kind === "region" ? { ...common, kind: "region", region: slot.region, corners: slot.corners.slice(), page: slot.page, pma: slot.pma, uvs: slot.uvs } : { ...common, kind: "mesh", vertices: slot.vertices.slice(), page: slot.page, pma: slot.pma, uvs: slot.uvs, triangles: slot.triangles, sequence: slot.sequence });
12532
+ }
12533
+ if (clip !== null && end === name) clip = null;
10803
12534
  }
10804
- const order = selected.map((slot) => {
10805
- const common = { ...slot, tint: slot.tint.slice() };
10806
- return slot.kind === "region" ? { ...common, kind: "region", region: slot.region, corners: slot.corners.slice(), page: slot.page, pma: slot.pma, uvs: slot.uvs } : { ...common, kind: "mesh", vertices: slot.vertices.slice(), page: slot.page, pma: slot.pma, uvs: slot.uvs, triangles: slot.triangles, sequence: slot.sequence };
10807
- });
10808
12535
  return { order };
10809
12536
  }
10810
12537
  }
@@ -11273,10 +13000,10 @@ class RigPlayer {
11273
13000
  if (mode !== "dom") throw new Error(`mode must be 'dom' or 'canvas', got ${String(mode)}`);
11274
13001
  if (this.assets.regions === "none") {
11275
13002
  throw new Error(
11276
- "these assets were loaded with regions: 'none' (no region bitmaps, for the canvas mode only); the DOM mode needs a load with regions 'blob' or 'data'"
13003
+ "these assets were loaded with regions: 'none' (no region bitmaps, for the canvas mode only); the DOM mode needs a load with regions 'atlas', 'blob' or 'data'"
11277
13004
  );
11278
13005
  }
11279
- const renderer = new DomRenderer(this.root, this.assets.regionImages, this.assets.pageImages);
13006
+ const renderer = new DomRenderer(this.root, this.assets.regionImages, this.assets.pageImages, this.assets.atlasRegions);
11280
13007
  if (this.meshBackend) renderer.meshBackend = this.meshBackend;
11281
13008
  return renderer;
11282
13009
  }
@@ -11536,6 +13263,70 @@ class RigPlayer {
11536
13263
  if (x !== 0 || y !== 0) live.physicsTranslate(x, y);
11537
13264
  if (motion.rotation) live.physicsRotate(0, 0, motion.rotation);
11538
13265
  }
13266
+ /**
13267
+ * Extra apparent motion for selected physics, synchronously before evaluation.
13268
+ * World distances are X right/Y up; rotation is XY roll around an explicit
13269
+ * world pivot, not yaw/pitch. Does not move bones or draw. A step(0), reset,
13270
+ * seek/skin replay or track promotion may consume/discard external history.
13271
+ * Writes outside the selection are untouched; solver dependencies still apply.
13272
+ */
13273
+ physicsMoved(scope, motion) {
13274
+ const object = (value, fields, where) => {
13275
+ if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error(`physicsMoved: ${where} must be an object`);
13276
+ for (const key of Reflect.ownKeys(value)) if (typeof key !== "string" || !fields.includes(key)) throw new Error(`physicsMoved: unknown ${where} field ${String(key)}`);
13277
+ return value;
13278
+ };
13279
+ const finite2 = (value, field) => {
13280
+ if (typeof value !== "number" || !Number.isFinite(value)) throw new Error(`physicsMoved: ${field} must be a finite number, got ${String(value)}`);
13281
+ return value;
13282
+ };
13283
+ const selection = object(scope, ["bone", "subtree", "constraints"], "scope");
13284
+ const keys = Object.keys(selection);
13285
+ if (keys.length !== 1) throw new Error("physicsMoved: scope must name exactly one of bone, subtree, constraints");
13286
+ const kind = keys[0];
13287
+ let names;
13288
+ if (kind === "constraints") {
13289
+ if (!Array.isArray(selection.constraints)) throw new Error("physicsMoved: constraints must be an array of exact physics names");
13290
+ names = [];
13291
+ for (const name of selection.constraints) {
13292
+ if (typeof name !== "string") throw new Error(`physicsMoved: constraint name ${String(name)} must be a string`);
13293
+ const c = this.view.constraints.find((c2) => c2.kind === "physics" && c2.name === name) ?? this.view.constraints.find((c2) => c2.name === name);
13294
+ if (c === void 0) throw new Error(`physicsMoved: no constraint ${JSON.stringify(name)}`);
13295
+ if (c.kind !== "physics") throw new Error(`physicsMoved: constraint ${JSON.stringify(name)} is ${c.kind}, not physics`);
13296
+ if (!names.includes(name)) names.push(name);
13297
+ }
13298
+ } else {
13299
+ const name = selection[kind];
13300
+ if (typeof name !== "string" || !this.view.bones.some((b) => b.name === name)) throw new Error(`physicsMoved: no bone ${JSON.stringify(name)}`);
13301
+ const parents = new Map(this.view.bones.map((b) => [b.name, b.parent]));
13302
+ const includes = (bone2) => {
13303
+ if (kind === "bone") return bone2 === name;
13304
+ for (let at = bone2; at !== void 0; at = parents.get(at)) if (at === name) return true;
13305
+ return false;
13306
+ };
13307
+ names = this.view.constraints.filter((c) => c.kind === "physics" && includes(c.record?.bone)).map((c) => c.name);
13308
+ }
13309
+ const input = object(motion, ["translation", "rotation"], "motion");
13310
+ if (Object.keys(input).length !== 1) throw new Error("physicsMoved: motion must name exactly one of translation, rotation");
13311
+ let x, y, degrees;
13312
+ if ("translation" in input) {
13313
+ const t = object(input.translation, ["x", "y"], "translation");
13314
+ x = finite2(t.x, "translation.x");
13315
+ y = finite2(t.y, "translation.y");
13316
+ } else {
13317
+ const r = object(input.rotation, ["degrees", "pivot"], "rotation");
13318
+ degrees = finite2(r.degrees, "rotation.degrees");
13319
+ if (!Array.isArray(r.pivot) || r.pivot.length !== 2) throw new Error("physicsMoved: rotation.pivot must be two finite world coordinates");
13320
+ x = finite2(r.pivot[0], "rotation.pivot[0]");
13321
+ y = finite2(r.pivot[1], "rotation.pivot[1]");
13322
+ }
13323
+ if (names.length === 0 || (degrees === void 0 ? x === 0 && y === 0 : degrees === 0)) return;
13324
+ const live = this.stepper();
13325
+ if (live === null) return;
13326
+ if (degrees === void 0) live.physicsTranslate(x, y, names);
13327
+ else live.physicsRotate(x, y, degrees, names);
13328
+ this.clearSeekCache();
13329
+ }
11539
13330
  /** Stops stepping; `frame` keeps drawing the pose it holds. */
11540
13331
  pause() {
11541
13332
  this.paused = true;
@@ -12416,6 +14207,11 @@ class RigPlayer {
12416
14207
  this.poserLabel = "oracle: explicitly selected";
12417
14208
  return;
12418
14209
  }
14210
+ const physics = this.view.constraints.find((constraint) => constraint.kind === "physics");
14211
+ if (physics) {
14212
+ this.poserLabel = `oracle: physics constraint "${physics.name}" requires player clock, seek and movement integration`;
14213
+ return;
14214
+ }
12419
14215
  try {
12420
14216
  this.runtimeModel = compileRuntime(this.view);
12421
14217
  this.poserLabel = this.trackMode ? "oracle: track composition" : this.adjustFn !== null ? "oracle: local adjustment" : "runtime";
@@ -12741,6 +14537,7 @@ export {
12741
14537
  CanvasRenderer,
12742
14538
  DomRenderer,
12743
14539
  RigPlayer,
14540
+ createAtlasRegions,
12744
14541
  loadModel,
12745
14542
  readSetupMesh,
12746
14543
  revokeRegions,