rig-play 1.4.0 → 1.5.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 +34 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1252 -74
- package/dist/index.js.map +1 -1
- package/dist/player.d.ts +131 -22
- 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) {
|
|
@@ -7322,9 +8316,27 @@ class RigPlayer {
|
|
|
7322
8316
|
read;
|
|
7323
8317
|
skinName;
|
|
7324
8318
|
seekStep;
|
|
8319
|
+
/**
|
|
8320
|
+
* The single live track (`openTrack`): what a plain `play` on track 0 runs
|
|
8321
|
+
* on, as every 1.x player did — one animation, every construct the core
|
|
8322
|
+
* poses, `seek` and a skin change by reopening. Null once the player is
|
|
8323
|
+
* composed.
|
|
8324
|
+
*/
|
|
7325
8325
|
track = null;
|
|
8326
|
+
/**
|
|
8327
|
+
* The live tracks (`openTracks`): opened by the first composing call — a
|
|
8328
|
+
* `play` with a mix or on another track, a `queue`, a `clear` — from the
|
|
8329
|
+
* single track's animation stepped to its time. The mixing is the Spine
|
|
8330
|
+
* runtime's, which does not mix a constraint, sequence, draw-order or
|
|
8331
|
+
* path-deform timeline, so such an animation is refused here and plays on
|
|
8332
|
+
* the single track only.
|
|
8333
|
+
*/
|
|
8334
|
+
tracks = null;
|
|
8335
|
+
/** Track 0's animation and loop as `play` last set them — what `seek` and a skin change reopen. */
|
|
7326
8336
|
animation = null;
|
|
7327
8337
|
loop = true;
|
|
8338
|
+
/** One past the highest track touched — how many `current(track)` reads a step takes. */
|
|
8339
|
+
trackCount = 0;
|
|
7328
8340
|
paused = false;
|
|
7329
8341
|
pose = null;
|
|
7330
8342
|
setup = null;
|
|
@@ -7388,23 +8400,118 @@ class RigPlayer {
|
|
|
7388
8400
|
if (this.meshBackend) renderer.meshBackend = this.meshBackend;
|
|
7389
8401
|
return renderer;
|
|
7390
8402
|
}
|
|
7391
|
-
/**
|
|
8403
|
+
/** What track 0 plays, or null before `play` and after a `clear` of track 0 has mixed out. */
|
|
7392
8404
|
get current() {
|
|
7393
|
-
return this.animation;
|
|
8405
|
+
return this.tracks !== null ? this.tracks.current(0)?.animation ?? null : this.animation;
|
|
7394
8406
|
}
|
|
7395
|
-
/** The
|
|
8407
|
+
/** The player's time — the running sum of the steps since `play` (or the last `seek`). */
|
|
7396
8408
|
get time() {
|
|
7397
|
-
return this.track?.trackTime ?? 0;
|
|
8409
|
+
return this.tracks?.trackTime ?? this.track?.trackTime ?? 0;
|
|
7398
8410
|
}
|
|
7399
8411
|
/**
|
|
7400
|
-
*
|
|
7401
|
-
*
|
|
8412
|
+
* Plays `name` on a track — track 0 by default, which is what `play(name)`
|
|
8413
|
+
* has always meant: the first `play` opens the tracks at their first pose
|
|
8414
|
+
* with the physics reset there, and the pose is drawn on the next `frame`.
|
|
8415
|
+
* Later calls replace what the track plays: the entry it replaces mixes out
|
|
8416
|
+
* over `mix` seconds (0 cuts), and every entry queued on that track is
|
|
8417
|
+
* dropped. The pose the player holds is re-posed at once, so a held player
|
|
8418
|
+
* shows the change on its next `draw`. The mixing is the Spine runtime's,
|
|
8419
|
+
* which rig-c holds at tolerance 0; an animation keying what the tracks do
|
|
8420
|
+
* not mix — a constraint, a sequence, the draw order, a path's deform — is
|
|
8421
|
+
* refused here, by name, in the core's words.
|
|
7402
8422
|
*/
|
|
7403
8423
|
play(name, options = {}) {
|
|
7404
|
-
|
|
7405
|
-
|
|
8424
|
+
const track = options.track ?? 0;
|
|
8425
|
+
const loop = options.loop ?? true;
|
|
8426
|
+
const mix = options.mix ?? 0;
|
|
7406
8427
|
this.paused = false;
|
|
7407
|
-
this.
|
|
8428
|
+
if (this.tracks === null && track === 0 && mix === 0) {
|
|
8429
|
+
this.animation = name;
|
|
8430
|
+
this.loop = loop;
|
|
8431
|
+
this.track = openTrack(this.view, name, { loop, reset: "animation", adjust: this.adjustHook });
|
|
8432
|
+
this.pose = this.track.first;
|
|
8433
|
+
return;
|
|
8434
|
+
}
|
|
8435
|
+
const tracks = this.compose();
|
|
8436
|
+
tracks.setAnimation(track, name, { loop, mixDuration: mix });
|
|
8437
|
+
this.trackCount = Math.max(this.trackCount, track + 1);
|
|
8438
|
+
if (track === 0) {
|
|
8439
|
+
this.animation = name;
|
|
8440
|
+
this.loop = loop;
|
|
8441
|
+
}
|
|
8442
|
+
this.pose = tracks.step(0);
|
|
8443
|
+
}
|
|
8444
|
+
/**
|
|
8445
|
+
* Queues `name` after the last entry on `track` (`QueueOptions`): with
|
|
8446
|
+
* `delay` at or below 0 it starts when that entry completes less its own
|
|
8447
|
+
* `mix`, so a one-shot hands over to it across a mix; on an empty track it
|
|
8448
|
+
* is set. Before any `play`, the tracks are opened for it.
|
|
8449
|
+
*/
|
|
8450
|
+
queue(name, options = {}) {
|
|
8451
|
+
const track = options.track ?? 0;
|
|
8452
|
+
const tracks = this.compose();
|
|
8453
|
+
tracks.addAnimation(track, name, { loop: options.loop ?? true, mixDuration: options.mix ?? 0, delay: options.delay ?? 0 });
|
|
8454
|
+
this.trackCount = Math.max(this.trackCount, track + 1);
|
|
8455
|
+
this.pose = tracks.step(0);
|
|
8456
|
+
}
|
|
8457
|
+
/**
|
|
8458
|
+
* Mixes `track` out to nothing over `mix` seconds — the layer or one-shot
|
|
8459
|
+
* gone and the tracks below showing through. Track 0 cleared shows the
|
|
8460
|
+
* setup pose. The pose the player holds is re-posed at once.
|
|
8461
|
+
*/
|
|
8462
|
+
clear(track = 0, options = {}) {
|
|
8463
|
+
const tracks = this.compose();
|
|
8464
|
+
tracks.setEmptyAnimation(track, options.mix ?? 0);
|
|
8465
|
+
this.trackCount = Math.max(this.trackCount, track + 1);
|
|
8466
|
+
this.pose = tracks.step(0);
|
|
8467
|
+
}
|
|
8468
|
+
/**
|
|
8469
|
+
* Queues a mix out to nothing after the last entry on `track`, over `mix`
|
|
8470
|
+
* seconds; with `delay` 0 (the default) the mix ends as that entry
|
|
8471
|
+
* completes, so a one-shot queued this way plays once and fades out over
|
|
8472
|
+
* its last `mix` seconds.
|
|
8473
|
+
*/
|
|
8474
|
+
queueClear(track = 0, options = {}) {
|
|
8475
|
+
const tracks = this.compose();
|
|
8476
|
+
tracks.addEmptyAnimation(track, options.mix ?? 0, options.delay ?? 0);
|
|
8477
|
+
this.trackCount = Math.max(this.trackCount, track + 1);
|
|
8478
|
+
this.pose = tracks.step(0);
|
|
8479
|
+
}
|
|
8480
|
+
/** `track`'s current entry, or null where it plays nothing (and before `play`). */
|
|
8481
|
+
entry(track = 0) {
|
|
8482
|
+
if (this.tracks !== null) {
|
|
8483
|
+
const view = this.tracks.current(track);
|
|
8484
|
+
return view === null ? null : { animation: view.animation, loop: view.loop, trackTime: view.trackTime, animationTime: view.animationTime, duration: view.duration };
|
|
8485
|
+
}
|
|
8486
|
+
const single = this.track;
|
|
8487
|
+
if (track !== 0 || single === null) return null;
|
|
8488
|
+
return { animation: this.animation, loop: this.loop, trackTime: single.trackTime, animationTime: single.animationTime, duration: single.duration };
|
|
8489
|
+
}
|
|
8490
|
+
/** Whether the player is composed: on the live tracks rather than the single track. */
|
|
8491
|
+
get composed() {
|
|
8492
|
+
return this.tracks !== null;
|
|
8493
|
+
}
|
|
8494
|
+
/**
|
|
8495
|
+
* The live tracks, opened by the first composing call. A single track in
|
|
8496
|
+
* play is carried over by replay: track 0 set to its animation, pose 0 taken
|
|
8497
|
+
* by `start()` (the player's — nothing steps before it) and the tracks
|
|
8498
|
+
* stepped to the single track's time in `seekStep` increments, so a physics
|
|
8499
|
+
* rig's history restarts there and nothing is fired on the way.
|
|
8500
|
+
*/
|
|
8501
|
+
compose() {
|
|
8502
|
+
if (this.tracks !== null) return this.tracks;
|
|
8503
|
+
const tracks = openTracks(this.view, { reset: "animation", adjust: this.adjustHook });
|
|
8504
|
+
const single = this.track;
|
|
8505
|
+
const time = single?.trackTime ?? 0;
|
|
8506
|
+
if (single !== null && this.animation !== null) {
|
|
8507
|
+
tracks.setAnimation(0, this.animation, { loop: this.loop, mixDuration: 0 });
|
|
8508
|
+
this.trackCount = 1;
|
|
8509
|
+
}
|
|
8510
|
+
this.tracks = tracks;
|
|
8511
|
+
this.track = null;
|
|
8512
|
+
this.pose = tracks.start();
|
|
8513
|
+
if (time > 0) this.stepTo(time);
|
|
8514
|
+
return tracks;
|
|
7408
8515
|
}
|
|
7409
8516
|
/** Stops stepping; `frame` keeps drawing the pose it holds. */
|
|
7410
8517
|
pause() {
|
|
@@ -7415,23 +8522,33 @@ class RigPlayer {
|
|
|
7415
8522
|
this.paused = false;
|
|
7416
8523
|
}
|
|
7417
8524
|
/**
|
|
7418
|
-
* Moves
|
|
7419
|
-
* in `seekStep` increments (the
|
|
7420
|
-
*
|
|
8525
|
+
* Moves a single animation on track 0 to `time`: the tracks are reopened
|
|
8526
|
+
* with that animation and stepped forward in `seekStep` increments (the
|
|
8527
|
+
* last one shorter) until the time is reached. Events and completes crossed
|
|
8528
|
+
* on the way are not fired. It has a meaning for one animation on one
|
|
8529
|
+
* track only — the player cannot replay when a mix, a second track or a
|
|
8530
|
+
* queue was asked for — so after any of those it refuses by name; step the
|
|
8531
|
+
* player forward instead.
|
|
7421
8532
|
*/
|
|
7422
8533
|
seek(time) {
|
|
7423
8534
|
if (this.animation === null) throw new Error("seek before play: no animation is open");
|
|
7424
8535
|
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.
|
|
8536
|
+
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");
|
|
8537
|
+
this.reopen();
|
|
7426
8538
|
this.stepTo(time);
|
|
7427
8539
|
}
|
|
7428
|
-
/**
|
|
8540
|
+
/** The single track opened again with track 0's animation as `play` last set it, at pose 0. */
|
|
8541
|
+
reopen() {
|
|
8542
|
+
this.track = openTrack(this.view, this.animation, { loop: this.loop, reset: "animation", adjust: this.adjustHook });
|
|
8543
|
+
this.pose = this.track.first;
|
|
8544
|
+
}
|
|
8545
|
+
/** Steps whatever is open forward to `time` in `seekStep` increments, the last one shorter. */
|
|
7429
8546
|
stepTo(time) {
|
|
7430
|
-
const
|
|
8547
|
+
const stepper = this.tracks ?? this.track;
|
|
7431
8548
|
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 =
|
|
8549
|
+
for (let i = 0; i < steps; i++) this.pose = stepper.step(this.seekStep);
|
|
8550
|
+
const rest = time - stepper.trackTime;
|
|
8551
|
+
if (rest > 0) this.pose = stepper.step(rest);
|
|
7435
8552
|
}
|
|
7436
8553
|
on(type, listener) {
|
|
7437
8554
|
const set = this.listeners[type];
|
|
@@ -7453,13 +8570,22 @@ class RigPlayer {
|
|
|
7453
8570
|
* checks and nothing else.
|
|
7454
8571
|
*/
|
|
7455
8572
|
advance(dt) {
|
|
7456
|
-
|
|
7457
|
-
if (track === null || this.pose === null) return;
|
|
8573
|
+
if (this.pose === null) return;
|
|
7458
8574
|
const step = dt * this.timeScale;
|
|
7459
8575
|
if (this.paused || !(step > 0)) return;
|
|
8576
|
+
const tracks = this.tracks;
|
|
8577
|
+
if (tracks !== null) {
|
|
8578
|
+
const before2 = [];
|
|
8579
|
+
for (let t = 0; t < this.trackCount; t++) before2.push(tracks.current(t));
|
|
8580
|
+
this.pose = tracks.step(step);
|
|
8581
|
+
this.dispatch(this.pose, before2, tracks);
|
|
8582
|
+
return;
|
|
8583
|
+
}
|
|
8584
|
+
const track = this.track;
|
|
8585
|
+
if (track === null) return;
|
|
7460
8586
|
const before = track.trackTime;
|
|
7461
8587
|
this.pose = track.step(step);
|
|
7462
|
-
this.
|
|
8588
|
+
this.dispatchSingle(this.pose, before, track);
|
|
7463
8589
|
}
|
|
7464
8590
|
/** Draws the pose the player holds. */
|
|
7465
8591
|
draw() {
|
|
@@ -7481,6 +8607,7 @@ class RigPlayer {
|
|
|
7481
8607
|
}
|
|
7482
8608
|
set skin(name) {
|
|
7483
8609
|
if (name === this.skinName) return;
|
|
8610
|
+
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
8611
|
const view = name === void 0 ? underNoSkin(this.read.doc) : underSkin(this.read.doc, name);
|
|
7485
8612
|
this.view = view;
|
|
7486
8613
|
this.source = frameSource(view, this.read.parsed);
|
|
@@ -7488,7 +8615,7 @@ class RigPlayer {
|
|
|
7488
8615
|
this.setup = null;
|
|
7489
8616
|
if (this.animation !== null && this.track !== null) {
|
|
7490
8617
|
const time = this.track.trackTime;
|
|
7491
|
-
this.
|
|
8618
|
+
this.reopen();
|
|
7492
8619
|
this.stepTo(time);
|
|
7493
8620
|
}
|
|
7494
8621
|
}
|
|
@@ -7516,8 +8643,9 @@ class RigPlayer {
|
|
|
7516
8643
|
* changed, then `draw`. Nothing before `play`.
|
|
7517
8644
|
*/
|
|
7518
8645
|
repose() {
|
|
7519
|
-
|
|
7520
|
-
this.pose
|
|
8646
|
+
const stepper = this.tracks ?? this.track;
|
|
8647
|
+
if (stepper === null || this.pose === null) return;
|
|
8648
|
+
this.pose = stepper.step(0);
|
|
7521
8649
|
}
|
|
7522
8650
|
/**
|
|
7523
8651
|
* Where `name` is in the pose the player holds — the pose of the last
|
|
@@ -7600,27 +8728,15 @@ class RigPlayer {
|
|
|
7600
8728
|
/** Removes the renderer's elements and hands its GPU pages back. The assets stay the caller's. */
|
|
7601
8729
|
dispose() {
|
|
7602
8730
|
this.renderer.dispose();
|
|
8731
|
+
this.tracks = null;
|
|
7603
8732
|
this.track = null;
|
|
7604
8733
|
this.pose = null;
|
|
7605
8734
|
}
|
|
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) {
|
|
8735
|
+
/** The single track's step: its events (track 0) and its complete, as every 1.x player fired them. */
|
|
8736
|
+
dispatchSingle(pose, before, track) {
|
|
7621
8737
|
if (this.listeners.event.size > 0) {
|
|
7622
8738
|
for (const [name, time, int, float, string] of pose.events) {
|
|
7623
|
-
const event = { name, time, int, float, string };
|
|
8739
|
+
const event = { name, time, int, float, string, track: 0 };
|
|
7624
8740
|
for (const listener of this.listeners.event) listener(event);
|
|
7625
8741
|
}
|
|
7626
8742
|
}
|
|
@@ -7630,7 +8746,69 @@ class RigPlayer {
|
|
|
7630
8746
|
const cycles = d > 0 ? Math.floor(after / d) : Infinity;
|
|
7631
8747
|
const was = d > 0 ? Math.floor(before / d) : -Infinity;
|
|
7632
8748
|
if (this.loop ? cycles > 0 && cycles > was : before < d && after >= d) {
|
|
7633
|
-
const payload2 = { animation: this.animation };
|
|
8749
|
+
const payload2 = { animation: this.animation, track: 0 };
|
|
8750
|
+
for (const listener of this.listeners.complete) listener(payload2);
|
|
8751
|
+
}
|
|
8752
|
+
}
|
|
8753
|
+
}
|
|
8754
|
+
/**
|
|
8755
|
+
* The step's events, then its completes, each with its track. The core
|
|
8756
|
+
* fires a track's keys from its current entry (`pose.events`, in track
|
|
8757
|
+
* order and in the runtime's order across a wrap) — an entry mixing out
|
|
8758
|
+
* fires none — and leaves `complete` to the player, which derives it per
|
|
8759
|
+
* track from that track's current entry: on a looping entry, once on a step
|
|
8760
|
+
* whose whole cycles rose; on a held one, on the step that reaches its
|
|
8761
|
+
* duration. An entry replaced inside the step — by `play`, or by a queued
|
|
8762
|
+
* entry starting at its completion less the mix — is no longer current and
|
|
8763
|
+
* completes no more than it fires, which is the runtime's rule too.
|
|
8764
|
+
*/
|
|
8765
|
+
dispatch(pose, before, tracks) {
|
|
8766
|
+
const after = [];
|
|
8767
|
+
for (let t = 0; t < this.trackCount; t++) after.push(tracks.current(t));
|
|
8768
|
+
const same = (b, a) => b !== null && a !== null && b.animation === a.animation && b.loop === a.loop && a.trackTime >= b.trackTime;
|
|
8769
|
+
if (this.listeners.event.size > 0 && pose.events.length > 0) {
|
|
8770
|
+
const counts = [];
|
|
8771
|
+
let total = 0;
|
|
8772
|
+
for (let t = 0; t < this.trackCount; t++) {
|
|
8773
|
+
const a = after[t];
|
|
8774
|
+
const b = before[t];
|
|
8775
|
+
let n = 0;
|
|
8776
|
+
if (a !== null && a.animation !== null) {
|
|
8777
|
+
const keys = this.view.animations.find((anim) => anim.name === a.animation)?.timelines.events ?? [];
|
|
8778
|
+
if (keys.length > 0) {
|
|
8779
|
+
const last = same(b, a) ? b.animationTime : -1;
|
|
8780
|
+
n = eventsFired(keys, last, a.animationTime, void 0, a.animationTime < last).length;
|
|
8781
|
+
}
|
|
8782
|
+
}
|
|
8783
|
+
counts.push(n);
|
|
8784
|
+
total += n;
|
|
8785
|
+
}
|
|
8786
|
+
const attributed = total === pose.events.length;
|
|
8787
|
+
let track = 0;
|
|
8788
|
+
let left = counts[0] ?? 0;
|
|
8789
|
+
for (const [name, time, int, float, string] of pose.events) {
|
|
8790
|
+
if (attributed) {
|
|
8791
|
+
while (left === 0 && track < counts.length - 1) {
|
|
8792
|
+
track++;
|
|
8793
|
+
left = counts[track];
|
|
8794
|
+
}
|
|
8795
|
+
left--;
|
|
8796
|
+
}
|
|
8797
|
+
const event = attributed ? { name, time, int, float, string, track } : { name, time, int, float, string };
|
|
8798
|
+
for (const listener of this.listeners.event) listener(event);
|
|
8799
|
+
}
|
|
8800
|
+
}
|
|
8801
|
+
if (this.listeners.complete.size > 0) {
|
|
8802
|
+
for (let t = 0; t < this.trackCount; t++) {
|
|
8803
|
+
const a = after[t];
|
|
8804
|
+
const b = before[t];
|
|
8805
|
+
if (!same(b, a) || a === null || a.animation === null) continue;
|
|
8806
|
+
const d = a.duration;
|
|
8807
|
+
const cycles = d > 0 ? Math.floor(a.trackTime / d) : Infinity;
|
|
8808
|
+
const was = d > 0 ? Math.floor(b.trackTime / d) : -Infinity;
|
|
8809
|
+
const fired = a.loop ? cycles > 0 && cycles > was : b.trackTime < d && a.trackTime >= d;
|
|
8810
|
+
if (!fired) continue;
|
|
8811
|
+
const payload2 = { animation: a.animation, track: t };
|
|
7634
8812
|
for (const listener of this.listeners.complete) listener(payload2);
|
|
7635
8813
|
}
|
|
7636
8814
|
}
|