rig-play 1.4.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1307 -82
- package/dist/index.js.map +1 -1
- package/dist/player.d.ts +160 -24
- package/dist/player.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -377,7 +377,7 @@ function timelineIdentity(slot, shown) {
|
|
|
377
377
|
return `${shown.skin}/${slot}/${shown.placeholder}`;
|
|
378
378
|
}
|
|
379
379
|
const nameOf = (a) => `${a.skin}/${a.slot}/${a.attachment}`;
|
|
380
|
-
function attachmentStates(doc, resolve, placeholders, sample, sliders, plant = {}) {
|
|
380
|
+
function attachmentStates(doc, resolve, placeholders, sample, sliders, plant = {}, mixed) {
|
|
381
381
|
const evalDeform = plant.deform ?? deformAt;
|
|
382
382
|
const blendOf = plant.deformBlend ?? blendDeform;
|
|
383
383
|
const evalFrame = plant.sequence ?? sequenceFrameAt;
|
|
@@ -388,9 +388,10 @@ function attachmentStates(doc, resolve, placeholders, sample, sliders, plant = {
|
|
|
388
388
|
for (const slot of doc.slots) {
|
|
389
389
|
const live = gate(doc, slot, active);
|
|
390
390
|
let placeholder = placeholders.get(slot.name) ?? null;
|
|
391
|
-
|
|
391
|
+
const base = live ? mixed?.get(slot.name) : void 0;
|
|
392
|
+
let deform = base?.deform ?? null;
|
|
392
393
|
let frame2 = null;
|
|
393
|
-
const seen = [];
|
|
394
|
+
const seen = base === void 0 ? [] : [...base.seen];
|
|
394
395
|
const state = () => {
|
|
395
396
|
if (placeholder === null) return null;
|
|
396
397
|
const s = resolve(doc, { ...slot, setup: placeholder });
|
|
@@ -2450,7 +2451,7 @@ function applySliderPhysics(r, at, alpha, target) {
|
|
|
2450
2451
|
}
|
|
2451
2452
|
}
|
|
2452
2453
|
}
|
|
2453
|
-
const clamp01$
|
|
2454
|
+
const clamp01$2 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
|
|
2454
2455
|
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] };
|
|
2455
2456
|
function applySliderSlots(slot, pose, applications) {
|
|
2456
2457
|
for (const app of applications) {
|
|
@@ -2465,8 +2466,8 @@ function applySliderSlots(slot, pose, applications) {
|
|
|
2465
2466
|
const v = valuesAt$1(tl.keys, app.at);
|
|
2466
2467
|
if (v === null) continue;
|
|
2467
2468
|
COLOUR_CHANNELS[tl.kind].forEach((at, i) => {
|
|
2468
|
-
if (at < 4) pose.light[at] = app.alpha === 1 ? clamp01$
|
|
2469
|
-
else if (pose.dark !== null) pose.dark[at - 4] = app.alpha === 1 ? clamp01$
|
|
2469
|
+
if (at < 4) pose.light[at] = app.alpha === 1 ? clamp01$2(v[i]) : clamp01$2(pose.light[at] + (v[i] - pose.light[at]) * app.alpha);
|
|
2470
|
+
else if (pose.dark !== null) pose.dark[at - 4] = app.alpha === 1 ? clamp01$2(v[i]) : clamp01$2(pose.dark[at - 4] + (v[i] - pose.dark[at - 4]) * app.alpha);
|
|
2470
2471
|
});
|
|
2471
2472
|
}
|
|
2472
2473
|
}
|
|
@@ -3922,23 +3923,15 @@ function adjustLocals(doc, bones, adjust) {
|
|
|
3922
3923
|
}
|
|
3923
3924
|
});
|
|
3924
3925
|
}
|
|
3925
|
-
const clamp01 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
|
|
3926
|
+
const clamp01$1 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
|
|
3926
3927
|
function posedSlots(doc, timelines, t, plant = {}, sliders = [], placeholders) {
|
|
3927
|
-
const resolve = plant.shown ?? shownAttachment;
|
|
3928
3928
|
const colour = plant.colour ?? readColour;
|
|
3929
|
-
const blend = plant.blend ?? readBlend;
|
|
3930
|
-
const round = plant.round ?? gridRound;
|
|
3931
3929
|
const byName = new Map(timelines.slots.map((s) => [s.name, s]));
|
|
3932
|
-
|
|
3933
|
-
const conflicts = [];
|
|
3934
|
-
const active = activeBones(doc);
|
|
3935
|
-
const gate = plant.slotTimelines ?? slotTimelinesApply;
|
|
3936
|
-
for (const slot of doc.slots) {
|
|
3930
|
+
return slotRowsFrom(doc, plant, sliders, placeholders, (slot, live) => {
|
|
3937
3931
|
let placeholder = slot.setup;
|
|
3938
3932
|
const light = slot.color === void 0 ? [1, 1, 1, 1] : colour(slot.color);
|
|
3939
3933
|
const setupDark = slot.dark === void 0 ? null : readColour(slot.dark);
|
|
3940
3934
|
const dark = setupDark === null ? null : [setupDark[0], setupDark[1], setupDark[2]];
|
|
3941
|
-
const live = gate(doc, slot, active);
|
|
3942
3935
|
for (const tl of live ? byName.get(slot.name)?.timelines ?? [] : []) {
|
|
3943
3936
|
if (tl.kind === "attachment") {
|
|
3944
3937
|
const key = keyAt(tl.keys, t, plant);
|
|
@@ -3950,10 +3943,26 @@ function posedSlots(doc, timelines, t, plant = {}, sliders = [], placeholders) {
|
|
|
3950
3943
|
const channels = SLOT_COLOUR_TIMELINES[tl.kind].channels;
|
|
3951
3944
|
channels.forEach((name, i) => {
|
|
3952
3945
|
const at = { r: 0, g: 1, b: 2, a: 3, r2: 4, g2: 5, b2: 6 }[name];
|
|
3953
|
-
if (at < 4) light[at] = v === null ? setupLight[at] : clamp01(v[i]);
|
|
3954
|
-
else if (dark !== null && setupDark !== null) dark[at - 4] = v === null ? setupDark[at - 4] : clamp01(v[i]);
|
|
3946
|
+
if (at < 4) light[at] = v === null ? setupLight[at] : clamp01$1(v[i]);
|
|
3947
|
+
else if (dark !== null && setupDark !== null) dark[at - 4] = v === null ? setupDark[at - 4] : clamp01$1(v[i]);
|
|
3955
3948
|
});
|
|
3956
3949
|
}
|
|
3950
|
+
return { placeholder, light, dark };
|
|
3951
|
+
});
|
|
3952
|
+
}
|
|
3953
|
+
function slotRowsFrom(doc, plant, sliders, placeholders, baseOf) {
|
|
3954
|
+
const resolve = plant.shown ?? shownAttachment;
|
|
3955
|
+
const blend = plant.blend ?? readBlend;
|
|
3956
|
+
const round = plant.round ?? gridRound;
|
|
3957
|
+
const rows = [];
|
|
3958
|
+
const conflicts = [];
|
|
3959
|
+
const active = activeBones(doc);
|
|
3960
|
+
const gate = plant.slotTimelines ?? slotTimelinesApply;
|
|
3961
|
+
for (const slot of doc.slots) {
|
|
3962
|
+
const live = gate(doc, slot, active);
|
|
3963
|
+
const base = baseOf(slot, live);
|
|
3964
|
+
let placeholder = base.placeholder;
|
|
3965
|
+
const { light, dark } = base;
|
|
3957
3966
|
placeholders?.set(slot.name, placeholder);
|
|
3958
3967
|
const pose = { placeholder, light, dark };
|
|
3959
3968
|
if (live) applySliderSlots(slot.name, pose, sliders);
|
|
@@ -3996,14 +4005,12 @@ function stepRows(posed) {
|
|
|
3996
4005
|
return posed.rows;
|
|
3997
4006
|
}
|
|
3998
4007
|
function posedBoneStep(doc, timelines, t, plant, constraints, sliders, step) {
|
|
3999
|
-
const active = activeBones(doc);
|
|
4000
4008
|
const bones = posedBones(doc, timelines, t, plant);
|
|
4009
|
+
if (constraints !== void 0 && step !== void 0) return steppedLocals(doc, bones, plant, () => pathDeformed(doc, posedRecords(constraintRecords(doc), constraints, t), timelines, t, plant), constraints.physicsKeyed ?? [], t, sliders, step);
|
|
4010
|
+
const active = activeBones(doc);
|
|
4001
4011
|
if (step?.adjust !== void 0) adjustLocals(doc, bones, step.adjust);
|
|
4002
4012
|
let world = (plant.evaluate ?? worldTransforms)(bones, active);
|
|
4003
|
-
if (constraints !== void 0
|
|
4004
|
-
const records = stepPhysicsRecords(pathDeformed(doc, posedRecords(constraintRecords(doc), constraints, t), timelines, t, plant), constraints.physicsKeyed ?? [], t, active, step.ctx, step.before);
|
|
4005
|
-
world = applyConstraints(bones, world, active, plant.constraints ? plant.constraints(records) : records, null, sliders, step.ctx, void 0, solverRules(plant.solver));
|
|
4006
|
-
} else if (constraints !== void 0) {
|
|
4013
|
+
if (constraints !== void 0) {
|
|
4007
4014
|
const setupRecords = constraintRecords(doc);
|
|
4008
4015
|
const records = pathDeformed(doc, posedRecords(setupRecords, constraints, t), timelines, t, plant);
|
|
4009
4016
|
const previous = setupRecords.some((r) => r.kind === "path") ? applyConstraints(doc.bones, (plant.evaluate ?? worldTransforms)(doc.bones, active), active, plant.constraints ? plant.constraints(setupRecords) : setupRecords, null, void 0, void 0, void 0, solverRules(plant.solver)) : null;
|
|
@@ -4012,6 +4019,20 @@ function posedBoneStep(doc, timelines, t, plant, constraints, sliders, step) {
|
|
|
4012
4019
|
for (const b of bones) if (world.get(b.name) === void 0) throw new CoreInputError(`the evaluator returned no transform for bone "${b.name}"`);
|
|
4013
4020
|
return { bones, world, active, round: plant.round ?? gridRound };
|
|
4014
4021
|
}
|
|
4022
|
+
function steppedLocals(doc, bones, plant, records, physicsKeyed, t, sliders, step) {
|
|
4023
|
+
const active = activeBones(doc);
|
|
4024
|
+
if (step.adjust !== void 0) adjustLocals(doc, bones, step.adjust);
|
|
4025
|
+
let world = (plant.evaluate ?? worldTransforms)(bones, active);
|
|
4026
|
+
const stepped = stepPhysicsRecords(records(), physicsKeyed, t, active, step.ctx, step.before);
|
|
4027
|
+
world = applyConstraints(bones, world, active, plant.constraints ? plant.constraints(stepped) : stepped, null, sliders, step.ctx, void 0, solverRules(plant.solver));
|
|
4028
|
+
for (const b of bones) if (world.get(b.name) === void 0) throw new CoreInputError(`the evaluator returned no transform for bone "${b.name}"`);
|
|
4029
|
+
return { bones, world, active, round: plant.round ?? gridRound };
|
|
4030
|
+
}
|
|
4031
|
+
function steppedLocalsWorld(doc, bones, plant, sliders, step, rows) {
|
|
4032
|
+
const posed = steppedLocals(doc, bones, plant, () => constraintRecords(doc), [], 0, sliders, step);
|
|
4033
|
+
stepRows(posed);
|
|
4034
|
+
return posed.world;
|
|
4035
|
+
}
|
|
4015
4036
|
function pathDeformed(doc, records, timelines, t, plant) {
|
|
4016
4037
|
if (timelines.attachments.every((a) => a.deform === null)) return records;
|
|
4017
4038
|
return records.map((r) => {
|
|
@@ -4929,6 +4950,14 @@ function openWalk(doc, anim, plant, reset, mode, shape, adjust) {
|
|
|
4929
4950
|
const ctx = freshStepContext(plant.physicsStep);
|
|
4930
4951
|
if (mode.loop && mode.wrapResets !== false) ctx.loop = true;
|
|
4931
4952
|
const track = { doc, anim, duration: anim.timelines.duration, mode, plant, raw: { ...plant, ...roundPlant(mode) }, resolve: plant.shown ?? shownAttachment, shape, ctx, adjust, trackTime: 0, last: -1 };
|
|
4953
|
+
if (reset === "setup") {
|
|
4954
|
+
const setup = poseSetup(doc, { ...plant, ...roundPlant(mode) }, ctx);
|
|
4955
|
+
const absent = setup.absent.filter(([block]) => block !== "setup.clipped");
|
|
4956
|
+
if (absent.length > 0) throw new CoreInputError(`the raw walk's reset pose leaves ${absent.map(([b, w]) => `${b} out (${w})`).join("; ")}`);
|
|
4957
|
+
if (setup.world === null || setup.shown === null || setup.setup.slots === null || setup.setup.drawOrder === null) throw new CoreInputError("the raw walk's reset pose was not posed");
|
|
4958
|
+
ctx.phase = "update";
|
|
4959
|
+
return { track, first: shape.setup(doc, setup.world, setup.setup.slots, setup.setup.drawOrder, setup.shown, plant, mode) };
|
|
4960
|
+
}
|
|
4932
4961
|
return { track, first: stepWalk(track, 0, true) };
|
|
4933
4962
|
}
|
|
4934
4963
|
function stepWalk(track, dt, first = false) {
|
|
@@ -4939,26 +4968,56 @@ function stepWalk(track, dt, first = false) {
|
|
|
4939
4968
|
const t = mode.loop ? (mode.time ?? loopedTime)(trackTime, duration, trackTime - dt) : trackTime < duration ? trackTime : duration;
|
|
4940
4969
|
const before = ctx.time;
|
|
4941
4970
|
ctx.time += mode.clock === void 0 ? dt : mode.clock(dt, t - (first ? 0 : Math.max(last, 0)));
|
|
4971
|
+
const fire = plant.events ?? eventsFired;
|
|
4972
|
+
const keys = anim.timelines.events;
|
|
4973
|
+
const pose = poseStepFrom(doc, plant, resolve, shape, mode, ctx, first, trackTime, t, {
|
|
4974
|
+
world: (sliders) => shape.rows ? posedBoneWorld(doc, anim.timelines, t, raw, anim.constraints, sliders, { ctx, before, adjust }).world : posedBoneWorldAlone(doc, anim.timelines, t, raw, anim.constraints, sliders, { ctx, before, adjust }),
|
|
4975
|
+
slots: (sliders, placeholders) => posedSlots(doc, anim.timelines, t, raw, sliders, placeholders),
|
|
4976
|
+
drawOrder: anim.timelines.drawOrder,
|
|
4977
|
+
at: t,
|
|
4978
|
+
sample: { timelines: anim.timelines, t },
|
|
4979
|
+
events: () => mode.loop && mode.live !== true ? [] : mode.loop ? fire(keys, last, t, rawNumber, t < last) : fire(keys, last, t, rawNumber)
|
|
4980
|
+
});
|
|
4981
|
+
track.last = t;
|
|
4982
|
+
return pose;
|
|
4983
|
+
}
|
|
4984
|
+
function poseStepFrom(doc, plant, resolve, shape, mode, ctx, first, trackTime, animationTime, posers) {
|
|
4942
4985
|
const sliders = [];
|
|
4943
|
-
const world =
|
|
4986
|
+
const world = posers.world(sliders);
|
|
4944
4987
|
if (first) ctx.phase = "update";
|
|
4945
4988
|
const placeholders = /* @__PURE__ */ new Map();
|
|
4946
|
-
const slots =
|
|
4989
|
+
const slots = posers.slots(sliders, placeholders);
|
|
4947
4990
|
if (slots.conflicts.length > 0) throw new CoreInputError(`the raw walk leaves the slots out: ${slots.conflicts.join("; ")}`);
|
|
4948
4991
|
const evalOrder = plant.drawOrder ?? drawOrderAt;
|
|
4949
|
-
let order = evalOrder(doc.slots.length,
|
|
4992
|
+
let order = evalOrder(doc.slots.length, posers.drawOrder, posers.at) ?? doc.slots.map((_s, k) => k);
|
|
4950
4993
|
for (const app of sliders) order = evalOrder(doc.slots.length, app.timelines.drawOrder, app.at) ?? order;
|
|
4951
4994
|
const drawOrder = order.map((k) => doc.slots[k].name);
|
|
4952
|
-
const states = attachmentStates(doc, resolve, placeholders,
|
|
4995
|
+
const states = attachmentStates(doc, resolve, placeholders, posers.sample, sliders, plant, posers.mixed);
|
|
4953
4996
|
if (states.why.length > 0) throw new CoreInputError(`the raw walk leaves the attachments out: ${states.why.join("; ")}`);
|
|
4954
4997
|
const rank = new Map(drawOrder.map((n, r) => [n, r]));
|
|
4955
4998
|
const shown = [...states.shown].sort((a, b) => (rank.get(a.slot) ?? 0) - (rank.get(b.slot) ?? 0));
|
|
4956
|
-
const
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4999
|
+
const events = () => posers.events().map((e) => [e[0], finite(e[1], "event time"), e[2], finite(e[3], "event float"), e[4]]);
|
|
5000
|
+
return shape.step(doc, { trackTime, animationTime, world, slots: slots.rows, drawOrder, shown, events }, plant, mode);
|
|
5001
|
+
}
|
|
5002
|
+
function openRawTracksPath(doc, plant) {
|
|
5003
|
+
const why = constraintsAbsentWhy(doc) ?? pathAnimationsWhy(doc) ?? steppedPreviousPassWhy(doc);
|
|
5004
|
+
if (why !== null) throw new CoreInputError(`the raw walk leaves the bones out: ${why}`);
|
|
5005
|
+
const ctx = freshStepContext(plant.physicsStep);
|
|
5006
|
+
const raw = { ...plant, ...roundPlant(RAW_MODE) };
|
|
5007
|
+
const resolve = plant.shown ?? shownAttachment;
|
|
5008
|
+
return {
|
|
5009
|
+
ctx,
|
|
5010
|
+
raw,
|
|
5011
|
+
setupPose: () => {
|
|
5012
|
+
const setup = poseSetup(doc, raw, ctx);
|
|
5013
|
+
const absent = setup.absent.filter(([block]) => block !== "setup.clipped");
|
|
5014
|
+
if (absent.length > 0) throw new CoreInputError(`the raw walk's reset pose leaves ${absent.map(([b, w]) => `${b} out (${w})`).join("; ")}`);
|
|
5015
|
+
if (setup.world === null || setup.shown === null || setup.setup.slots === null || setup.setup.drawOrder === null) throw new CoreInputError("the raw walk's reset pose was not posed");
|
|
5016
|
+
ctx.phase = "update";
|
|
5017
|
+
return RAW_SHAPE.setup(doc, setup.world, setup.setup.slots, setup.setup.drawOrder, setup.shown, plant, RAW_MODE);
|
|
5018
|
+
},
|
|
5019
|
+
step: (first, trackTime, animationTime, posers) => poseStepFrom(doc, plant, resolve, RAW_SHAPE, RAW_MODE, ctx, first, trackTime, animationTime, posers)
|
|
5020
|
+
};
|
|
4962
5021
|
}
|
|
4963
5022
|
function openRawTrack(doc, animation, plant, reset, mode, adjust) {
|
|
4964
5023
|
return openWalk(doc, animationOf(doc, animation), plant, reset, mode, RAW_SHAPE, adjust);
|
|
@@ -4970,6 +5029,7 @@ const HELD = { loop: false, keep: false };
|
|
|
4970
5029
|
const LOOPED = { loop: true, keep: false, live: true };
|
|
4971
5030
|
function openTrack(doc, animation, options, plant = {}) {
|
|
4972
5031
|
if (typeof options !== "object" || options === null || typeof options.loop !== "boolean") throw new CoreInputError(`the live track's options.loop is ${JSON.stringify(options?.loop)}, not true or false`);
|
|
5032
|
+
if (options.reset !== "animation" && options.reset !== "setup") throw new CoreInputError(`the live track's options.reset is ${JSON.stringify(options.reset)}, not "animation" or "setup"`);
|
|
4973
5033
|
if (options.adjust !== void 0 && typeof options.adjust !== "function") throw new CoreInputError(`the live track's options.adjust is ${typeof options.adjust}, not a function`);
|
|
4974
5034
|
const { track, first } = openRawTrack(doc, animation, plant, options.reset, options.loop ? LOOPED : HELD, options.adjust);
|
|
4975
5035
|
return new Track(track, first);
|
|
@@ -5006,6 +5066,940 @@ class Track {
|
|
|
5006
5066
|
}
|
|
5007
5067
|
}
|
|
5008
5068
|
}
|
|
5069
|
+
const CURRENT = 0;
|
|
5070
|
+
const SETUP = 1;
|
|
5071
|
+
const FIRST = 2;
|
|
5072
|
+
const MODE = 3;
|
|
5073
|
+
const HOLD = 4;
|
|
5074
|
+
const EMPTY = { name: null, duration: 0, timelines: [], ids: /* @__PURE__ */ new Set() };
|
|
5075
|
+
const BONE_PROPERTIES = {
|
|
5076
|
+
rotate: ["rotate"],
|
|
5077
|
+
translate: ["x", "y"],
|
|
5078
|
+
translatex: ["x"],
|
|
5079
|
+
translatey: ["y"],
|
|
5080
|
+
scale: ["scaleX", "scaleY"],
|
|
5081
|
+
scalex: ["scaleX"],
|
|
5082
|
+
scaley: ["scaleY"],
|
|
5083
|
+
shear: ["shearX", "shearY"],
|
|
5084
|
+
shearx: ["shearX"],
|
|
5085
|
+
sheary: ["shearY"],
|
|
5086
|
+
inherit: ["inherit"]
|
|
5087
|
+
};
|
|
5088
|
+
const COLOUR_PROPERTIES = { rgba: ["rgb", "alpha"], rgb: ["rgb"], alpha: ["alpha"], rgba2: ["rgb", "alpha", "rgb2"], rgb2: ["rgb", "rgb2"] };
|
|
5089
|
+
function mixAnimationOf(doc, name) {
|
|
5090
|
+
const anim = doc.animations.find((a) => a.name === name);
|
|
5091
|
+
if (anim === void 0) throw new CoreInputError(`animation "${name}" is not one of this document's [${doc.animations.map((a) => a.name).join(", ")}]`);
|
|
5092
|
+
const t = anim.timelines;
|
|
5093
|
+
const c = anim.constraints;
|
|
5094
|
+
const left = [];
|
|
5095
|
+
for (const [kind, n] of [["ik", c.ik.length], ["transform", c.transform.length], ["path", c.path.length], ["physics", c.physics + (c.physicsKeyed?.length ?? 0)], ["slider", c.slider.length]]) if (n > 0) left.push(`${kind} constraint timelines`);
|
|
5096
|
+
if (t.attachments.some((a) => a.sequence !== null)) left.push("a sequence timeline");
|
|
5097
|
+
if (t.drawOrder.length > 0) left.push("the draw order");
|
|
5098
|
+
for (const a of t.attachments) {
|
|
5099
|
+
if (a.deform === null) continue;
|
|
5100
|
+
const g = doc.skins.find((k) => k.name === a.skin)?.attachments[a.slot]?.[a.attachment]?.geometry;
|
|
5101
|
+
if (g?.kind === "path") left.push(`a deform of path attachment "${a.skin}/${a.slot}/${a.attachment}"`);
|
|
5102
|
+
}
|
|
5103
|
+
if (left.length > 0) throw new CoreInputError(`animation "${name}" keys ${left.join(", ")}, which the live tracks do not mix — they mix bone, slot colour, attachment and deform timelines, and fire events (issue #1336)`);
|
|
5104
|
+
const boneIndex2 = new Map(doc.bones.map((b, i) => [b.name, i]));
|
|
5105
|
+
const slotIndex = new Map(doc.slots.map((s, i) => [s.name, i]));
|
|
5106
|
+
const timelines = [];
|
|
5107
|
+
for (const target of t.slots) {
|
|
5108
|
+
const slot = slotIndex.get(target.name);
|
|
5109
|
+
for (const tl of target.timelines) {
|
|
5110
|
+
if (tl.kind === "attachment") timelines.push({ type: "attachment", slot, keys: tl.keys, ids: [`attachment|${target.name}`], instant: true });
|
|
5111
|
+
else timelines.push({ type: "colour", slot, kind: tl.kind, keys: tl.keys, ids: COLOUR_PROPERTIES[tl.kind].map((p) => `${p}|${target.name}`), instant: false });
|
|
5112
|
+
}
|
|
5113
|
+
}
|
|
5114
|
+
for (const target of t.bones) {
|
|
5115
|
+
const bone2 = boneIndex2.get(target.name);
|
|
5116
|
+
for (const tl of target.timelines) timelines.push({ type: "bone", bone: bone2, kind: tl.kind, keys: tl.keys, ids: BONE_PROPERTIES[tl.kind].map((p) => `${p}|${target.name}`), instant: tl.kind === "inherit" });
|
|
5117
|
+
}
|
|
5118
|
+
for (const a of t.attachments) {
|
|
5119
|
+
if (a.deform === null) continue;
|
|
5120
|
+
const identity = `${a.skin}/${a.slot}/${a.attachment}`;
|
|
5121
|
+
timelines.push({ type: "deform", identity, timeline: a, ids: [`deform|${a.slot}|${identity}`], instant: false });
|
|
5122
|
+
}
|
|
5123
|
+
if (t.events.length > 0) timelines.push({ type: "events", keys: t.events, ids: ["event"], instant: true });
|
|
5124
|
+
return { name, duration: t.duration, timelines, ids: new Set(timelines.flatMap((x) => x.ids)) };
|
|
5125
|
+
}
|
|
5126
|
+
function newEntry(anim, loop) {
|
|
5127
|
+
return {
|
|
5128
|
+
anim,
|
|
5129
|
+
loop,
|
|
5130
|
+
previous: null,
|
|
5131
|
+
next: null,
|
|
5132
|
+
mixingFrom: null,
|
|
5133
|
+
mixingTo: null,
|
|
5134
|
+
delay: 0,
|
|
5135
|
+
trackTime: 0,
|
|
5136
|
+
trackLast: -1,
|
|
5137
|
+
nextTrackLast: -1,
|
|
5138
|
+
trackEnd: Number.MAX_VALUE,
|
|
5139
|
+
animationLast: -1,
|
|
5140
|
+
nextAnimationLast: -1,
|
|
5141
|
+
mixTime: 0,
|
|
5142
|
+
mixDuration: 0,
|
|
5143
|
+
totalAlpha: 0,
|
|
5144
|
+
keepHold: false,
|
|
5145
|
+
timelineMode: [],
|
|
5146
|
+
timelineHoldMix: [],
|
|
5147
|
+
timelinesRotation: []
|
|
5148
|
+
};
|
|
5149
|
+
}
|
|
5150
|
+
function animationTimeOf(e) {
|
|
5151
|
+
if (!e.loop) return Math.min(e.trackTime + 0, e.anim.duration);
|
|
5152
|
+
const d = e.anim.duration;
|
|
5153
|
+
if (d === 0) return 0;
|
|
5154
|
+
return e.trackTime % d + 0;
|
|
5155
|
+
}
|
|
5156
|
+
function trackComplete(e) {
|
|
5157
|
+
const d = e.anim.duration;
|
|
5158
|
+
if (d !== 0) {
|
|
5159
|
+
if (e.loop) return d * (1 + (e.trackTime / d | 0));
|
|
5160
|
+
if (e.trackTime < d) return d;
|
|
5161
|
+
}
|
|
5162
|
+
return e.trackTime;
|
|
5163
|
+
}
|
|
5164
|
+
const signum = (v) => v > 0 ? 1 : v < 0 ? -1 : 0;
|
|
5165
|
+
const clamp01 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
|
|
5166
|
+
const ATTACH_NONE = 0;
|
|
5167
|
+
const ATTACH_SETUP = 1;
|
|
5168
|
+
const ATTACH_RETAIN = 2;
|
|
5169
|
+
class Pose {
|
|
5170
|
+
constructor(doc, plant) {
|
|
5171
|
+
this.doc = doc;
|
|
5172
|
+
this.plant = plant;
|
|
5173
|
+
this.events = [];
|
|
5174
|
+
this.resolved = /* @__PURE__ */ new Map();
|
|
5175
|
+
this.bones = [...doc.bones];
|
|
5176
|
+
this.active = activeBones(doc);
|
|
5177
|
+
const colour = plant.colour ?? readColour;
|
|
5178
|
+
const gate = plant.slotTimelines ?? slotTimelinesApply;
|
|
5179
|
+
this.live = doc.slots.map((s) => gate(doc, s, this.active));
|
|
5180
|
+
this.slots = doc.slots.map((slot) => {
|
|
5181
|
+
const setupDark = slot.dark === void 0 ? null : readColour(slot.dark);
|
|
5182
|
+
return { placeholder: slot.setup, light: slot.color === void 0 ? [1, 1, 1, 1] : colour(slot.color), dark: setupDark === null ? null : [setupDark[0], setupDark[1], setupDark[2]], deform: null, seen: [], attachmentState: ATTACH_NONE };
|
|
5183
|
+
});
|
|
5184
|
+
}
|
|
5185
|
+
/** Bone `i`'s locals to write: the setup bone copied on its first write, as `posedBones` copies a keyed one. */
|
|
5186
|
+
bone(i) {
|
|
5187
|
+
const b = this.bones[i];
|
|
5188
|
+
if (!this.active.has(b.name)) return null;
|
|
5189
|
+
if (b === this.doc.bones[i]) this.bones[i] = { ...b };
|
|
5190
|
+
return this.bones[i];
|
|
5191
|
+
}
|
|
5192
|
+
slotLive(i) {
|
|
5193
|
+
return this.live[i];
|
|
5194
|
+
}
|
|
5195
|
+
/** What `placeholder` shows in slot `i` (`Resolved`). */
|
|
5196
|
+
resolve(i, placeholder) {
|
|
5197
|
+
if (placeholder === null) return { key: null, vertex: false, timeline: null, vertices: null };
|
|
5198
|
+
const slot = this.doc.slots[i];
|
|
5199
|
+
const memo = `${i}\0${placeholder}`;
|
|
5200
|
+
const known = this.resolved.get(memo);
|
|
5201
|
+
if (known !== void 0) return known;
|
|
5202
|
+
const s = (this.plant.shown ?? shownAttachment)(this.doc, { ...slot, setup: placeholder });
|
|
5203
|
+
let out;
|
|
5204
|
+
if (s === null) out = { key: null, vertex: false, timeline: null, vertices: null };
|
|
5205
|
+
else if ("conflict" in s) out = { key: `conflict\0${placeholder}`, vertex: false, timeline: null, vertices: null };
|
|
5206
|
+
else out = resolvedOf(this.doc, slot, s);
|
|
5207
|
+
this.resolved.set(memo, out);
|
|
5208
|
+
return out;
|
|
5209
|
+
}
|
|
5210
|
+
/** `SlotPose.setAttachment`: a different attachment clears the deform unless both play the same record's timelines. */
|
|
5211
|
+
setAttachment(i, placeholder) {
|
|
5212
|
+
const s = this.slots[i];
|
|
5213
|
+
const was = this.resolve(i, s.placeholder);
|
|
5214
|
+
const now = this.resolve(i, placeholder);
|
|
5215
|
+
s.placeholder = placeholder;
|
|
5216
|
+
if (was.key === now.key) return;
|
|
5217
|
+
if (!now.vertex || !was.vertex || now.timeline !== was.timeline) s.deform = null;
|
|
5218
|
+
}
|
|
5219
|
+
}
|
|
5220
|
+
function resolvedOf(doc, slot, s) {
|
|
5221
|
+
const g = s.record.geometry;
|
|
5222
|
+
const key = `${s.skin}\0${s.placeholder}`;
|
|
5223
|
+
if (g === void 0 || g.kind === "region") return { key, vertex: false, timeline: null, vertices: null };
|
|
5224
|
+
if (g.kind === "linkedmesh") {
|
|
5225
|
+
const source = doc.skins.find((k) => k.name === g.skin)?.attachments[g.slot]?.[g.source]?.geometry;
|
|
5226
|
+
const vertices = source !== void 0 && source.kind !== "region" && source.kind !== "linkedmesh" ? source.vertices : null;
|
|
5227
|
+
return { key, vertex: true, timeline: s.record.timelines === true ? `${g.skin}/${g.slot}/${g.source}` : `${s.skin}/${slot.name}/${s.placeholder}`, vertices };
|
|
5228
|
+
}
|
|
5229
|
+
return { key, vertex: true, timeline: `${s.skin}/${slot.name}/${s.placeholder}`, vertices: g.vertices };
|
|
5230
|
+
}
|
|
5231
|
+
class Mixer {
|
|
5232
|
+
constructor(doc, plant) {
|
|
5233
|
+
this.doc = doc;
|
|
5234
|
+
this.plant = plant;
|
|
5235
|
+
this.tracks = [];
|
|
5236
|
+
this.animationsChanged = false;
|
|
5237
|
+
this.propertyIds = /* @__PURE__ */ new Map();
|
|
5238
|
+
this.anims = /* @__PURE__ */ new Map();
|
|
5239
|
+
}
|
|
5240
|
+
animation(name) {
|
|
5241
|
+
let a = this.anims.get(name);
|
|
5242
|
+
if (a === void 0) {
|
|
5243
|
+
a = mixAnimationOf(this.doc, name);
|
|
5244
|
+
this.anims.set(name, a);
|
|
5245
|
+
}
|
|
5246
|
+
return a;
|
|
5247
|
+
}
|
|
5248
|
+
changed() {
|
|
5249
|
+
this.animationsChanged = true;
|
|
5250
|
+
}
|
|
5251
|
+
expandToIndex(index) {
|
|
5252
|
+
while (this.tracks.length <= index) this.tracks.push(null);
|
|
5253
|
+
return this.tracks[index];
|
|
5254
|
+
}
|
|
5255
|
+
clearNext(entry) {
|
|
5256
|
+
entry.next = null;
|
|
5257
|
+
}
|
|
5258
|
+
setTrack(index, current) {
|
|
5259
|
+
const from = this.expandToIndex(index);
|
|
5260
|
+
this.tracks[index] = current;
|
|
5261
|
+
current.previous = null;
|
|
5262
|
+
if (from !== null) {
|
|
5263
|
+
from.next = null;
|
|
5264
|
+
current.mixingFrom = from;
|
|
5265
|
+
from.mixingTo = current;
|
|
5266
|
+
current.mixTime = 0;
|
|
5267
|
+
from.timelinesRotation.length = 0;
|
|
5268
|
+
}
|
|
5269
|
+
this.changed();
|
|
5270
|
+
}
|
|
5271
|
+
setAnimation(track, anim, loop) {
|
|
5272
|
+
let current = this.expandToIndex(track);
|
|
5273
|
+
if (current !== null) {
|
|
5274
|
+
if (current.nextTrackLast === -1 && current.anim === anim) {
|
|
5275
|
+
this.tracks[track] = current.mixingFrom;
|
|
5276
|
+
this.changed();
|
|
5277
|
+
this.clearNext(current);
|
|
5278
|
+
current = current.mixingFrom;
|
|
5279
|
+
} else this.clearNext(current);
|
|
5280
|
+
}
|
|
5281
|
+
const entry = newEntry(anim, loop);
|
|
5282
|
+
this.setTrack(track, entry);
|
|
5283
|
+
return entry;
|
|
5284
|
+
}
|
|
5285
|
+
addAnimation(track, anim, loop, delay) {
|
|
5286
|
+
let last = this.expandToIndex(track);
|
|
5287
|
+
if (last !== null) while (last.next !== null) last = last.next;
|
|
5288
|
+
const entry = newEntry(anim, loop);
|
|
5289
|
+
if (last === null) {
|
|
5290
|
+
this.setTrack(track, entry);
|
|
5291
|
+
if (delay < 0) delay = 0;
|
|
5292
|
+
} else {
|
|
5293
|
+
last.next = entry;
|
|
5294
|
+
entry.previous = last;
|
|
5295
|
+
if (delay <= 0) delay = Math.max(delay + this.complete(last) - entry.mixDuration, 0);
|
|
5296
|
+
}
|
|
5297
|
+
entry.delay = delay;
|
|
5298
|
+
return entry;
|
|
5299
|
+
}
|
|
5300
|
+
/** `TrackEntry.setMixDuration(mixDuration, delay)`. */
|
|
5301
|
+
setMixDuration(entry, mixDuration, delay) {
|
|
5302
|
+
entry.mixDuration = mixDuration;
|
|
5303
|
+
if (delay <= 0) delay = entry.previous === null ? 0 : Math.max(delay + this.complete(entry.previous) - mixDuration, 0);
|
|
5304
|
+
entry.delay = delay;
|
|
5305
|
+
}
|
|
5306
|
+
/** `getTrackComplete`, or 0 under the plant that reads a queued delay as "now". */
|
|
5307
|
+
complete(e) {
|
|
5308
|
+
return this.plant.queueDelayNow === true ? e.trackTime : trackComplete(e);
|
|
5309
|
+
}
|
|
5310
|
+
/** `TrackEntry.mix()`: `mixTime / mixDuration` clamped to 1, 1 over a mix of 0 — reversed under the plant. */
|
|
5311
|
+
mixOf(e) {
|
|
5312
|
+
let m;
|
|
5313
|
+
if (e.mixDuration === 0) m = 1;
|
|
5314
|
+
else {
|
|
5315
|
+
m = e.mixTime / e.mixDuration;
|
|
5316
|
+
if (m >= 1) m = 1;
|
|
5317
|
+
}
|
|
5318
|
+
return this.plant.mixReversed === true ? 1 - m : m;
|
|
5319
|
+
}
|
|
5320
|
+
update(delta) {
|
|
5321
|
+
const tracks = this.tracks;
|
|
5322
|
+
for (let i = 0, n = tracks.length; i < n; i++) {
|
|
5323
|
+
const current = tracks[i];
|
|
5324
|
+
if (current === null) continue;
|
|
5325
|
+
current.animationLast = current.nextAnimationLast;
|
|
5326
|
+
current.trackLast = current.nextTrackLast;
|
|
5327
|
+
let currentDelta = delta;
|
|
5328
|
+
if (current.delay > 0) {
|
|
5329
|
+
current.delay -= currentDelta;
|
|
5330
|
+
if (current.delay > 0) continue;
|
|
5331
|
+
currentDelta = -current.delay;
|
|
5332
|
+
current.delay = 0;
|
|
5333
|
+
}
|
|
5334
|
+
let next = current.next;
|
|
5335
|
+
if (next !== null) {
|
|
5336
|
+
const nextTime = current.trackLast - next.delay;
|
|
5337
|
+
if (nextTime >= 0) {
|
|
5338
|
+
next.delay = 0;
|
|
5339
|
+
next.trackTime += nextTime + delta;
|
|
5340
|
+
current.trackTime += currentDelta;
|
|
5341
|
+
this.setTrack(i, next);
|
|
5342
|
+
while (next.mixingFrom !== null) {
|
|
5343
|
+
next.mixTime += delta;
|
|
5344
|
+
next = next.mixingFrom;
|
|
5345
|
+
}
|
|
5346
|
+
continue;
|
|
5347
|
+
}
|
|
5348
|
+
} else if (current.trackLast >= current.trackEnd && current.mixingFrom === null) {
|
|
5349
|
+
tracks[i] = null;
|
|
5350
|
+
this.changed();
|
|
5351
|
+
this.clearNext(current);
|
|
5352
|
+
continue;
|
|
5353
|
+
}
|
|
5354
|
+
if (current.mixingFrom !== null && this.updateMixingFrom(current, delta)) {
|
|
5355
|
+
const from = current.mixingFrom;
|
|
5356
|
+
current.mixingFrom = null;
|
|
5357
|
+
if (from !== null) from.mixingTo = null;
|
|
5358
|
+
this.changed();
|
|
5359
|
+
}
|
|
5360
|
+
current.trackTime += currentDelta;
|
|
5361
|
+
}
|
|
5362
|
+
}
|
|
5363
|
+
updateMixingFrom(to, delta) {
|
|
5364
|
+
const from = to.mixingFrom;
|
|
5365
|
+
if (from === null) return true;
|
|
5366
|
+
const finished = this.updateMixingFrom(from, delta);
|
|
5367
|
+
from.animationLast = from.nextAnimationLast;
|
|
5368
|
+
from.trackLast = from.nextTrackLast;
|
|
5369
|
+
if (to.nextTrackLast !== -1 && to.mixTime >= to.mixDuration) {
|
|
5370
|
+
if (from.totalAlpha === 0 || to.mixDuration === 0) {
|
|
5371
|
+
to.mixingFrom = from.mixingFrom;
|
|
5372
|
+
if (from.mixingFrom !== null) from.mixingFrom.mixingTo = to;
|
|
5373
|
+
if (from.totalAlpha === 0) for (let next = to; next.mixingTo !== null; next = next.mixingTo) next.keepHold = true;
|
|
5374
|
+
this.changed();
|
|
5375
|
+
}
|
|
5376
|
+
return finished;
|
|
5377
|
+
}
|
|
5378
|
+
from.trackTime += delta;
|
|
5379
|
+
to.mixTime += delta;
|
|
5380
|
+
return false;
|
|
5381
|
+
}
|
|
5382
|
+
/** `AnimationState.apply` onto one step's pose, every track in order; returns track 0's current entry's animation time (0 where it applied none). */
|
|
5383
|
+
apply(pose) {
|
|
5384
|
+
if (this.animationsChanged) this.computeModes();
|
|
5385
|
+
let base = 0;
|
|
5386
|
+
const tracks = this.tracks;
|
|
5387
|
+
for (let i = 0, n = tracks.length; i < n; i++) {
|
|
5388
|
+
const current = tracks[i];
|
|
5389
|
+
if (current === null || current.delay > 0) continue;
|
|
5390
|
+
let alpha = 1;
|
|
5391
|
+
if (current.mixingFrom !== null) alpha *= this.applyMixingFrom(current, pose, i);
|
|
5392
|
+
else if (current.trackTime >= current.trackEnd && current.next === null) alpha = 0;
|
|
5393
|
+
const animationLast = current.animationLast;
|
|
5394
|
+
const animationTime = animationTimeOf(current);
|
|
5395
|
+
const timelines = current.anim.timelines;
|
|
5396
|
+
const count = timelines.length;
|
|
5397
|
+
if (i === 0 && alpha === 1) {
|
|
5398
|
+
for (const tl of timelines) {
|
|
5399
|
+
if (tl.type === "attachment") this.applyAttachment(pose, tl, animationTime, SETUP, true);
|
|
5400
|
+
else this.applyTimeline(pose, tl, animationLast, animationTime, true, 1, SETUP, false);
|
|
5401
|
+
}
|
|
5402
|
+
} else {
|
|
5403
|
+
const modes = current.timelineMode;
|
|
5404
|
+
const retain = alpha >= 0;
|
|
5405
|
+
const firstFrame = current.timelinesRotation.length !== count << 1;
|
|
5406
|
+
if (firstFrame) current.timelinesRotation.length = count << 1;
|
|
5407
|
+
timelines.forEach((tl, ii) => {
|
|
5408
|
+
const from = this.fromOf(modes[ii] & MODE, i);
|
|
5409
|
+
if (tl.type === "bone" && tl.kind === "rotate") this.applyRotate(pose, tl, animationTime, alpha, from, current.timelinesRotation, ii << 1, firstFrame);
|
|
5410
|
+
else if (tl.type === "attachment") this.applyAttachment(pose, tl, animationTime, from, retain);
|
|
5411
|
+
else this.applyTimeline(pose, tl, animationLast, animationTime, true, alpha, from, false);
|
|
5412
|
+
});
|
|
5413
|
+
}
|
|
5414
|
+
if (i === 0) base = animationTime;
|
|
5415
|
+
current.nextAnimationLast = animationTime;
|
|
5416
|
+
current.nextTrackLast = current.trackTime;
|
|
5417
|
+
}
|
|
5418
|
+
pose.slots.forEach((s, k) => {
|
|
5419
|
+
if (s.attachmentState === ATTACH_SETUP) pose.setAttachment(k, this.doc.slots[k].setup);
|
|
5420
|
+
});
|
|
5421
|
+
return base;
|
|
5422
|
+
}
|
|
5423
|
+
/** Where a timeline mixes from — `CURRENT` read as `SETUP` above track 0 under the plant. */
|
|
5424
|
+
fromOf(from, track) {
|
|
5425
|
+
return this.plant.higherTracksFromSetup === true && track > 0 && from === CURRENT ? SETUP : from;
|
|
5426
|
+
}
|
|
5427
|
+
applyMixingFrom(to, pose, track) {
|
|
5428
|
+
const from = to.mixingFrom;
|
|
5429
|
+
const fromMix = from.mixingFrom !== null ? this.applyMixingFrom(from, pose, track) : 1;
|
|
5430
|
+
const mix = this.mixOf(to);
|
|
5431
|
+
const a = 1 * fromMix;
|
|
5432
|
+
const keep = 1 - mix * 1;
|
|
5433
|
+
const alphaMix = a * (1 - mix);
|
|
5434
|
+
const alphaHold = keep > 0 ? alphaMix / keep : a;
|
|
5435
|
+
const timelines = from.anim.timelines;
|
|
5436
|
+
const count = timelines.length;
|
|
5437
|
+
const modes = from.timelineMode;
|
|
5438
|
+
const holdMix = from.timelineHoldMix;
|
|
5439
|
+
const firstFrame = from.timelinesRotation.length !== count << 1;
|
|
5440
|
+
if (firstFrame) from.timelinesRotation.length = count << 1;
|
|
5441
|
+
const animationLast = from.animationLast;
|
|
5442
|
+
const animationTime = animationTimeOf(from);
|
|
5443
|
+
const fire = this.plant.eventsUntilHalfMix === true && mix < 0.5;
|
|
5444
|
+
from.totalAlpha = 0;
|
|
5445
|
+
timelines.forEach((tl, i) => {
|
|
5446
|
+
const mode = modes[i];
|
|
5447
|
+
const mixFrom = this.fromOf(mode & MODE, track);
|
|
5448
|
+
let alpha;
|
|
5449
|
+
if ((mode & HOLD) !== 0 && this.plant.holdFaded !== true) {
|
|
5450
|
+
const h = holdMix[i];
|
|
5451
|
+
alpha = h === null || h === void 0 ? alphaHold : alphaHold * (1 - this.mixOf(h));
|
|
5452
|
+
} else alpha = alphaMix;
|
|
5453
|
+
from.totalAlpha += alpha;
|
|
5454
|
+
if (tl.type === "bone" && tl.kind === "rotate") this.applyRotate(pose, tl, animationTime, alpha, mixFrom, from.timelinesRotation, i << 1, firstFrame);
|
|
5455
|
+
else if (tl.type === "attachment") this.applyAttachment(pose, tl, animationTime, mixFrom, false);
|
|
5456
|
+
else this.applyTimeline(pose, tl, animationLast, animationTime, fire, alpha, mixFrom, true);
|
|
5457
|
+
});
|
|
5458
|
+
from.nextAnimationLast = animationTime;
|
|
5459
|
+
from.nextTrackLast = from.trackTime;
|
|
5460
|
+
return mix;
|
|
5461
|
+
}
|
|
5462
|
+
/** `_animationsChanged`: every track's entries' modes, oldest first, the property owners cleared after. */
|
|
5463
|
+
computeModes() {
|
|
5464
|
+
this.animationsChanged = false;
|
|
5465
|
+
this.tracks.forEach((top, i) => {
|
|
5466
|
+
if (top === null) return;
|
|
5467
|
+
let entry = top;
|
|
5468
|
+
while (entry.mixingFrom !== null) entry = entry.mixingFrom;
|
|
5469
|
+
do {
|
|
5470
|
+
this.computeHold(entry, i);
|
|
5471
|
+
entry = entry.mixingTo;
|
|
5472
|
+
} while (entry !== null);
|
|
5473
|
+
});
|
|
5474
|
+
this.propertyIds.clear();
|
|
5475
|
+
}
|
|
5476
|
+
computeHold(entry, track) {
|
|
5477
|
+
const timelines = entry.anim.timelines;
|
|
5478
|
+
const modes = entry.timelineMode;
|
|
5479
|
+
modes.length = timelines.length;
|
|
5480
|
+
const holdMix = entry.timelineHoldMix;
|
|
5481
|
+
holdMix.length = 0;
|
|
5482
|
+
const to = entry.mixingTo;
|
|
5483
|
+
timelines.forEach((tl, i) => {
|
|
5484
|
+
const from = this.propertyFrom(track, tl.ids);
|
|
5485
|
+
let mode;
|
|
5486
|
+
if (to === null || tl.instant || !hasAny(to.anim, tl.ids)) mode = from;
|
|
5487
|
+
else {
|
|
5488
|
+
mode = from | HOLD;
|
|
5489
|
+
for (let next = to.mixingTo; next !== null; next = next.mixingTo) {
|
|
5490
|
+
if (!hasAny(next.anim, tl.ids)) {
|
|
5491
|
+
if (next.mixDuration > 0) holdMix[i] = next;
|
|
5492
|
+
break;
|
|
5493
|
+
}
|
|
5494
|
+
}
|
|
5495
|
+
}
|
|
5496
|
+
if (entry.keepHold) mode = mode & ~HOLD | (modes[i] ?? 0) & HOLD;
|
|
5497
|
+
modes[i] = mode;
|
|
5498
|
+
});
|
|
5499
|
+
}
|
|
5500
|
+
/** `AnimationState.from`: `SETUP` for a property no one keyed yet, `FIRST` for one this track keyed first, `CURRENT` for one a lower track keyed. */
|
|
5501
|
+
propertyFrom(track, ids) {
|
|
5502
|
+
const owners = this.propertyIds;
|
|
5503
|
+
let from = SETUP;
|
|
5504
|
+
for (let i = 0, n = ids.length; i < n; i++) {
|
|
5505
|
+
const owner = owners.get(ids[i]);
|
|
5506
|
+
if (owner === void 0) owners.set(ids[i], track);
|
|
5507
|
+
else {
|
|
5508
|
+
if (owner !== track) {
|
|
5509
|
+
while (++i < n) if (!owners.has(ids[i])) owners.set(ids[i], track);
|
|
5510
|
+
return CURRENT;
|
|
5511
|
+
}
|
|
5512
|
+
from = FIRST;
|
|
5513
|
+
}
|
|
5514
|
+
}
|
|
5515
|
+
return from;
|
|
5516
|
+
}
|
|
5517
|
+
// -------------------------------------------------------------------------
|
|
5518
|
+
// the timelines' `apply`
|
|
5519
|
+
// -------------------------------------------------------------------------
|
|
5520
|
+
values(keys, t) {
|
|
5521
|
+
const index = (this.plant.search ?? keyIndexAt)(keys, t);
|
|
5522
|
+
if (index < 0) return null;
|
|
5523
|
+
const channel2 = this.plant.channel ?? channelAt;
|
|
5524
|
+
return keys[index].values.map((_v, c) => channel2(keys, index, c, t));
|
|
5525
|
+
}
|
|
5526
|
+
/** `Timeline.apply` for every timeline but an attachment's and a rotation mixed below full weight. */
|
|
5527
|
+
applyTimeline(pose, tl, lastTime, time, fire, alpha, from, out) {
|
|
5528
|
+
switch (tl.type) {
|
|
5529
|
+
case "bone":
|
|
5530
|
+
return this.applyBone(pose, tl, time, alpha, from, out);
|
|
5531
|
+
case "colour":
|
|
5532
|
+
return this.applyColour(pose, tl, time, alpha, from);
|
|
5533
|
+
case "deform":
|
|
5534
|
+
return this.applyDeform(pose, tl, time, alpha, from);
|
|
5535
|
+
case "events":
|
|
5536
|
+
if (fire) pose.events.push(...eventsFired(tl.keys, lastTime, time, rawNumber, time < lastTime));
|
|
5537
|
+
return;
|
|
5538
|
+
case "attachment":
|
|
5539
|
+
return;
|
|
5540
|
+
}
|
|
5541
|
+
}
|
|
5542
|
+
applyBone(pose, tl, time, alpha, from, out) {
|
|
5543
|
+
const bone2 = pose.bone(tl.bone);
|
|
5544
|
+
if (bone2 === null) return;
|
|
5545
|
+
const setup = this.doc.bones[tl.bone];
|
|
5546
|
+
if (tl.kind === "inherit") {
|
|
5547
|
+
const restore = () => {
|
|
5548
|
+
if (setup.inheritMode === void 0) delete bone2.inheritMode;
|
|
5549
|
+
else bone2.inheritMode = setup.inheritMode;
|
|
5550
|
+
};
|
|
5551
|
+
if (out) {
|
|
5552
|
+
if (from !== CURRENT) restore();
|
|
5553
|
+
return;
|
|
5554
|
+
}
|
|
5555
|
+
const index = (this.plant.search ?? keyIndexAt)(tl.keys, time);
|
|
5556
|
+
if (index < 0) {
|
|
5557
|
+
if (from !== CURRENT) restore();
|
|
5558
|
+
} else bone2.inheritMode = tl.keys[index].mode;
|
|
5559
|
+
return;
|
|
5560
|
+
}
|
|
5561
|
+
const v = this.values(tl.keys, time);
|
|
5562
|
+
const relative = (field, i) => {
|
|
5563
|
+
const s = setup[field] ?? 0;
|
|
5564
|
+
const c = bone2[field] ?? 0;
|
|
5565
|
+
bone2[field] = v === null ? beforeFirstKey(from, alpha, c, s) : from === SETUP ? s + v[i] * alpha : c + (v[i] + s - c) * alpha;
|
|
5566
|
+
};
|
|
5567
|
+
const pair = (fx, fy) => {
|
|
5568
|
+
const sx = setup[fx] ?? 0;
|
|
5569
|
+
const sy = setup[fy] ?? 0;
|
|
5570
|
+
if (v === null) {
|
|
5571
|
+
if (from === SETUP) {
|
|
5572
|
+
bone2[fx] = sx;
|
|
5573
|
+
bone2[fy] = sy;
|
|
5574
|
+
} else if (from === FIRST) {
|
|
5575
|
+
bone2[fx] = (bone2[fx] ?? 0) + (sx - (bone2[fx] ?? 0)) * alpha;
|
|
5576
|
+
bone2[fy] = (bone2[fy] ?? 0) + (sy - (bone2[fy] ?? 0)) * alpha;
|
|
5577
|
+
}
|
|
5578
|
+
return;
|
|
5579
|
+
}
|
|
5580
|
+
if (from === SETUP) {
|
|
5581
|
+
bone2[fx] = sx + v[0] * alpha;
|
|
5582
|
+
bone2[fy] = sy + v[1] * alpha;
|
|
5583
|
+
} else {
|
|
5584
|
+
bone2[fx] = (bone2[fx] ?? 0) + (sx + v[0] - (bone2[fx] ?? 0)) * alpha;
|
|
5585
|
+
bone2[fy] = (bone2[fy] ?? 0) + (sy + v[1] - (bone2[fy] ?? 0)) * alpha;
|
|
5586
|
+
}
|
|
5587
|
+
};
|
|
5588
|
+
const scale = (field, i) => {
|
|
5589
|
+
const s = setup[field] ?? 1;
|
|
5590
|
+
const c = bone2[field] ?? 1;
|
|
5591
|
+
bone2[field] = v === null ? beforeFirstKey(from, alpha, c, s) : scaleValue(v[i] * s, alpha, from, out, c, s);
|
|
5592
|
+
};
|
|
5593
|
+
switch (tl.kind) {
|
|
5594
|
+
case "rotate":
|
|
5595
|
+
relative("rotation", 0);
|
|
5596
|
+
break;
|
|
5597
|
+
case "translate":
|
|
5598
|
+
pair("x", "y");
|
|
5599
|
+
break;
|
|
5600
|
+
case "translatex":
|
|
5601
|
+
relative("x", 0);
|
|
5602
|
+
break;
|
|
5603
|
+
case "translatey":
|
|
5604
|
+
relative("y", 0);
|
|
5605
|
+
break;
|
|
5606
|
+
case "shear":
|
|
5607
|
+
pair("shearX", "shearY");
|
|
5608
|
+
break;
|
|
5609
|
+
case "shearx":
|
|
5610
|
+
relative("shearX", 0);
|
|
5611
|
+
break;
|
|
5612
|
+
case "sheary":
|
|
5613
|
+
relative("shearY", 0);
|
|
5614
|
+
break;
|
|
5615
|
+
case "scale":
|
|
5616
|
+
if (v === null) {
|
|
5617
|
+
const sx = setup.scaleX ?? 1;
|
|
5618
|
+
const sy = setup.scaleY ?? 1;
|
|
5619
|
+
if (from === SETUP) {
|
|
5620
|
+
bone2.scaleX = sx;
|
|
5621
|
+
bone2.scaleY = sy;
|
|
5622
|
+
} else if (from === FIRST) {
|
|
5623
|
+
bone2.scaleX = (bone2.scaleX ?? 1) + (sx - (bone2.scaleX ?? 1)) * alpha;
|
|
5624
|
+
bone2.scaleY = (bone2.scaleY ?? 1) + (sy - (bone2.scaleY ?? 1)) * alpha;
|
|
5625
|
+
}
|
|
5626
|
+
} else {
|
|
5627
|
+
bone2.scaleX = scaleValue(v[0] * (setup.scaleX ?? 1), alpha, from, out, bone2.scaleX ?? 1, setup.scaleX ?? 1);
|
|
5628
|
+
bone2.scaleY = scaleValue(v[1] * (setup.scaleY ?? 1), alpha, from, out, bone2.scaleY ?? 1, setup.scaleY ?? 1);
|
|
5629
|
+
}
|
|
5630
|
+
break;
|
|
5631
|
+
case "scalex":
|
|
5632
|
+
scale("scaleX", 0);
|
|
5633
|
+
break;
|
|
5634
|
+
case "scaley":
|
|
5635
|
+
scale("scaleY", 0);
|
|
5636
|
+
break;
|
|
5637
|
+
}
|
|
5638
|
+
}
|
|
5639
|
+
/** `applyRotateTimeline`: a rotation mixed below full weight turns the way it first turned. */
|
|
5640
|
+
applyRotate(pose, tl, time, alpha, from, rotations, i, firstFrame) {
|
|
5641
|
+
if (firstFrame) rotations[i] = 0;
|
|
5642
|
+
if (alpha === 1) {
|
|
5643
|
+
this.applyBone(pose, tl, time, 1, from, false);
|
|
5644
|
+
return;
|
|
5645
|
+
}
|
|
5646
|
+
const bone2 = pose.bone(tl.bone);
|
|
5647
|
+
if (bone2 === null) return;
|
|
5648
|
+
const setup = this.doc.bones[tl.bone].rotation ?? 0;
|
|
5649
|
+
const v = this.values(tl.keys, time);
|
|
5650
|
+
let r1;
|
|
5651
|
+
let r2;
|
|
5652
|
+
if (v === null) {
|
|
5653
|
+
if (from === SETUP) {
|
|
5654
|
+
bone2.rotation = setup;
|
|
5655
|
+
return;
|
|
5656
|
+
}
|
|
5657
|
+
if (from === CURRENT) return;
|
|
5658
|
+
r1 = bone2.rotation ?? 0;
|
|
5659
|
+
r2 = setup;
|
|
5660
|
+
} else {
|
|
5661
|
+
r1 = from === SETUP ? setup : bone2.rotation ?? 0;
|
|
5662
|
+
r2 = setup + v[0];
|
|
5663
|
+
}
|
|
5664
|
+
let total;
|
|
5665
|
+
let diff = r2 - r1;
|
|
5666
|
+
diff -= Math.ceil(diff / 360 - 0.5) * 360;
|
|
5667
|
+
if (diff === 0) total = rotations[i];
|
|
5668
|
+
else {
|
|
5669
|
+
let lastTotal;
|
|
5670
|
+
let lastDiff;
|
|
5671
|
+
if (firstFrame) {
|
|
5672
|
+
lastTotal = 0;
|
|
5673
|
+
lastDiff = diff;
|
|
5674
|
+
} else {
|
|
5675
|
+
lastTotal = rotations[i];
|
|
5676
|
+
lastDiff = rotations[i + 1] ?? Number.NaN;
|
|
5677
|
+
}
|
|
5678
|
+
const loops = lastTotal - lastTotal % 360;
|
|
5679
|
+
total = diff + loops;
|
|
5680
|
+
const current = diff >= 0;
|
|
5681
|
+
let dir = lastTotal >= 0;
|
|
5682
|
+
if (Math.abs(lastDiff) <= 90 && signum(lastDiff) !== signum(diff)) {
|
|
5683
|
+
if (Math.abs(lastTotal - loops) > 180) {
|
|
5684
|
+
total += 360 * signum(lastTotal);
|
|
5685
|
+
dir = current;
|
|
5686
|
+
} else if (loops !== 0) total -= 360 * signum(lastTotal);
|
|
5687
|
+
else dir = current;
|
|
5688
|
+
}
|
|
5689
|
+
if (dir !== current) total += 360 * signum(lastTotal);
|
|
5690
|
+
rotations[i] = total;
|
|
5691
|
+
}
|
|
5692
|
+
rotations[i + 1] = diff;
|
|
5693
|
+
bone2.rotation = r1 + total * alpha;
|
|
5694
|
+
}
|
|
5695
|
+
/** `applyAttachmentTimeline`: shown for good when retained, else for the deform timelines only and set back at the end of the apply. */
|
|
5696
|
+
applyAttachment(pose, tl, time, from, retain) {
|
|
5697
|
+
if (!pose.slotLive(tl.slot)) return;
|
|
5698
|
+
const s = pose.slots[tl.slot];
|
|
5699
|
+
if (!retain && s.attachmentState === ATTACH_RETAIN) return;
|
|
5700
|
+
const index = (this.plant.search ?? keyIndexAt)(tl.keys, time);
|
|
5701
|
+
let setup = index < 0;
|
|
5702
|
+
let name = null;
|
|
5703
|
+
if (!setup) {
|
|
5704
|
+
name = tl.keys[index].name ?? null;
|
|
5705
|
+
setup = !retain && name === null;
|
|
5706
|
+
}
|
|
5707
|
+
if (setup) {
|
|
5708
|
+
if (from === CURRENT) return;
|
|
5709
|
+
name = this.doc.slots[tl.slot].setup;
|
|
5710
|
+
}
|
|
5711
|
+
pose.setAttachment(tl.slot, name);
|
|
5712
|
+
if (retain) s.attachmentState = ATTACH_RETAIN;
|
|
5713
|
+
else if (!setup) s.attachmentState = ATTACH_SETUP;
|
|
5714
|
+
}
|
|
5715
|
+
applyColour(pose, tl, time, alpha, from) {
|
|
5716
|
+
if (!pose.slotLive(tl.slot)) return;
|
|
5717
|
+
const slot = this.doc.slots[tl.slot];
|
|
5718
|
+
const s = pose.slots[tl.slot];
|
|
5719
|
+
const l = s.light;
|
|
5720
|
+
const d = s.dark;
|
|
5721
|
+
const sl = slot.color === void 0 ? [1, 1, 1, 1] : (this.plant.colour ?? readColour)(slot.color);
|
|
5722
|
+
const sdFull = slot.dark === void 0 ? null : readColour(slot.dark);
|
|
5723
|
+
const sd = sdFull === null ? [0, 0, 0] : [sdFull[0], sdFull[1], sdFull[2]];
|
|
5724
|
+
const clampLight = () => {
|
|
5725
|
+
for (let k = 0; k < 4; k++) l[k] = clamp01(l[k]);
|
|
5726
|
+
};
|
|
5727
|
+
const v = this.values(tl.keys, time);
|
|
5728
|
+
const channels = SLOT_COLOUR_TIMELINES[tl.kind].channels.length;
|
|
5729
|
+
switch (tl.kind) {
|
|
5730
|
+
case "rgba": {
|
|
5731
|
+
if (v === null) {
|
|
5732
|
+
if (from === SETUP) for (let k = 0; k < 4; k++) l[k] = sl[k];
|
|
5733
|
+
else if (from === FIRST) {
|
|
5734
|
+
for (let k = 0; k < 4; k++) l[k] += (sl[k] - l[k]) * alpha;
|
|
5735
|
+
clampLight();
|
|
5736
|
+
}
|
|
5737
|
+
return;
|
|
5738
|
+
}
|
|
5739
|
+
if (alpha === 1) for (let k = 0; k < 4; k++) l[k] = v[k];
|
|
5740
|
+
else if (from === SETUP) for (let k = 0; k < 4; k++) l[k] = sl[k] + (v[k] - sl[k]) * alpha;
|
|
5741
|
+
else for (let k = 0; k < 4; k++) l[k] += (v[k] - l[k]) * alpha;
|
|
5742
|
+
clampLight();
|
|
5743
|
+
return;
|
|
5744
|
+
}
|
|
5745
|
+
case "rgb": {
|
|
5746
|
+
if (v === null) {
|
|
5747
|
+
if (from === SETUP) for (let k = 0; k < 3; k++) l[k] = sl[k];
|
|
5748
|
+
else if (from === FIRST) for (let k = 0; k < 3; k++) l[k] += (sl[k] - l[k]) * alpha;
|
|
5749
|
+
return;
|
|
5750
|
+
}
|
|
5751
|
+
const rgb = [v[0], v[1], v[2]];
|
|
5752
|
+
if (alpha !== 1) for (let k = 0; k < 3; k++) rgb[k] = from === SETUP ? sl[k] + (rgb[k] - sl[k]) * alpha : l[k] + (rgb[k] - l[k]) * alpha;
|
|
5753
|
+
for (let k = 0; k < 3; k++) l[k] = clamp01(rgb[k]);
|
|
5754
|
+
return;
|
|
5755
|
+
}
|
|
5756
|
+
case "alpha": {
|
|
5757
|
+
if (v === null) {
|
|
5758
|
+
if (from === SETUP) l[3] = sl[3];
|
|
5759
|
+
else if (from === FIRST) l[3] += (sl[3] - l[3]) * alpha;
|
|
5760
|
+
return;
|
|
5761
|
+
}
|
|
5762
|
+
let a = v[0];
|
|
5763
|
+
if (alpha !== 1) a = from === SETUP ? sl[3] + (a - sl[3]) * alpha : l[3] + (a - l[3]) * alpha;
|
|
5764
|
+
l[3] = clamp01(a);
|
|
5765
|
+
return;
|
|
5766
|
+
}
|
|
5767
|
+
case "rgba2":
|
|
5768
|
+
case "rgb2": {
|
|
5769
|
+
if (d === null) return;
|
|
5770
|
+
const withAlpha = tl.kind === "rgba2";
|
|
5771
|
+
const lightN = withAlpha ? 4 : 3;
|
|
5772
|
+
if (v === null) {
|
|
5773
|
+
if (from === SETUP) {
|
|
5774
|
+
for (let k = 0; k < lightN; k++) l[k] = sl[k];
|
|
5775
|
+
for (let k = 0; k < 3; k++) d[k] = sd[k];
|
|
5776
|
+
} else if (from === FIRST) {
|
|
5777
|
+
for (let k = 0; k < lightN; k++) l[k] += (sl[k] - l[k]) * alpha;
|
|
5778
|
+
if (withAlpha) clampLight();
|
|
5779
|
+
for (let k = 0; k < 3; k++) d[k] += (sd[k] - d[k]) * alpha;
|
|
5780
|
+
}
|
|
5781
|
+
return;
|
|
5782
|
+
}
|
|
5783
|
+
const light = v.slice(0, lightN);
|
|
5784
|
+
const dark = v.slice(lightN, channels);
|
|
5785
|
+
if (withAlpha) {
|
|
5786
|
+
if (alpha === 1) for (let k = 0; k < 4; k++) l[k] = light[k];
|
|
5787
|
+
else if (from === SETUP) {
|
|
5788
|
+
for (let k = 0; k < 4; k++) l[k] = sl[k] + (light[k] - sl[k]) * alpha;
|
|
5789
|
+
for (let k = 0; k < 3; k++) dark[k] = sd[k] + (dark[k] - sd[k]) * alpha;
|
|
5790
|
+
} else {
|
|
5791
|
+
for (let k = 0; k < 4; k++) l[k] += (light[k] - l[k]) * alpha;
|
|
5792
|
+
for (let k = 0; k < 3; k++) dark[k] = d[k] + (dark[k] - d[k]) * alpha;
|
|
5793
|
+
}
|
|
5794
|
+
clampLight();
|
|
5795
|
+
} else {
|
|
5796
|
+
if (alpha !== 1) {
|
|
5797
|
+
for (let k = 0; k < 3; k++) {
|
|
5798
|
+
light[k] = from === SETUP ? sl[k] + (light[k] - sl[k]) * alpha : l[k] + (light[k] - l[k]) * alpha;
|
|
5799
|
+
dark[k] = from === SETUP ? sd[k] + (dark[k] - sd[k]) * alpha : d[k] + (dark[k] - d[k]) * alpha;
|
|
5800
|
+
}
|
|
5801
|
+
}
|
|
5802
|
+
for (let k = 0; k < 3; k++) l[k] = clamp01(light[k]);
|
|
5803
|
+
}
|
|
5804
|
+
for (let k = 0; k < 3; k++) d[k] = clamp01(dark[k]);
|
|
5805
|
+
return;
|
|
5806
|
+
}
|
|
5807
|
+
}
|
|
5808
|
+
}
|
|
5809
|
+
/** `DeformTimeline.apply`: every live slot showing the record the timeline keys, its deform array mixed. */
|
|
5810
|
+
applyDeform(pose, tl, time, alpha, from) {
|
|
5811
|
+
const keys = tl.timeline.deform;
|
|
5812
|
+
if (keys === null) return;
|
|
5813
|
+
pose.slots.forEach((s, k) => {
|
|
5814
|
+
if (!pose.slotLive(k)) return;
|
|
5815
|
+
const shown = pose.resolve(k, s.placeholder);
|
|
5816
|
+
if (!shown.vertex || shown.timeline !== tl.identity || shown.vertices === null) return;
|
|
5817
|
+
const vertices = shown.vertices;
|
|
5818
|
+
if (!s.seen.includes(tl.identity)) s.seen.push(tl.identity);
|
|
5819
|
+
const target = (this.plant.deform ?? deformAt)(vertices, keys, time);
|
|
5820
|
+
let mixFrom = from;
|
|
5821
|
+
if (s.deform === null) mixFrom = SETUP;
|
|
5822
|
+
if (target === null) {
|
|
5823
|
+
if (mixFrom === SETUP) s.deform = null;
|
|
5824
|
+
else if (mixFrom === FIRST) {
|
|
5825
|
+
if (alpha === 1) {
|
|
5826
|
+
s.deform = null;
|
|
5827
|
+
return;
|
|
5828
|
+
}
|
|
5829
|
+
const current = s.deform;
|
|
5830
|
+
if (!vertices.weighted) {
|
|
5831
|
+
const setup = setupArray(vertices);
|
|
5832
|
+
s.deform = current.map((c, i) => c + (setup[i] - c) * alpha);
|
|
5833
|
+
} else {
|
|
5834
|
+
const keep = 1 - alpha;
|
|
5835
|
+
s.deform = current.map((c) => c * keep);
|
|
5836
|
+
}
|
|
5837
|
+
}
|
|
5838
|
+
return;
|
|
5839
|
+
}
|
|
5840
|
+
if (alpha === 1) s.deform = [...target];
|
|
5841
|
+
else if (mixFrom === SETUP) {
|
|
5842
|
+
if (!vertices.weighted) {
|
|
5843
|
+
const setup = setupArray(vertices);
|
|
5844
|
+
s.deform = target.map((v, i) => setup[i] + (v - setup[i]) * alpha);
|
|
5845
|
+
} else s.deform = target.map((v) => v * alpha);
|
|
5846
|
+
} else {
|
|
5847
|
+
const current = s.deform;
|
|
5848
|
+
s.deform = current.map((c, i) => c + (target[i] - c) * alpha);
|
|
5849
|
+
}
|
|
5850
|
+
});
|
|
5851
|
+
}
|
|
5852
|
+
}
|
|
5853
|
+
function beforeFirstKey(from, alpha, current, setup) {
|
|
5854
|
+
if (from === SETUP) return setup;
|
|
5855
|
+
if (from === FIRST) return current + (setup - current) * alpha;
|
|
5856
|
+
return current;
|
|
5857
|
+
}
|
|
5858
|
+
function scaleValue(value, alpha, from, out, current, setup) {
|
|
5859
|
+
if (alpha === 1) return value;
|
|
5860
|
+
let base = from === SETUP ? setup : current;
|
|
5861
|
+
if (out) return base + (Math.abs(value) * Math.sign(base) - base) * alpha;
|
|
5862
|
+
base = Math.abs(base) * Math.sign(value);
|
|
5863
|
+
return base + (value - base) * alpha;
|
|
5864
|
+
}
|
|
5865
|
+
function hasAny(anim, ids) {
|
|
5866
|
+
for (const id of ids) if (anim.ids.has(id)) return true;
|
|
5867
|
+
return false;
|
|
5868
|
+
}
|
|
5869
|
+
function trackIndex(track, call) {
|
|
5870
|
+
if (typeof track !== "number" || !Number.isInteger(track) || track < 0) throw new CoreInputError(`the live tracks' ${call} names track ${String(track)}, not a whole number at or above 0`);
|
|
5871
|
+
return track;
|
|
5872
|
+
}
|
|
5873
|
+
function finiteAtOrAbove0(v, what, call) {
|
|
5874
|
+
if (typeof v !== "number" || !Number.isFinite(v) || v < 0) throw new CoreInputError(`the live tracks' ${call} ${what} is ${String(v)}, not a finite time at or above 0`);
|
|
5875
|
+
return v;
|
|
5876
|
+
}
|
|
5877
|
+
function loopOf(options, call) {
|
|
5878
|
+
if (typeof options !== "object" || options === null || typeof options.loop !== "boolean") throw new CoreInputError(`the live tracks' ${call} options.loop is ${JSON.stringify(options?.loop)}, not true or false`);
|
|
5879
|
+
return options.loop;
|
|
5880
|
+
}
|
|
5881
|
+
function delayOf(v, call) {
|
|
5882
|
+
if (typeof v !== "number" || !Number.isFinite(v)) throw new CoreInputError(`the live tracks' ${call} delay is ${String(v)}, not a finite number`);
|
|
5883
|
+
return v;
|
|
5884
|
+
}
|
|
5885
|
+
function openTracks(doc, options, plant = {}) {
|
|
5886
|
+
if (typeof options !== "object" || options === null || options.reset !== "animation" && options.reset !== "setup") throw new CoreInputError(`the live tracks' options.reset is ${JSON.stringify(options?.reset)}, not "animation" or "setup"`);
|
|
5887
|
+
if (options.adjust !== void 0 && typeof options.adjust !== "function") throw new CoreInputError(`the live tracks' options.adjust is ${typeof options.adjust}, not a function`);
|
|
5888
|
+
return new Tracks(doc, options.reset, options.adjust, plant, openRawTracksPath(doc, plant));
|
|
5889
|
+
}
|
|
5890
|
+
class Tracks {
|
|
5891
|
+
constructor(doc, reset, adjust, plant, path) {
|
|
5892
|
+
this.doc = doc;
|
|
5893
|
+
this.reset = reset;
|
|
5894
|
+
this.adjust = adjust;
|
|
5895
|
+
this.plant = plant;
|
|
5896
|
+
this.path = path;
|
|
5897
|
+
this.started = false;
|
|
5898
|
+
this.steps = 0;
|
|
5899
|
+
this.time = 0;
|
|
5900
|
+
this.refused = null;
|
|
5901
|
+
this.mixer = new Mixer(doc, plant);
|
|
5902
|
+
}
|
|
5903
|
+
get trackTime() {
|
|
5904
|
+
return this.time;
|
|
5905
|
+
}
|
|
5906
|
+
live(call) {
|
|
5907
|
+
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)`);
|
|
5908
|
+
}
|
|
5909
|
+
setAnimation(track, animation, options) {
|
|
5910
|
+
this.live("setAnimation");
|
|
5911
|
+
const i = trackIndex(track, "setAnimation");
|
|
5912
|
+
const loop = loopOf(options, "setAnimation");
|
|
5913
|
+
const mix = finiteAtOrAbove0(options.mixDuration, "mixDuration", "setAnimation");
|
|
5914
|
+
const entry = this.mixer.setAnimation(i, this.mixer.animation(animation), loop);
|
|
5915
|
+
entry.mixDuration = mix;
|
|
5916
|
+
}
|
|
5917
|
+
setEmptyAnimation(track, mixDuration) {
|
|
5918
|
+
this.live("setEmptyAnimation");
|
|
5919
|
+
const i = trackIndex(track, "setEmptyAnimation");
|
|
5920
|
+
const mix = finiteAtOrAbove0(mixDuration, "mixDuration", "setEmptyAnimation");
|
|
5921
|
+
const entry = this.mixer.setAnimation(i, EMPTY, false);
|
|
5922
|
+
entry.mixDuration = mix;
|
|
5923
|
+
entry.trackEnd = mix;
|
|
5924
|
+
}
|
|
5925
|
+
addAnimation(track, animation, options) {
|
|
5926
|
+
this.live("addAnimation");
|
|
5927
|
+
const i = trackIndex(track, "addAnimation");
|
|
5928
|
+
const loop = loopOf(options, "addAnimation");
|
|
5929
|
+
const mix = finiteAtOrAbove0(options.mixDuration, "mixDuration", "addAnimation");
|
|
5930
|
+
const delay = delayOf(options.delay, "addAnimation");
|
|
5931
|
+
const entry = this.mixer.addAnimation(i, this.mixer.animation(animation), loop, delay);
|
|
5932
|
+
this.mixer.setMixDuration(entry, mix, delay);
|
|
5933
|
+
}
|
|
5934
|
+
addEmptyAnimation(track, mixDuration, delay) {
|
|
5935
|
+
this.live("addEmptyAnimation");
|
|
5936
|
+
const i = trackIndex(track, "addEmptyAnimation");
|
|
5937
|
+
const mix = finiteAtOrAbove0(mixDuration, "mixDuration", "addEmptyAnimation");
|
|
5938
|
+
const d = delayOf(delay, "addEmptyAnimation");
|
|
5939
|
+
const entry = this.mixer.addAnimation(i, EMPTY, false, d);
|
|
5940
|
+
if (d <= 0) entry.delay = Math.max(entry.delay + entry.mixDuration - mix, 0);
|
|
5941
|
+
entry.mixDuration = mix;
|
|
5942
|
+
entry.trackEnd = mix;
|
|
5943
|
+
}
|
|
5944
|
+
current(track) {
|
|
5945
|
+
const e = this.mixer.tracks[trackIndex(track, "current")] ?? null;
|
|
5946
|
+
if (e === null) return null;
|
|
5947
|
+
return { animation: e.anim.name, loop: e.loop, trackTime: e.trackTime, animationTime: animationTimeOf(e), duration: e.anim.duration };
|
|
5948
|
+
}
|
|
5949
|
+
start() {
|
|
5950
|
+
this.live("start");
|
|
5951
|
+
if (this.started) throw new CoreInputError("the live tracks were started already; pose 0 is posed once");
|
|
5952
|
+
this.started = true;
|
|
5953
|
+
return this.guard("start", () => {
|
|
5954
|
+
if (this.reset === "setup") return this.path.setupPose();
|
|
5955
|
+
return this.pose(0, true);
|
|
5956
|
+
});
|
|
5957
|
+
}
|
|
5958
|
+
step(dt) {
|
|
5959
|
+
const n = this.steps + 1;
|
|
5960
|
+
this.live(`step ${n}`);
|
|
5961
|
+
if (!this.started) throw new CoreInputError(`the live tracks' step ${n} comes before start(), which poses pose 0`);
|
|
5962
|
+
if (typeof dt !== "number" || !Number.isFinite(dt) || dt < 0) throw new CoreInputError(`the live tracks' step ${n} is ${String(dt)}, not a finite time at or above 0`);
|
|
5963
|
+
return this.guard(`step ${n}`, () => {
|
|
5964
|
+
this.time += dt;
|
|
5965
|
+
this.mixer.update(dt);
|
|
5966
|
+
const pose = this.pose(dt, false);
|
|
5967
|
+
this.steps = n;
|
|
5968
|
+
return pose;
|
|
5969
|
+
});
|
|
5970
|
+
}
|
|
5971
|
+
/** `f`, the state refused for good if it throws once anything has moved. */
|
|
5972
|
+
guard(call, f) {
|
|
5973
|
+
try {
|
|
5974
|
+
return f();
|
|
5975
|
+
} catch (err) {
|
|
5976
|
+
this.refused = `${call} (${err instanceof Error ? err.message : String(err)})`;
|
|
5977
|
+
throw err;
|
|
5978
|
+
}
|
|
5979
|
+
}
|
|
5980
|
+
/** One pose: the entries applied over the setup pose, then the raw pose path (`openRawTracksPath`). */
|
|
5981
|
+
pose(dt, first) {
|
|
5982
|
+
const { doc, path } = this;
|
|
5983
|
+
const ctx = path.ctx;
|
|
5984
|
+
const before = ctx.time;
|
|
5985
|
+
ctx.time += dt;
|
|
5986
|
+
const pose = new Pose(doc, this.plant);
|
|
5987
|
+
const animationTime = this.mixer.apply(pose);
|
|
5988
|
+
const mixed = new Map(doc.slots.map((s, k) => [s.name, { deform: pose.slots[k].deform, seen: pose.slots[k].seen }]));
|
|
5989
|
+
return path.step(first, this.time, animationTime, {
|
|
5990
|
+
world: (sliders) => steppedLocalsWorld(doc, pose.bones, path.raw, sliders, { ctx, before, adjust: this.adjust }),
|
|
5991
|
+
slots: (sliders, placeholders) => slotRowsFrom(doc, path.raw, sliders, placeholders, (_slot, _live) => {
|
|
5992
|
+
const s = pose.slots[doc.slots.indexOf(_slot)];
|
|
5993
|
+
return { placeholder: s.placeholder, light: s.light, dark: s.dark };
|
|
5994
|
+
}),
|
|
5995
|
+
drawOrder: [],
|
|
5996
|
+
at: 0,
|
|
5997
|
+
sample: null,
|
|
5998
|
+
mixed,
|
|
5999
|
+
events: () => pose.events
|
|
6000
|
+
});
|
|
6001
|
+
}
|
|
6002
|
+
}
|
|
5009
6003
|
function documentPageLookup(pages) {
|
|
5010
6004
|
const first = /* @__PURE__ */ new Map();
|
|
5011
6005
|
for (const page of pages) {
|
|
@@ -7222,7 +8216,7 @@ function trianglesOf(source, drawn) {
|
|
|
7222
8216
|
}
|
|
7223
8217
|
return kept;
|
|
7224
8218
|
}
|
|
7225
|
-
function clipCover(pose, active) {
|
|
8219
|
+
function clipCover(pose, active, draws = () => true) {
|
|
7226
8220
|
const polygons = new Map(pose.clips.map((c) => [c[0], { end: c[2], polygon: c[3] }]));
|
|
7227
8221
|
const shownOf = new Map(pose.shown.map((s) => [s.slot, s]));
|
|
7228
8222
|
const cover = /* @__PURE__ */ new Map();
|
|
@@ -7233,7 +8227,7 @@ function clipCover(pose, active) {
|
|
|
7233
8227
|
const clip = g?.kind === "clipping" ? polygons.get(slot) : void 0;
|
|
7234
8228
|
if (shown !== void 0 && g?.kind === "clipping" && clip !== void 0) {
|
|
7235
8229
|
if (current !== null && current.end === slot) current = null;
|
|
7236
|
-
if (active.has(shown.bone) && current === null) {
|
|
8230
|
+
if (active.has(shown.bone) && current === null && draws(slot)) {
|
|
7237
8231
|
current = { end: clip.end, clip: { polygon: clip.polygon, inverse: g.inverse, convex: g.convex } };
|
|
7238
8232
|
}
|
|
7239
8233
|
continue;
|
|
@@ -7243,16 +8237,17 @@ function clipCover(pose, active) {
|
|
|
7243
8237
|
}
|
|
7244
8238
|
return cover;
|
|
7245
8239
|
}
|
|
7246
|
-
function frameOf(source, pose, color = [1, 1, 1, 1]) {
|
|
8240
|
+
function frameOf(source, pose, color = [1, 1, 1, 1], draws = () => true) {
|
|
7247
8241
|
const { drawn: regions, why } = drawnRegions(source.doc, pose.shown, pose.drawOrder, source.uv);
|
|
7248
8242
|
if (regions === null) throw new CoreInputError(`the page UVs are not posed: ${why}`);
|
|
7249
8243
|
const regionOf = new Map(regions.map((r) => [r.slot, r]));
|
|
7250
8244
|
const rows = new Map(pose.slots.map((row) => [row[0], row]));
|
|
7251
8245
|
const z = new Map(pose.drawOrder.map((slot, index) => [slot, index]));
|
|
7252
|
-
const cover = clipCover(pose, source.active);
|
|
8246
|
+
const cover = clipCover(pose, source.active, draws);
|
|
7253
8247
|
const [sr, sg, sb, sa] = color;
|
|
7254
8248
|
const order = [];
|
|
7255
8249
|
for (const d of pose.drawn) {
|
|
8250
|
+
if (!draws(d.slot)) continue;
|
|
7256
8251
|
const bone2 = source.boneOf.get(d.slot);
|
|
7257
8252
|
if (bone2 === void 0 || !source.active.has(bone2)) continue;
|
|
7258
8253
|
const found = regionOf.get(d.slot);
|
|
@@ -7300,6 +8295,10 @@ function frameOf(source, pose, color = [1, 1, 1, 1]) {
|
|
|
7300
8295
|
}
|
|
7301
8296
|
return { order };
|
|
7302
8297
|
}
|
|
8298
|
+
function predicateOf(names) {
|
|
8299
|
+
const allowed = new Set(names);
|
|
8300
|
+
return (slot) => allowed.has(slot);
|
|
8301
|
+
}
|
|
7303
8302
|
class RigPlayer {
|
|
7304
8303
|
/**
|
|
7305
8304
|
* The renderer this player draws through — its knobs and counters are the
|
|
@@ -7322,13 +8321,34 @@ class RigPlayer {
|
|
|
7322
8321
|
read;
|
|
7323
8322
|
skinName;
|
|
7324
8323
|
seekStep;
|
|
8324
|
+
/**
|
|
8325
|
+
* The single live track (`openTrack`): what a plain `play` on track 0 runs
|
|
8326
|
+
* on, as every 1.x player did — one animation, every construct the core
|
|
8327
|
+
* poses, `seek` and a skin change by reopening. Null once the player is
|
|
8328
|
+
* composed.
|
|
8329
|
+
*/
|
|
7325
8330
|
track = null;
|
|
8331
|
+
/**
|
|
8332
|
+
* The live tracks (`openTracks`): opened by the first composing call — a
|
|
8333
|
+
* `play` with a mix or on another track, a `queue`, a `clear` — from the
|
|
8334
|
+
* single track's animation stepped to its time. The mixing is the Spine
|
|
8335
|
+
* runtime's, which does not mix a constraint, sequence, draw-order or
|
|
8336
|
+
* path-deform timeline, so such an animation is refused here and plays on
|
|
8337
|
+
* the single track only.
|
|
8338
|
+
*/
|
|
8339
|
+
tracks = null;
|
|
8340
|
+
/** Track 0's animation and loop as `play` last set them — what `seek` and a skin change reopen. */
|
|
7326
8341
|
animation = null;
|
|
7327
8342
|
loop = true;
|
|
8343
|
+
/** One past the highest track touched — how many `current(track)` reads a step takes. */
|
|
8344
|
+
trackCount = 0;
|
|
7328
8345
|
paused = false;
|
|
7329
8346
|
pose = null;
|
|
7330
8347
|
setup = null;
|
|
7331
8348
|
adjustFn = null;
|
|
8349
|
+
slotFilter = null;
|
|
8350
|
+
/** `slotFilter` as a predicate; every slot when null. */
|
|
8351
|
+
draws = () => true;
|
|
7332
8352
|
/** Handed to every track the player opens; reads the current `adjust` so setting it later needs no reopen. */
|
|
7333
8353
|
adjustHook = (locals) => {
|
|
7334
8354
|
if (this.adjustFn !== null) this.adjustFn(locals);
|
|
@@ -7349,6 +8369,7 @@ class RigPlayer {
|
|
|
7349
8369
|
this.assets = assets;
|
|
7350
8370
|
this.meshBackend = options.meshBackend;
|
|
7351
8371
|
this.adjustFn = options.adjust ?? null;
|
|
8372
|
+
if (options.slots !== void 0 && options.slots !== null) this.slots = options.slots;
|
|
7352
8373
|
this.active = this.create(options.mode ?? "dom");
|
|
7353
8374
|
}
|
|
7354
8375
|
/**
|
|
@@ -7388,23 +8409,118 @@ class RigPlayer {
|
|
|
7388
8409
|
if (this.meshBackend) renderer.meshBackend = this.meshBackend;
|
|
7389
8410
|
return renderer;
|
|
7390
8411
|
}
|
|
7391
|
-
/**
|
|
8412
|
+
/** What track 0 plays, or null before `play` and after a `clear` of track 0 has mixed out. */
|
|
7392
8413
|
get current() {
|
|
7393
|
-
return this.animation;
|
|
8414
|
+
return this.tracks !== null ? this.tracks.current(0)?.animation ?? null : this.animation;
|
|
7394
8415
|
}
|
|
7395
|
-
/** The
|
|
8416
|
+
/** The player's time — the running sum of the steps since `play` (or the last `seek`). */
|
|
7396
8417
|
get time() {
|
|
7397
|
-
return this.track?.trackTime ?? 0;
|
|
8418
|
+
return this.tracks?.trackTime ?? this.track?.trackTime ?? 0;
|
|
7398
8419
|
}
|
|
7399
8420
|
/**
|
|
7400
|
-
*
|
|
7401
|
-
*
|
|
8421
|
+
* Plays `name` on a track — track 0 by default, which is what `play(name)`
|
|
8422
|
+
* has always meant: the first `play` opens the tracks at their first pose
|
|
8423
|
+
* with the physics reset there, and the pose is drawn on the next `frame`.
|
|
8424
|
+
* Later calls replace what the track plays: the entry it replaces mixes out
|
|
8425
|
+
* over `mix` seconds (0 cuts), and every entry queued on that track is
|
|
8426
|
+
* dropped. The pose the player holds is re-posed at once, so a held player
|
|
8427
|
+
* shows the change on its next `draw`. The mixing is the Spine runtime's,
|
|
8428
|
+
* which rig-c holds at tolerance 0; an animation keying what the tracks do
|
|
8429
|
+
* not mix — a constraint, a sequence, the draw order, a path's deform — is
|
|
8430
|
+
* refused here, by name, in the core's words.
|
|
7402
8431
|
*/
|
|
7403
8432
|
play(name, options = {}) {
|
|
7404
|
-
|
|
7405
|
-
|
|
8433
|
+
const track = options.track ?? 0;
|
|
8434
|
+
const loop = options.loop ?? true;
|
|
8435
|
+
const mix = options.mix ?? 0;
|
|
7406
8436
|
this.paused = false;
|
|
7407
|
-
this.
|
|
8437
|
+
if (this.tracks === null && track === 0 && mix === 0) {
|
|
8438
|
+
this.animation = name;
|
|
8439
|
+
this.loop = loop;
|
|
8440
|
+
this.track = openTrack(this.view, name, { loop, reset: "animation", adjust: this.adjustHook });
|
|
8441
|
+
this.pose = this.track.first;
|
|
8442
|
+
return;
|
|
8443
|
+
}
|
|
8444
|
+
const tracks = this.compose();
|
|
8445
|
+
tracks.setAnimation(track, name, { loop, mixDuration: mix });
|
|
8446
|
+
this.trackCount = Math.max(this.trackCount, track + 1);
|
|
8447
|
+
if (track === 0) {
|
|
8448
|
+
this.animation = name;
|
|
8449
|
+
this.loop = loop;
|
|
8450
|
+
}
|
|
8451
|
+
this.pose = tracks.step(0);
|
|
8452
|
+
}
|
|
8453
|
+
/**
|
|
8454
|
+
* Queues `name` after the last entry on `track` (`QueueOptions`): with
|
|
8455
|
+
* `delay` at or below 0 it starts when that entry completes less its own
|
|
8456
|
+
* `mix`, so a one-shot hands over to it across a mix; on an empty track it
|
|
8457
|
+
* is set. Before any `play`, the tracks are opened for it.
|
|
8458
|
+
*/
|
|
8459
|
+
queue(name, options = {}) {
|
|
8460
|
+
const track = options.track ?? 0;
|
|
8461
|
+
const tracks = this.compose();
|
|
8462
|
+
tracks.addAnimation(track, name, { loop: options.loop ?? true, mixDuration: options.mix ?? 0, delay: options.delay ?? 0 });
|
|
8463
|
+
this.trackCount = Math.max(this.trackCount, track + 1);
|
|
8464
|
+
this.pose = tracks.step(0);
|
|
8465
|
+
}
|
|
8466
|
+
/**
|
|
8467
|
+
* Mixes `track` out to nothing over `mix` seconds — the layer or one-shot
|
|
8468
|
+
* gone and the tracks below showing through. Track 0 cleared shows the
|
|
8469
|
+
* setup pose. The pose the player holds is re-posed at once.
|
|
8470
|
+
*/
|
|
8471
|
+
clear(track = 0, options = {}) {
|
|
8472
|
+
const tracks = this.compose();
|
|
8473
|
+
tracks.setEmptyAnimation(track, options.mix ?? 0);
|
|
8474
|
+
this.trackCount = Math.max(this.trackCount, track + 1);
|
|
8475
|
+
this.pose = tracks.step(0);
|
|
8476
|
+
}
|
|
8477
|
+
/**
|
|
8478
|
+
* Queues a mix out to nothing after the last entry on `track`, over `mix`
|
|
8479
|
+
* seconds; with `delay` 0 (the default) the mix ends as that entry
|
|
8480
|
+
* completes, so a one-shot queued this way plays once and fades out over
|
|
8481
|
+
* its last `mix` seconds.
|
|
8482
|
+
*/
|
|
8483
|
+
queueClear(track = 0, options = {}) {
|
|
8484
|
+
const tracks = this.compose();
|
|
8485
|
+
tracks.addEmptyAnimation(track, options.mix ?? 0, options.delay ?? 0);
|
|
8486
|
+
this.trackCount = Math.max(this.trackCount, track + 1);
|
|
8487
|
+
this.pose = tracks.step(0);
|
|
8488
|
+
}
|
|
8489
|
+
/** `track`'s current entry, or null where it plays nothing (and before `play`). */
|
|
8490
|
+
entry(track = 0) {
|
|
8491
|
+
if (this.tracks !== null) {
|
|
8492
|
+
const view = this.tracks.current(track);
|
|
8493
|
+
return view === null ? null : { animation: view.animation, loop: view.loop, trackTime: view.trackTime, animationTime: view.animationTime, duration: view.duration };
|
|
8494
|
+
}
|
|
8495
|
+
const single = this.track;
|
|
8496
|
+
if (track !== 0 || single === null) return null;
|
|
8497
|
+
return { animation: this.animation, loop: this.loop, trackTime: single.trackTime, animationTime: single.animationTime, duration: single.duration };
|
|
8498
|
+
}
|
|
8499
|
+
/** Whether the player is composed: on the live tracks rather than the single track. */
|
|
8500
|
+
get composed() {
|
|
8501
|
+
return this.tracks !== null;
|
|
8502
|
+
}
|
|
8503
|
+
/**
|
|
8504
|
+
* The live tracks, opened by the first composing call. A single track in
|
|
8505
|
+
* play is carried over by replay: track 0 set to its animation, pose 0 taken
|
|
8506
|
+
* by `start()` (the player's — nothing steps before it) and the tracks
|
|
8507
|
+
* stepped to the single track's time in `seekStep` increments, so a physics
|
|
8508
|
+
* rig's history restarts there and nothing is fired on the way.
|
|
8509
|
+
*/
|
|
8510
|
+
compose() {
|
|
8511
|
+
if (this.tracks !== null) return this.tracks;
|
|
8512
|
+
const tracks = openTracks(this.view, { reset: "animation", adjust: this.adjustHook });
|
|
8513
|
+
const single = this.track;
|
|
8514
|
+
const time = single?.trackTime ?? 0;
|
|
8515
|
+
if (single !== null && this.animation !== null) {
|
|
8516
|
+
tracks.setAnimation(0, this.animation, { loop: this.loop, mixDuration: 0 });
|
|
8517
|
+
this.trackCount = 1;
|
|
8518
|
+
}
|
|
8519
|
+
this.tracks = tracks;
|
|
8520
|
+
this.track = null;
|
|
8521
|
+
this.pose = tracks.start();
|
|
8522
|
+
if (time > 0) this.stepTo(time);
|
|
8523
|
+
return tracks;
|
|
7408
8524
|
}
|
|
7409
8525
|
/** Stops stepping; `frame` keeps drawing the pose it holds. */
|
|
7410
8526
|
pause() {
|
|
@@ -7415,23 +8531,33 @@ class RigPlayer {
|
|
|
7415
8531
|
this.paused = false;
|
|
7416
8532
|
}
|
|
7417
8533
|
/**
|
|
7418
|
-
* Moves
|
|
7419
|
-
* in `seekStep` increments (the
|
|
7420
|
-
*
|
|
8534
|
+
* Moves a single animation on track 0 to `time`: the tracks are reopened
|
|
8535
|
+
* with that animation and stepped forward in `seekStep` increments (the
|
|
8536
|
+
* last one shorter) until the time is reached. Events and completes crossed
|
|
8537
|
+
* on the way are not fired. It has a meaning for one animation on one
|
|
8538
|
+
* track only — the player cannot replay when a mix, a second track or a
|
|
8539
|
+
* queue was asked for — so after any of those it refuses by name; step the
|
|
8540
|
+
* player forward instead.
|
|
7421
8541
|
*/
|
|
7422
8542
|
seek(time) {
|
|
7423
8543
|
if (this.animation === null) throw new Error("seek before play: no animation is open");
|
|
7424
8544
|
if (!(time >= 0) || !Number.isFinite(time)) throw new Error(`seek time must be a finite time at or above 0, got ${String(time)}`);
|
|
7425
|
-
this.
|
|
8545
|
+
if (this.tracks !== null) throw new Error("seek is defined for a single animation on track 0; after a mix, another track, a queue or a clear the player cannot replay to a time — step it forward with frame/advance instead");
|
|
8546
|
+
this.reopen();
|
|
7426
8547
|
this.stepTo(time);
|
|
7427
8548
|
}
|
|
7428
|
-
/**
|
|
8549
|
+
/** The single track opened again with track 0's animation as `play` last set it, at pose 0. */
|
|
8550
|
+
reopen() {
|
|
8551
|
+
this.track = openTrack(this.view, this.animation, { loop: this.loop, reset: "animation", adjust: this.adjustHook });
|
|
8552
|
+
this.pose = this.track.first;
|
|
8553
|
+
}
|
|
8554
|
+
/** Steps whatever is open forward to `time` in `seekStep` increments, the last one shorter. */
|
|
7429
8555
|
stepTo(time) {
|
|
7430
|
-
const
|
|
8556
|
+
const stepper = this.tracks ?? this.track;
|
|
7431
8557
|
const steps = Math.floor(time / this.seekStep);
|
|
7432
|
-
for (let i = 0; i < steps; i++) this.pose =
|
|
7433
|
-
const rest = time -
|
|
7434
|
-
if (rest > 0) this.pose =
|
|
8558
|
+
for (let i = 0; i < steps; i++) this.pose = stepper.step(this.seekStep);
|
|
8559
|
+
const rest = time - stepper.trackTime;
|
|
8560
|
+
if (rest > 0) this.pose = stepper.step(rest);
|
|
7435
8561
|
}
|
|
7436
8562
|
on(type, listener) {
|
|
7437
8563
|
const set = this.listeners[type];
|
|
@@ -7453,13 +8579,22 @@ class RigPlayer {
|
|
|
7453
8579
|
* checks and nothing else.
|
|
7454
8580
|
*/
|
|
7455
8581
|
advance(dt) {
|
|
7456
|
-
|
|
7457
|
-
if (track === null || this.pose === null) return;
|
|
8582
|
+
if (this.pose === null) return;
|
|
7458
8583
|
const step = dt * this.timeScale;
|
|
7459
8584
|
if (this.paused || !(step > 0)) return;
|
|
8585
|
+
const tracks = this.tracks;
|
|
8586
|
+
if (tracks !== null) {
|
|
8587
|
+
const before2 = [];
|
|
8588
|
+
for (let t = 0; t < this.trackCount; t++) before2.push(tracks.current(t));
|
|
8589
|
+
this.pose = tracks.step(step);
|
|
8590
|
+
this.dispatch(this.pose, before2, tracks);
|
|
8591
|
+
return;
|
|
8592
|
+
}
|
|
8593
|
+
const track = this.track;
|
|
8594
|
+
if (track === null) return;
|
|
7460
8595
|
const before = track.trackTime;
|
|
7461
8596
|
this.pose = track.step(step);
|
|
7462
|
-
this.
|
|
8597
|
+
this.dispatchSingle(this.pose, before, track);
|
|
7463
8598
|
}
|
|
7464
8599
|
/** Draws the pose the player holds. */
|
|
7465
8600
|
draw() {
|
|
@@ -7481,6 +8616,7 @@ class RigPlayer {
|
|
|
7481
8616
|
}
|
|
7482
8617
|
set skin(name) {
|
|
7483
8618
|
if (name === this.skinName) return;
|
|
8619
|
+
if (this.tracks !== null) throw new Error("skin cannot change after a mix, another track, a queue or a clear: the player cannot replay those to the current time — set it before composing, or play again");
|
|
7484
8620
|
const view = name === void 0 ? underNoSkin(this.read.doc) : underSkin(this.read.doc, name);
|
|
7485
8621
|
this.view = view;
|
|
7486
8622
|
this.source = frameSource(view, this.read.parsed);
|
|
@@ -7488,10 +8624,41 @@ class RigPlayer {
|
|
|
7488
8624
|
this.setup = null;
|
|
7489
8625
|
if (this.animation !== null && this.track !== null) {
|
|
7490
8626
|
const time = this.track.trackTime;
|
|
7491
|
-
this.
|
|
8627
|
+
this.reopen();
|
|
7492
8628
|
this.stepTo(time);
|
|
7493
8629
|
}
|
|
7494
8630
|
}
|
|
8631
|
+
/**
|
|
8632
|
+
* Which slots this player draws (`SlotFilter`), or null for every slot.
|
|
8633
|
+
* Settable on a live player: the next `draw` shows it, and `bounds()` reads
|
|
8634
|
+
* the drawn slots. An allow list naming a slot the document does not have
|
|
8635
|
+
* is refused by name. The pose is the whole rig's: `bone()`, `adjust`, the
|
|
8636
|
+
* events and the tracks see no filter.
|
|
8637
|
+
*/
|
|
8638
|
+
get slots() {
|
|
8639
|
+
return this.slotFilter;
|
|
8640
|
+
}
|
|
8641
|
+
set slots(filter) {
|
|
8642
|
+
if (filter === null) {
|
|
8643
|
+
this.slotFilter = null;
|
|
8644
|
+
this.draws = () => true;
|
|
8645
|
+
return;
|
|
8646
|
+
}
|
|
8647
|
+
if (typeof filter === "function") {
|
|
8648
|
+
this.slotFilter = filter;
|
|
8649
|
+
this.draws = filter;
|
|
8650
|
+
return;
|
|
8651
|
+
}
|
|
8652
|
+
if (!Array.isArray(filter)) throw new Error(`slots must be an array of slot names, a function, or null, got ${typeof filter}`);
|
|
8653
|
+
const known = new Set(this.read.doc.slots.map((slot) => slot.name));
|
|
8654
|
+
const unknown = filter.filter((name) => !known.has(name));
|
|
8655
|
+
if (unknown.length > 0) {
|
|
8656
|
+
throw new Error(`no slot named ${unknown.map((n) => JSON.stringify(n)).join(", ")} in this document (slots: ${[...known].join(", ")})`);
|
|
8657
|
+
}
|
|
8658
|
+
const allowed = new Set(filter);
|
|
8659
|
+
this.slotFilter = [...filter];
|
|
8660
|
+
this.draws = (slot) => allowed.has(slot);
|
|
8661
|
+
}
|
|
7495
8662
|
/**
|
|
7496
8663
|
* The per-pose adjustment of bone locals (`BoneAdjust`), or null for none.
|
|
7497
8664
|
* It is applied by every track the player opens — `play`, the reopen inside
|
|
@@ -7516,8 +8683,9 @@ class RigPlayer {
|
|
|
7516
8683
|
* changed, then `draw`. Nothing before `play`.
|
|
7517
8684
|
*/
|
|
7518
8685
|
repose() {
|
|
7519
|
-
|
|
7520
|
-
this.pose
|
|
8686
|
+
const stepper = this.tracks ?? this.track;
|
|
8687
|
+
if (stepper === null || this.pose === null) return;
|
|
8688
|
+
this.pose = stepper.step(0);
|
|
7521
8689
|
}
|
|
7522
8690
|
/**
|
|
7523
8691
|
* Where `name` is in the pose the player holds — the pose of the last
|
|
@@ -7556,7 +8724,7 @@ class RigPlayer {
|
|
|
7556
8724
|
* mode; nothing is computed until called.
|
|
7557
8725
|
*/
|
|
7558
8726
|
bounds() {
|
|
7559
|
-
const frame2 = this.pose === null ? frameOf(this.source, this.posed(), this.color) : this.posedFrame();
|
|
8727
|
+
const frame2 = this.pose === null ? frameOf(this.source, this.posed(), this.color, this.draws) : this.posedFrame();
|
|
7560
8728
|
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
7561
8729
|
for (const slot of frame2.order) {
|
|
7562
8730
|
const points = slot.kind === "region" ? slot.corners : slot.vertices;
|
|
@@ -7592,35 +8760,30 @@ class RigPlayer {
|
|
|
7592
8760
|
if (this.setup === null) this.setup = poseRawSetup(this.view);
|
|
7593
8761
|
return this.setup;
|
|
7594
8762
|
}
|
|
7595
|
-
/**
|
|
7596
|
-
|
|
8763
|
+
/**
|
|
8764
|
+
* The pose the player holds, as the frame record the renderer draws — under
|
|
8765
|
+
* this player's `slots` filter, or under `slots` given here instead (a
|
|
8766
|
+
* second renderer over the same assets drawing this player's pose on
|
|
8767
|
+
* another root: `other.render(player.posedFrame(['hand_f']))`, the one rig
|
|
8768
|
+
* posed once).
|
|
8769
|
+
*/
|
|
8770
|
+
posedFrame(slots) {
|
|
7597
8771
|
if (this.pose === null) return { order: [] };
|
|
7598
|
-
|
|
8772
|
+
const draws = slots === void 0 ? this.draws : slots === null ? () => true : typeof slots === "function" ? slots : predicateOf(slots);
|
|
8773
|
+
return frameOf(this.source, this.pose, this.color, draws);
|
|
7599
8774
|
}
|
|
7600
8775
|
/** Removes the renderer's elements and hands its GPU pages back. The assets stay the caller's. */
|
|
7601
8776
|
dispose() {
|
|
7602
8777
|
this.renderer.dispose();
|
|
8778
|
+
this.tracks = null;
|
|
7603
8779
|
this.track = null;
|
|
7604
8780
|
this.pose = null;
|
|
7605
8781
|
}
|
|
7606
|
-
|
|
7607
|
-
|
|
7608
|
-
this.pose = this.track.first;
|
|
7609
|
-
}
|
|
7610
|
-
/**
|
|
7611
|
-
* The step's events, then its complete. The core fires the keys
|
|
7612
|
-
* (`pose.events`, in the runtime's order across a wrap) and leaves
|
|
7613
|
-
* `complete` to the player, which derives it from the track time and the
|
|
7614
|
-
* duration: on a looping track, once on a step whose whole cycles rose past
|
|
7615
|
-
* 0 (rig-c `src/core/track.ts`, *Across the wrap*); on a held one, on the
|
|
7616
|
-
* step that reaches the duration. The runtime queues a looping complete
|
|
7617
|
-
* between the keys before the wrap and the keys after it; this player
|
|
7618
|
-
* dispatches it after all of the step's keys.
|
|
7619
|
-
*/
|
|
7620
|
-
dispatch(pose, before, track) {
|
|
8782
|
+
/** The single track's step: its events (track 0) and its complete, as every 1.x player fired them. */
|
|
8783
|
+
dispatchSingle(pose, before, track) {
|
|
7621
8784
|
if (this.listeners.event.size > 0) {
|
|
7622
8785
|
for (const [name, time, int, float, string] of pose.events) {
|
|
7623
|
-
const event = { name, time, int, float, string };
|
|
8786
|
+
const event = { name, time, int, float, string, track: 0 };
|
|
7624
8787
|
for (const listener of this.listeners.event) listener(event);
|
|
7625
8788
|
}
|
|
7626
8789
|
}
|
|
@@ -7630,7 +8793,69 @@ class RigPlayer {
|
|
|
7630
8793
|
const cycles = d > 0 ? Math.floor(after / d) : Infinity;
|
|
7631
8794
|
const was = d > 0 ? Math.floor(before / d) : -Infinity;
|
|
7632
8795
|
if (this.loop ? cycles > 0 && cycles > was : before < d && after >= d) {
|
|
7633
|
-
const payload2 = { animation: this.animation };
|
|
8796
|
+
const payload2 = { animation: this.animation, track: 0 };
|
|
8797
|
+
for (const listener of this.listeners.complete) listener(payload2);
|
|
8798
|
+
}
|
|
8799
|
+
}
|
|
8800
|
+
}
|
|
8801
|
+
/**
|
|
8802
|
+
* The step's events, then its completes, each with its track. The core
|
|
8803
|
+
* fires a track's keys from its current entry (`pose.events`, in track
|
|
8804
|
+
* order and in the runtime's order across a wrap) — an entry mixing out
|
|
8805
|
+
* fires none — and leaves `complete` to the player, which derives it per
|
|
8806
|
+
* track from that track's current entry: on a looping entry, once on a step
|
|
8807
|
+
* whose whole cycles rose; on a held one, on the step that reaches its
|
|
8808
|
+
* duration. An entry replaced inside the step — by `play`, or by a queued
|
|
8809
|
+
* entry starting at its completion less the mix — is no longer current and
|
|
8810
|
+
* completes no more than it fires, which is the runtime's rule too.
|
|
8811
|
+
*/
|
|
8812
|
+
dispatch(pose, before, tracks) {
|
|
8813
|
+
const after = [];
|
|
8814
|
+
for (let t = 0; t < this.trackCount; t++) after.push(tracks.current(t));
|
|
8815
|
+
const same = (b, a) => b !== null && a !== null && b.animation === a.animation && b.loop === a.loop && a.trackTime >= b.trackTime;
|
|
8816
|
+
if (this.listeners.event.size > 0 && pose.events.length > 0) {
|
|
8817
|
+
const counts = [];
|
|
8818
|
+
let total = 0;
|
|
8819
|
+
for (let t = 0; t < this.trackCount; t++) {
|
|
8820
|
+
const a = after[t];
|
|
8821
|
+
const b = before[t];
|
|
8822
|
+
let n = 0;
|
|
8823
|
+
if (a !== null && a.animation !== null) {
|
|
8824
|
+
const keys = this.view.animations.find((anim) => anim.name === a.animation)?.timelines.events ?? [];
|
|
8825
|
+
if (keys.length > 0) {
|
|
8826
|
+
const last = same(b, a) ? b.animationTime : -1;
|
|
8827
|
+
n = eventsFired(keys, last, a.animationTime, void 0, a.animationTime < last).length;
|
|
8828
|
+
}
|
|
8829
|
+
}
|
|
8830
|
+
counts.push(n);
|
|
8831
|
+
total += n;
|
|
8832
|
+
}
|
|
8833
|
+
const attributed = total === pose.events.length;
|
|
8834
|
+
let track = 0;
|
|
8835
|
+
let left = counts[0] ?? 0;
|
|
8836
|
+
for (const [name, time, int, float, string] of pose.events) {
|
|
8837
|
+
if (attributed) {
|
|
8838
|
+
while (left === 0 && track < counts.length - 1) {
|
|
8839
|
+
track++;
|
|
8840
|
+
left = counts[track];
|
|
8841
|
+
}
|
|
8842
|
+
left--;
|
|
8843
|
+
}
|
|
8844
|
+
const event = attributed ? { name, time, int, float, string, track } : { name, time, int, float, string };
|
|
8845
|
+
for (const listener of this.listeners.event) listener(event);
|
|
8846
|
+
}
|
|
8847
|
+
}
|
|
8848
|
+
if (this.listeners.complete.size > 0) {
|
|
8849
|
+
for (let t = 0; t < this.trackCount; t++) {
|
|
8850
|
+
const a = after[t];
|
|
8851
|
+
const b = before[t];
|
|
8852
|
+
if (!same(b, a) || a === null || a.animation === null) continue;
|
|
8853
|
+
const d = a.duration;
|
|
8854
|
+
const cycles = d > 0 ? Math.floor(a.trackTime / d) : Infinity;
|
|
8855
|
+
const was = d > 0 ? Math.floor(b.trackTime / d) : -Infinity;
|
|
8856
|
+
const fired = a.loop ? cycles > 0 && cycles > was : b.trackTime < d && a.trackTime >= d;
|
|
8857
|
+
if (!fired) continue;
|
|
8858
|
+
const payload2 = { animation: a.animation, track: t };
|
|
7634
8859
|
for (const listener of this.listeners.complete) listener(payload2);
|
|
7635
8860
|
}
|
|
7636
8861
|
}
|