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