@rig-c/play 1.20.0 → 1.21.1

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)`);
@@ -9030,26 +9045,30 @@ function compileCurve(keys, channels) {
9030
9045
  for (let i = 0; i < n; i++) {
9031
9046
  const a = keys[i];
9032
9047
  times2[i] = a.time;
9033
- for (let c = 0; c < channels; c++) values2[i * channels + c] = a.values[c];
9034
- 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";
9035
9051
  kinds[i] = curve === "linear" ? CURVE_LINEAR : curve === "stepped" ? CURVE_STEPPED : CURVE_BEZIER;
9036
9052
  if (typeof curve === "string" || i + 1 >= n) continue;
9037
9053
  const b = keys[i + 1];
9054
+ const aStated = a.stated;
9055
+ const bStated = b.stated;
9056
+ const bValues = b.values;
9038
9057
  for (let c = 0; c < channels; c++) {
9039
- 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]);
9040
9059
  const o = (i * channels + c) * POLYLINE;
9041
9060
  polylines[o] = a.time;
9042
- polylines[o + 1] = a.values[c];
9061
+ polylines[o + 1] = av[c];
9043
9062
  for (let q = 0; q < inner.length; q++) polylines[o + 2 + q] = inner[q];
9044
9063
  polylines[o + POLYLINE - 2] = b.time;
9045
- polylines[o + POLYLINE - 1] = b.values[c];
9064
+ polylines[o + POLYLINE - 1] = bValues[c];
9046
9065
  }
9047
9066
  }
9048
9067
  return { keys: n, channels, times: times2, values: values2, kinds, polylines };
9049
9068
  }
9050
9069
  function keyAt(curve, cursors, slot, t) {
9051
9070
  const times2 = curve.times;
9052
- let i = cursors[slot];
9071
+ let i = Math.min(cursors[slot], curve.keys - 1);
9053
9072
  while (i >= 0 && times2[i] > t) i--;
9054
9073
  while (i + 1 < curve.keys && times2[i + 1] <= t) i++;
9055
9074
  cursors[slot] = i;
@@ -9104,7 +9123,7 @@ function deformFraction(curve, i, t) {
9104
9123
  if (q === POLYLINE - 2) return y0 + (1 - y0) * (t - x0) / (x1 - x0);
9105
9124
  return y0 + (t - x0) / (x1 - x0) * (y1 - y0);
9106
9125
  }
9107
- const RUNTIME_STAGE = 2;
9126
+ const RUNTIME_STAGE = 9;
9108
9127
  function missingText(m) {
9109
9128
  return `${m.feature}: ${m.detail}${m.animation === null ? "" : ` (animation "${m.animation}")`}`;
9110
9129
  }
@@ -9147,6 +9166,15 @@ const SCALE_Y_VOLUME = 2;
9147
9166
  const IK_BEND_POSITIVE = 1;
9148
9167
  const IK_COMPRESS = 2;
9149
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;
9150
9178
  const RAD = RUNTIME_PI / 180;
9151
9179
  const BONE_KINDS = {
9152
9180
  rotate: [[L_ROTATION, 0]],
@@ -9160,7 +9188,7 @@ const BONE_KINDS = {
9160
9188
  shearx: [[L_SHEAR_X, 0]],
9161
9189
  sheary: [[L_SHEAR_Y, 0]]
9162
9190
  };
9163
- 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] };
9164
9192
  class Refusals {
9165
9193
  constructor(admitted) {
9166
9194
  this.admitted = admitted;
@@ -9182,7 +9210,7 @@ class Refusals {
9182
9210
  function compileRuntime(doc, options = {}, plant = {}) {
9183
9211
  const refusals = new Refusals(new Set(plant.admit ?? []));
9184
9212
  const ignoreEvents = (options.ignore ?? []).includes("events");
9185
- const absentWhy = constraintsAbsentWhy(doc);
9213
+ const absentWhy = constraintsAbsentWhy(doc) ?? pathAnimationsWhy(doc) ?? steppedPreviousPassWhy(doc);
9186
9214
  if (absentWhy !== null) refusals.note("oracle-absent", `the oracle leaves the bones out: ${absentWhy}`);
9187
9215
  const nb = doc.bones.length;
9188
9216
  const boneIndex2 = new Map(doc.bones.map((b, i) => [b.name, i]));
@@ -9191,6 +9219,12 @@ function compileRuntime(doc, options = {}, plant = {}) {
9191
9219
  if (i === void 0) throw new RuntimeInputError(`bone "${name}" is not a bone of this document (readModel refuses it first)`);
9192
9220
  return i;
9193
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
+ };
9194
9228
  const active = activeBones(doc);
9195
9229
  const parents = new Int32Array(nb);
9196
9230
  const modes = new Uint8Array(nb);
@@ -9225,27 +9259,35 @@ function compileRuntime(doc, options = {}, plant = {}) {
9225
9259
  }
9226
9260
  subtreeStart[nb] = flat.length;
9227
9261
  const constraints = [];
9228
- 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}"`);
9229
- for (const r of constraintRecords(doc)) {
9230
- 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) {
9231
9269
  const applied = constraintInactiveWhy(r, active) === null;
9232
- const bones = Int32Array.from(r.bones.map(indexOf));
9233
- 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));
9234
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`);
9235
9281
  if (r.kind === "ik") constraints.push(compileIk(r, index, applied, bones, indexOf));
9236
- else {
9237
- 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 ")}`);
9238
- if (r.additive) refusals.note("additive-transform", `transform constraint "${r.name}" is additive`);
9239
- const t = compileTransform(r, index, applied, bones, indexOf);
9240
- if (applied) {
9241
- for (let k = 0; k < bones.length; k++) {
9242
- const m = modes[bones[k]];
9243
- 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`);
9244
- }
9245
- }
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);
9246
9287
  constraints.push(t);
9247
9288
  }
9248
9289
  }
9290
+ const hasClipping = doc.skins.some((s) => Object.values(s.attachments).some((table) => Object.values(table).some((at) => at.geometry?.kind === "clipping")));
9249
9291
  const attachments = [];
9250
9292
  let vertexCount = 0;
9251
9293
  let deformCount = 0;
@@ -9272,8 +9314,9 @@ function compileRuntime(doc, options = {}, plant = {}) {
9272
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 };
9273
9315
  let made = null;
9274
9316
  let missing = null;
9275
- if (g?.kind === "clipping") missing = ["clipping", `slot "${slot.name}" shows clipping attachment "${row.name}"`];
9276
- 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") {
9277
9320
  const r = g.region;
9278
9321
  if (r.atlas === null) missing = ["null-atlas-region", `slot "${slot.name}" shows region "${row.name}", whose atlas rectangle the model states as null`];
9279
9322
  else {
@@ -9307,8 +9350,18 @@ function compileRuntime(doc, options = {}, plant = {}) {
9307
9350
  const source = g.kind === "mesh" ? g.vertices : sourceOf(g.skin, g.slot, g.source);
9308
9351
  if (typeof source === "string") throw new RuntimeInputError(`slot "${slot.name}": ${source} (readModel refuses it first)`);
9309
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);
9310
9355
  }
9311
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
+ }
9312
9365
  attachments.push(made);
9313
9366
  vertexCount += made.count;
9314
9367
  deformCount += made.deformCount;
@@ -9328,7 +9381,21 @@ function compileRuntime(doc, options = {}, plant = {}) {
9328
9381
  blend: readBlend(slot)
9329
9382
  };
9330
9383
  });
9331
- 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
+ }
9332
9399
  const animations = /* @__PURE__ */ new Map();
9333
9400
  const refused = [];
9334
9401
  let cursorMax = 0;
@@ -9341,11 +9408,25 @@ function compileRuntime(doc, options = {}, plant = {}) {
9341
9408
  cursorMax = Math.max(cursorMax, compiled.cursors);
9342
9409
  }
9343
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
+ }
9344
9416
  if (refusals.model.length > 0) throw new RuntimeRefusal(refusals.model);
9345
9417
  const names = doc.animations.map((a) => a.name);
9346
9418
  return {
9419
+ omitPhysics: plant.omitPhysics === true,
9420
+ sliderAsTrack: plant.sliderAsTrack === true,
9347
9421
  omitDeform: plant.omitDeform === true,
9348
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,
9349
9430
  deformCount,
9350
9431
  eventWrapOffByOne: plant.eventWrapOffByOne === true,
9351
9432
  restoreEventsFromStart: plant.restoreEventsFromStart === true,
@@ -9404,6 +9485,10 @@ function meshAttachment(at, vertices, indexOf, rewrite, bones) {
9404
9485
  }
9405
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) };
9406
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
+ }
9407
9492
  function compileIk(r, index, applied, bones, indexOf) {
9408
9493
  return {
9409
9494
  kind: "ik",
@@ -9421,6 +9506,34 @@ function compileIk(r, index, applied, bones, indexOf) {
9421
9506
  function ikFlags(bendPositive, compress, stretch) {
9422
9507
  return (bendPositive ? IK_BEND_POSITIVE : 0) | (compress ? IK_COMPRESS : 0) | (stretch ? IK_STRETCH : 0);
9423
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
+ }
9424
9537
  function compileTransform(r, index, applied, bones, indexOf) {
9425
9538
  const fromProperty = [];
9426
9539
  const fromOffset = [];
@@ -9459,7 +9572,10 @@ function compileTransform(r, index, applied, bones, indexOf) {
9459
9572
  toMax: Float64Array.from(toMax),
9460
9573
  offsets: Float64Array.from(TRANSFORM_PROPERTIES.map((p) => r.offsets[p])),
9461
9574
  mixes: Float64Array.from(TRANSFORM_PROPERTIES.map((p) => r.mixes[p])),
9462
- clamp: r.clamp
9575
+ clamp: r.clamp,
9576
+ localSource: r.localSource,
9577
+ localTarget: r.localTarget,
9578
+ additive: r.additive
9463
9579
  };
9464
9580
  }
9465
9581
  function compileAnimation(anim, refusals, ignoreEvents, boneIndex2, slotIndex, slots, constraints, doc, resolve) {
@@ -9469,21 +9585,52 @@ function compileAnimation(anim, refusals, ignoreEvents, boneIndex2, slotIndex, s
9469
9585
  const deform = tl.attachments.filter((a) => a.deform !== null);
9470
9586
  const sequence = tl.attachments.filter((a) => a.sequence !== null);
9471
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);
9472
- 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
+ }
9473
9609
  const lastBy = (targets) => new Map(targets.map((t) => [t.name, t]));
9474
9610
  const bones = [];
9475
- 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);
9476
9617
  const bone2 = boneIndex2.get(target.name);
9477
- if (bone2 === void 0) continue;
9618
+ if (bone2 === void 0) return keyed;
9478
9619
  for (const t of target.timelines) {
9479
9620
  if (t.kind === "inherit") {
9480
- 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
+ }
9481
9625
  continue;
9482
9626
  }
9483
9627
  const spec = BONE_KINDS[t.kind];
9484
- 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])) });
9485
9629
  }
9486
- }
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)) : [];
9487
9634
  const slotTimelines = [];
9488
9635
  const keyedSlots = [...lastBy(tl.slots).values()].flatMap((target) => {
9489
9636
  const si = slotIndex.get(target.name);
@@ -9495,8 +9642,6 @@ function compileAnimation(anim, refusals, ignoreEvents, boneIndex2, slotIndex, s
9495
9642
  if (t.kind === "attachment") {
9496
9643
  const ids = Int32Array.from(t.keys.map((k) => resolve(si, k.name ?? null)));
9497
9644
  slotTimelines.push({ slot: si, curve: compileCurve(t.keys, 0), cursor: cursors++, attachments: ids, channels: null });
9498
- } else if (t.kind === "rgba2" || t.kind === "rgb2") {
9499
- refusals.note("dark-colour-timeline", `slot "${target.name}" keys an ${t.kind} timeline`, name);
9500
9645
  } else {
9501
9646
  const channels = COLOUR_CHANNELS[t.kind];
9502
9647
  slotTimelines.push({ slot: si, curve: compileCurve(t.keys, channels.length), cursor: cursors++, attachments: null, channels: Int32Array.from(channels) });
@@ -9509,12 +9654,37 @@ function compileAnimation(anim, refusals, ignoreEvents, boneIndex2, slotIndex, s
9509
9654
  const keyed = anim.constraints.ik.find((x) => x.name === c.name);
9510
9655
  if (keyed === void 0) continue;
9511
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))) };
9512
- } else {
9657
+ } else if (c.kind === "transform") {
9513
9658
  const keyed = anim.constraints.transform.find((x) => x.name === c.name);
9514
9659
  if (keyed === void 0) continue;
9515
9660
  constraintTimelines[c.index] = { constraint: c.index, curve: compileCurve(keyed.keys, TRANSFORM_PROPERTIES.length), cursor: cursors++, flags: null };
9516
9661
  }
9517
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
+ }));
9518
9688
  const deforms = deform.map((a) => {
9519
9689
  const g = doc.skins.find((s) => s.name === a.skin)?.attachments[a.slot]?.[a.attachment]?.geometry;
9520
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)`);
@@ -9522,15 +9692,613 @@ function compileAnimation(anim, refusals, ignoreEvents, boneIndex2, slotIndex, s
9522
9692
  const arrays = keys.map((k) => heldArray(g.vertices, k));
9523
9693
  return { identity: `${a.skin}/${a.slot}/${a.attachment}`, cursor: cursors++, curve: compileDeformCurve(keys), count: arrays[0].length, held: Float64Array.from(arrays.flat()) };
9524
9694
  });
9525
- 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 }))) };
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 }))) };
9696
+ }
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;
9759
+ }
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
+ }
9847
+ }
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;
9854
+ }
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
+ }
10040
+ }
10041
+ }
10042
+ }
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
+ }
10095
+ }
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;
9526
10292
  }
9527
10293
  const DEG = RUNTIME_DEG;
9528
10294
  const TWO_PI = 2 * RUNTIME_PI;
10295
+ const PATH_EPSILON = 1e-5;
9529
10296
  const P_ROTATE = 0;
9530
10297
  const P_X = 1;
9531
10298
  const P_Y = 2;
9532
10299
  const P_SCALE_X = 3;
9533
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]);
9534
10302
  function wrap180(r) {
9535
10303
  return r > 180 ? r - 360 : r <= -180 ? r + 360 : r;
9536
10304
  }
@@ -9539,21 +10307,39 @@ function volumeScaleY(scaleY, s) {
9539
10307
  }
9540
10308
  const clamp01 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
9541
10309
  class RuntimePose {
9542
- constructor(model) {
10310
+ constructor(model, options = {}) {
9543
10311
  this.trackTime = 0;
9544
10312
  this.time = 0;
9545
10313
  this.fired = [];
9546
10314
  this.eventTime = -1;
9547
10315
  this.current = null;
10316
+ this.pathSegmentEnds = new Float64Array(10);
9548
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]);
9549
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);
9550
10332
  const nb = model.boneNames.length;
9551
10333
  const ns = model.slots.length;
9552
10334
  const nc = model.constraints.length;
10335
+ this.physicsScope = new Uint8Array(nc);
9553
10336
  this.locals = new Float64Array(nb * LOCALS);
9554
10337
  this.world = new Float64Array(nb * WORLDS);
9555
10338
  this.light = new Float64Array(ns * 4);
10339
+ this.dark = new Float64Array(ns * 3);
9556
10340
  this.shown = new Int32Array(ns);
10341
+ this.drawOrder = new Int32Array(ns);
10342
+ for (let s = 0; s < ns; s++) this.drawOrder[s] = s;
9557
10343
  this.vertices = new Float64Array(model.vertexCount);
9558
10344
  this.deform = new Float64Array(model.deformCount);
9559
10345
  this.deformed = new Uint8Array(model.attachments.length);
@@ -9562,6 +10348,16 @@ class RuntimePose {
9562
10348
  this.ikSoftness = new Float64Array(nc);
9563
10349
  this.ikFlags = new Uint8Array(nc);
9564
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);
9565
10361
  this.changed = new Int32Array(model.scratch);
9566
10362
  this.inWorld = new Int32Array(model.scratch);
9567
10363
  this.changedMark = new Uint8Array(nb);
@@ -9576,6 +10372,10 @@ class RuntimePose {
9576
10372
  /** The animation this pose plays (a covered one: `model.animation` throws a `RuntimeRefusal` for a refused one), from track time 0. */
9577
10373
  setAnimation(name) {
9578
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);
9579
10379
  this.cursors.fill(-1);
9580
10380
  this.trackTime = 0;
9581
10381
  this.time = 0;
@@ -9596,38 +10396,57 @@ class RuntimePose {
9596
10396
  this.apply(animationTime);
9597
10397
  }
9598
10398
  /** The setup pose: every constraint applied with its own values, every slot as it is set up. */
9599
- setupPose() {
10399
+ setupPose(physics = "none") {
10400
+ this.physicsPhase = physics;
9600
10401
  this.current = null;
9601
10402
  this.time = 0;
9602
10403
  this.eventTime = -1;
9603
10404
  this.fired.length = 0;
10405
+ this.physicsValues(null, 0, false);
9604
10406
  this.locals.set(this.model.setup);
9605
10407
  this.hierarchy();
9606
- this.constraintValues(null, 0);
9607
- this.solveConstraints();
9608
10408
  this.slotValues(null, 0);
9609
10409
  this.deformValues(null, 0);
10410
+ this.constraintValues(null, 0);
10411
+ this.solveConstraints();
10412
+ this.sliderAttachments();
9610
10413
  }
9611
10414
  /** The animation posed at its time `t`, from the setup pose (the header). */
9612
- apply(t) {
10415
+ apply(t, physics = "none", delta = 0) {
9613
10416
  const anim = this.current;
9614
10417
  if (anim === null) throw new RuntimeInputError("apply: no animation is set (setAnimation first)");
9615
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
+ }
9616
10426
  this.collectEvents(anim, t);
9617
10427
  this.time = t;
9618
10428
  this.boneTimelines(anim, t);
9619
10429
  this.hierarchy();
9620
- this.constraintValues(anim, t);
9621
- this.solveConstraints();
9622
10430
  this.slotValues(anim, t);
9623
10431
  this.deformValues(anim, t);
10432
+ this.constraintValues(anim, t);
10433
+ this.solveConstraints();
10434
+ this.sliderAttachments();
9624
10435
  }
9625
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). */
9626
10437
  step(dt) {
9627
10438
  const anim = this.current;
9628
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`);
9629
10448
  this.trackTime += dt;
9630
- this.apply(anim.duration === 0 ? 0 : this.trackTime % anim.duration);
10449
+ this.apply(anim.duration === 0 ? 0 : this.trackTime % anim.duration, "update", moved);
9631
10450
  }
9632
10451
  /** The oracle interval (last,t], tail then head on a wrap; never copies a payload. */
9633
10452
  collectEvents(anim, t) {
@@ -9824,20 +10643,738 @@ class RuntimePose {
9824
10643
  this.ikSoftness[k] = c.softness + (channelValue(tl.curve, i, 1, t) - c.softness) * 1;
9825
10644
  this.ikFlags[k] = tl.flags[i];
9826
10645
  }
9827
- } else {
10646
+ } else if (c.kind === "transform") {
9828
10647
  const o = k * 6;
9829
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;
9830
10695
  }
9831
10696
  }
9832
10697
  }
9833
10698
  /** Every applied constraint in the document's order, each followed by the update order's repose (`applyConstraints`). */
9834
10699
  solveConstraints() {
9835
10700
  const cs = this.model.constraints;
10701
+ const hasPath = this.model.hasPath;
9836
10702
  for (let k = 0; k < cs.length; k++) {
10703
+ if (hasPath) this.snapSlotBones(k, false);
9837
10704
  const c = cs[k];
9838
10705
  if (!c.applied) continue;
9839
10706
  if (c.kind === "ik") this.solveIk(c, k);
9840
- 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
+ }
9841
11378
  }
9842
11379
  }
9843
11380
  solveIk(c, k) {
@@ -10094,7 +11631,10 @@ class RuntimePose {
10094
11631
  L[cl + L_ROTATION] = childRotation + wrap180(childTarget - childRotation) * mix;
10095
11632
  return true;
10096
11633
  }
10097
- /** 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
+ */
10098
11638
  solveTransform(c, k) {
10099
11639
  const W = this.world;
10100
11640
  const w = this.w;
@@ -10124,24 +11664,31 @@ class RuntimePose {
10124
11664
  v = v < lo ? lo : v > hi ? hi : v;
10125
11665
  }
10126
11666
  applied = true;
10127
- 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);
10128
11672
  }
10129
11673
  }
10130
11674
  if (!applied) continue;
10131
- W[o + W_A] = w[W_A];
10132
- W[o + W_B] = w[W_B];
10133
- W[o + W_C] = w[W_C];
10134
- W[o + W_D] = w[W_D];
10135
- W[o + W_X] = w[W_X];
10136
- 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
+ }
10137
11683
  this.changed[n] = bi;
10138
11684
  this.inWorld[n] = bi;
10139
11685
  n++;
10140
11686
  }
10141
- if (n > 0) this.repose(n, n);
11687
+ if (n > 0) this.repose(n, c.localTarget ? 0 : n);
10142
11688
  }
10143
- /** 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). */
10144
11690
  sourceValue(c, property) {
11691
+ if (c.localSource) return this.locals[c.source * LOCALS + PROPERTY_LOCAL[property]] + c.offsets[property];
10145
11692
  const W = this.world;
10146
11693
  const s = c.source * WORLDS;
10147
11694
  const o = c.offsets;
@@ -10221,7 +11768,10 @@ class RuntimePose {
10221
11768
  if (p >= 0 && below[p] === 1 && (active[p] === 1 || this.changedMark[p] === 1)) below[b] = 1;
10222
11769
  if (below[b] === 1 && this.inWorldMark[b] === 0) this.poseBone(b, false);
10223
11770
  }
10224
- /** 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
+ */
10225
11775
  localFromWorld(bi) {
10226
11776
  const m = this.model;
10227
11777
  const W = this.world;
@@ -10255,6 +11805,21 @@ class RuntimePose {
10255
11805
  const wb = W[w + W_B];
10256
11806
  const wc = W[w + W_C];
10257
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
+ }
10258
11823
  if (mode === MODE_ONLY_TRANSLATION) {
10259
11824
  this.decompose(l, wa, wc, wb, wd);
10260
11825
  return;
@@ -10273,7 +11838,7 @@ class RuntimePose {
10273
11838
  this.decompose(l, ia * wa - ib * wc, id * wc - ic * wa, ia * wb - ib * wd, id * wd - ic * wb);
10274
11839
  }
10275
11840
  /** A local matrix with columns `(la, lc)` and `(lb, ld)` as rotation, scales and shear (`decompose`). */
10276
- decompose(l, la, lc, lb, ld) {
11841
+ decompose(l, la, lc, lb, ld, shearX = false) {
10277
11842
  const L = this.locals;
10278
11843
  const sx = Math.sqrt(la * la + lc * lc);
10279
11844
  const yLength = Math.sqrt(lb * lb + ld * ld);
@@ -10287,11 +11852,11 @@ class RuntimePose {
10287
11852
  const xAngle = Math.atan2(lc, la) * DEG;
10288
11853
  const det = la * ld - lb * lc;
10289
11854
  const yAngle = Math.atan2(ld, lb) * DEG;
10290
- L[l + L_ROTATION] = xAngle;
11855
+ L[l + (shearX ? L_SHEAR_X : L_ROTATION)] = xAngle;
10291
11856
  L[l + L_SCALE_X] = sx;
10292
11857
  L[l + L_SCALE_Y] = det < 0 ? -yLength : yLength;
10293
11858
  const right = det < 0 ? -90 : 90;
10294
- let shear = yAngle - (xAngle + right);
11859
+ let shear = yAngle - ((shearX ? 0 : xAngle) + right);
10295
11860
  shear = shear > 180 ? shear - 360 : shear < -180 ? shear + 360 : shear;
10296
11861
  L[l + L_SHEAR_Y] = yLength > 1e-5 ? shear : 0;
10297
11862
  }
@@ -10302,16 +11867,32 @@ class RuntimePose {
10302
11867
  slotValues(anim, t) {
10303
11868
  const slots = this.model.slots;
10304
11869
  const light = this.light;
11870
+ const dark = this.dark;
10305
11871
  for (let s = 0; s < slots.length; s++) {
10306
11872
  const slot = slots[s];
10307
11873
  this.shown[s] = slot.setupAttachment;
11874
+ this.drawOrder[s] = s;
10308
11875
  const o = s * 4;
10309
11876
  light[o] = slot.light[0];
10310
11877
  light[o + 1] = slot.light[1];
10311
11878
  light[o + 2] = slot.light[2];
10312
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
+ }
10313
11887
  }
10314
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
+ }
10315
11896
  const timelines = anim.slots;
10316
11897
  for (let k = 0; k < timelines.length; k++) {
10317
11898
  const tl = timelines[k];
@@ -10322,11 +11903,18 @@ class RuntimePose {
10322
11903
  continue;
10323
11904
  }
10324
11905
  const channels = tl.channels;
10325
- const setup = slots[tl.slot].light;
11906
+ const slot = slots[tl.slot];
11907
+ const setup = slot.light;
11908
+ const setupDark = slot.dark;
10326
11909
  const o = tl.slot * 4;
11910
+ const d = tl.slot * 3;
10327
11911
  for (let c = 0; c < channels.length; c++) {
10328
11912
  const at = channels[c];
10329
- 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
+ }
10330
11918
  }
10331
11919
  }
10332
11920
  }
@@ -10355,6 +11943,13 @@ class RuntimePose {
10355
11943
  }
10356
11944
  }
10357
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
+ }
10358
11953
  /** Every shown attachment's world vertices into `vertices` at its offset (`regionCorners`, `worldVertices`). */
10359
11954
  computeVertices() {
10360
11955
  const m = this.model;
@@ -10425,10 +12020,10 @@ class RuntimePose {
10425
12020
  }
10426
12021
  }
10427
12022
  }
10428
- function applyWorld(w, property, v, mix) {
12023
+ function applyWorld(w, property, v, mix, additive) {
10429
12024
  switch (property) {
10430
12025
  case P_ROTATE: {
10431
- 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]);
10432
12027
  if (r > RUNTIME_PI) r -= TWO_PI;
10433
12028
  else if (r < -RUNTIME_PI) r += TWO_PI;
10434
12029
  r *= mix;
@@ -10443,10 +12038,10 @@ function applyWorld(w, property, v, mix) {
10443
12038
  return;
10444
12039
  }
10445
12040
  case P_X:
10446
- 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;
10447
12042
  return;
10448
12043
  case P_Y:
10449
- 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;
10450
12045
  return;
10451
12046
  case P_SCALE_X:
10452
12047
  case P_SCALE_Y: {
@@ -10454,7 +12049,7 @@ function applyWorld(w, property, v, mix) {
10454
12049
  const q = property === P_SCALE_X ? W_C : W_D;
10455
12050
  const s = Math.sqrt(w[p] * w[p] + w[q] * w[q]);
10456
12051
  if (s === 0) return;
10457
- const k = 1 + (v - s) * mix / s;
12052
+ const k = additive ? 1 + (v - 1) * mix : 1 + (v - s) * mix / s;
10458
12053
  w[p] *= k;
10459
12054
  w[q] *= k;
10460
12055
  return;
@@ -10462,8 +12057,8 @@ function applyWorld(w, property, v, mix) {
10462
12057
  default: {
10463
12058
  const yAngle = Math.atan2(w[W_D], w[W_B]);
10464
12059
  const xAngle = Math.atan2(w[W_C], w[W_A]);
10465
- let r = (v + 90) * RAD - (yAngle - xAngle);
10466
- 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;
10467
12062
  const angle = yAngle + r * mix;
10468
12063
  const s = Math.sqrt(w[W_B] * w[W_B] + w[W_D] * w[W_D]);
10469
12064
  w[W_B] = Math.cos(angle) * s;
@@ -10471,6 +12066,43 @@ function applyWorld(w, property, v, mix) {
10471
12066
  }
10472
12067
  }
10473
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
+ }
10474
12106
  function frameSource(doc, parsed) {
10475
12107
  if (doc.pages === null) {
10476
12108
  throw new CoreInputError(
@@ -10734,6 +12366,10 @@ function checkedLiveFrame(frame2, current) {
10734
12366
  if (drawing) liveDrawings.set(lease, drawing);
10735
12367
  return lease;
10736
12368
  }
12369
+ function withLiveClip(metadata, template) {
12370
+ Object.defineProperty(metadata, "clip", { get: () => template?.clip ?? null });
12371
+ return metadata;
12372
+ }
10737
12373
  class RuntimeFrames {
10738
12374
  model;
10739
12375
  pose;
@@ -10742,6 +12378,7 @@ class RuntimeFrames {
10742
12378
  order;
10743
12379
  tint;
10744
12380
  templates;
12381
+ clips;
10745
12382
  colours;
10746
12383
  /** Incremented when a draw refresh changes colour without a pose step. */
10747
12384
  revision = 0;
@@ -10755,6 +12392,11 @@ class RuntimeFrames {
10755
12392
  this.tint = new Float64Array(this.model.slots.length * 4);
10756
12393
  const colours = [];
10757
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
+ });
10758
12400
  this.templates = this.model.attachments.map((attachment, index) => {
10759
12401
  const identity = attachment;
10760
12402
  if (identity.skin === void 0 || identity.placeholder === void 0) {
@@ -10772,8 +12414,9 @@ class RuntimeFrames {
10772
12414
  if (template.kind === "region") template.corners = vertices;
10773
12415
  else template.vertices = vertices;
10774
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);
10775
12418
  }
10776
- metadata.push(Object.freeze({
12419
+ const item = {
10777
12420
  slot: slot.name,
10778
12421
  slotIndex: attachment.slot,
10779
12422
  attachment: attachment.name,
@@ -10788,9 +12431,10 @@ class RuntimeFrames {
10788
12431
  count: attachment.count,
10789
12432
  uvs: Object.freeze(template?.uvs ?? []),
10790
12433
  triangles: Object.freeze(template?.kind === "mesh" ? template.triangles : []),
10791
- dark: template?.dark ? Object.freeze(template.dark) : null,
12434
+ dark: template?.dark ?? null,
10792
12435
  clip: null
10793
- }));
12436
+ };
12437
+ metadata.push(Object.freeze(this.pose.clipping === null ? item : withLiveClip(item, template)));
10794
12438
  return template;
10795
12439
  });
10796
12440
  this.colours = colours;
@@ -10818,32 +12462,76 @@ class RuntimeFrames {
10818
12462
  }
10819
12463
  if (colorChanged) this.revision++;
10820
12464
  let count = 0;
10821
- 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];
10822
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
+ }
10823
12480
  const template = ai < 0 ? null : this.templates[ai];
10824
- if (!template || !this.model.active[this.model.slots[si].bone]) continue;
10825
- const offset = si * 4;
10826
- const colour = this.colours[ai];
10827
- for (let c = 0; c < 4; c++) this.tint[offset + c] = this.pose.light[offset + c] * colour[c] * color[c];
10828
- if (!draws(template.slot)) continue;
10829
- this.order[count] = ai;
10830
- 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;
10831
12495
  }
10832
12496
  this.renderFrame.order.length = count;
10833
12497
  this.drawn = count;
10834
12498
  this.epoch = generation;
10835
12499
  }
10836
12500
  snapshot(draws) {
10837
- const selected = [];
10838
- 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];
10839
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
+ }
10840
12518
  const slot = ai < 0 ? null : this.templates[ai];
10841
- 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;
10842
12534
  }
10843
- const order = selected.map((slot) => {
10844
- const common = { ...slot, tint: slot.tint.slice() };
10845
- 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 };
10846
- });
10847
12535
  return { order };
10848
12536
  }
10849
12537
  }
@@ -11575,6 +13263,70 @@ class RigPlayer {
11575
13263
  if (x !== 0 || y !== 0) live.physicsTranslate(x, y);
11576
13264
  if (motion.rotation) live.physicsRotate(0, 0, motion.rotation);
11577
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
+ }
11578
13330
  /** Stops stepping; `frame` keeps drawing the pose it holds. */
11579
13331
  pause() {
11580
13332
  this.paused = true;
@@ -12455,6 +14207,11 @@ class RigPlayer {
12455
14207
  this.poserLabel = "oracle: explicitly selected";
12456
14208
  return;
12457
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
+ }
12458
14215
  try {
12459
14216
  this.runtimeModel = compileRuntime(this.view);
12460
14217
  this.poserLabel = this.trackMode ? "oracle: track composition" : this.adjustFn !== null ? "oracle: local adjustment" : "runtime";