@esotericsoftware/spine-core
Version:
The official Spine Runtimes for the web.
1,105 lines (1,104 loc) • 348 kB
JavaScript
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2025, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software
* or otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
import { SequenceMode, SequenceModeValues } from "./attachments/Sequence.js";
import { Color, StringSet, Utils } from "./Utils.js";
/** Stores a list of timelines to animate a skeleton's pose over time.
*
* See <a href='https://esotericsoftware.com/spine-applying-animations#Timeline-API'>Applying Animations</a> in the Spine Runtimes
* Guide. */
export class Animation {
/** The animation's name, unique across all animations in the skeleton.
*
* See {@link SkeletonData.findAnimation}. */
name;
/** The duration of the animation in seconds, which is usually the highest time of all frames in the timelines. The duration is
* used to know when the animation has completed and, for animations that repeat, when it should loop back to the start. */
timelines = [];
timelineIds;
/** {@link Skeleton.getBones} indices that this animation's timelines modify.
*
* See {@link BoneTimeline.bones}. */
bones;
// Nonessential.
/** The color of the animation as it was in Spine, or a default color if nonessential data was not exported. */
color = new Color(1, 1, 1, 1);
/** The duration of the animation in seconds, which is usually the highest time of all frames in the timeline. The duration is
* used to know when it has completed and when it should loop back to the start. */
duration;
constructor(name, timelines, duration) {
if (!name)
throw new Error("name cannot be null.");
this.name = name;
this.duration = duration;
this.timelineIds = new StringSet();
this.bones = [];
this.setTimelines(timelines);
}
setTimelines(timelines) {
if (!timelines)
throw new Error("timelines cannot be null.");
this.timelines = timelines;
const n = timelines.length;
this.timelineIds.clear();
this.bones.length = 0;
const boneSet = new Set();
const items = timelines;
for (let i = 0; i < n; i++) {
const timeline = items[i];
this.timelineIds.addAll(timeline.propertyIds);
if (isBoneTimeline(timeline) && boneSet.add(timeline.boneIndex))
this.bones.push(timeline.boneIndex);
}
}
/** Returns true if this animation contains a timeline with any of the specified property IDs.
*
* See {@link Timeline.propertyIds}. */
hasTimeline(ids) {
for (let i = 0; i < ids.length; i++)
if (this.timelineIds.contains(ids[i]))
return true;
return false;
}
/** Applies the animation's timelines to the specified skeleton.
*
* See {@link Timeline.apply} and
* <a href='https://esotericsoftware.com/spine-applying-animations#Timeline-API'>Applying Animations</a> in the Spine Runtimes
* Guide.
* @param skeleton The skeleton the animation is applied to. This provides access to the bones, slots, and other skeleton
* components the timelines may change.
* @param lastTime The last time in seconds this animation was applied. Some timelines trigger only at discrete times, in which
* case all keys are triggered between `lastTime` (exclusive) and `time` (inclusive). Pass -1
* the first time an animation is applied to ensure frame 0 is triggered.
* @param time The time in seconds the skeleton is being posed for. Timelines find the frame before and after this time and
* interpolate between the frame values.
* @param loop True if `time` beyond the {@link duration} repeats the animation, else the last frame is used.
* @param events If any events are fired, they are added to this list. Pass null to ignore fired events or if no timelines fire
* events.
* @param alpha 0 applies setup or current values (depending on `fromSetup`), 1 uses timeline values, and
* intermediate values interpolate between them. Adjusting `alpha` over time can mix an animation in or
* out.
* @param fromSetup If true, `alpha` transitions between setup and timeline values, setup values are used before the
* first frame (current values are not used). If false, `alpha` transitions between current and timeline
* values, no change is made before the first frame.
* @param add If true, for timelines that support it, their values are added to the setup or current values (depending on
* `fromSetup`).
* @param out True when the animation is mixing out, else it is mixing in. Used by timelines that perform instant transitions.
* @param appliedPose True to modify {@link Posed.appliedPose}, else {@link Posed.pose} is modified. */
apply(skeleton, lastTime, time, loop, events, alpha, fromSetup, add, out, appliedPose) {
if (!skeleton)
throw new Error("skeleton cannot be null.");
if (loop && this.duration !== 0) {
time %= this.duration;
if (lastTime > 0)
lastTime %= this.duration;
}
const timelines = this.timelines;
for (let i = 0, n = timelines.length; i < n; i++)
timelines[i].apply(skeleton, lastTime, time, events, alpha, fromSetup, add, out, appliedPose);
}
}
export var Property;
(function (Property) {
Property[Property["rotate"] = 0] = "rotate";
Property[Property["x"] = 1] = "x";
Property[Property["y"] = 2] = "y";
Property[Property["scaleX"] = 3] = "scaleX";
Property[Property["scaleY"] = 4] = "scaleY";
Property[Property["shearX"] = 5] = "shearX";
Property[Property["shearY"] = 6] = "shearY";
Property[Property["inherit"] = 7] = "inherit";
Property[Property["rgb"] = 8] = "rgb";
Property[Property["alpha"] = 9] = "alpha";
Property[Property["rgb2"] = 10] = "rgb2";
Property[Property["attachment"] = 11] = "attachment";
Property[Property["deform"] = 12] = "deform";
Property[Property["event"] = 13] = "event";
Property[Property["drawOrder"] = 14] = "drawOrder";
Property[Property["ikConstraint"] = 15] = "ikConstraint";
Property[Property["transformConstraint"] = 16] = "transformConstraint";
Property[Property["pathConstraintPosition"] = 17] = "pathConstraintPosition";
Property[Property["pathConstraintSpacing"] = 18] = "pathConstraintSpacing";
Property[Property["pathConstraintMix"] = 19] = "pathConstraintMix";
Property[Property["physicsConstraintInertia"] = 20] = "physicsConstraintInertia";
Property[Property["physicsConstraintStrength"] = 21] = "physicsConstraintStrength";
Property[Property["physicsConstraintDamping"] = 22] = "physicsConstraintDamping";
Property[Property["physicsConstraintMass"] = 23] = "physicsConstraintMass";
Property[Property["physicsConstraintWind"] = 24] = "physicsConstraintWind";
Property[Property["physicsConstraintGravity"] = 25] = "physicsConstraintGravity";
Property[Property["physicsConstraintMix"] = 26] = "physicsConstraintMix";
Property[Property["physicsConstraintReset"] = 27] = "physicsConstraintReset";
Property[Property["sequence"] = 28] = "sequence";
Property[Property["sliderTime"] = 29] = "sliderTime";
Property[Property["sliderMix"] = 30] = "sliderMix";
})(Property || (Property = {}));
/** The base class for all timelines.
*
* See <a href='https://esotericsoftware.com/spine-applying-animations#Timeline-API'>Applying Animations</a> in the Spine
* Runtimes Guide. */
export class Timeline {
propertyIds;
frames;
/** True if this timeline supports additive blending. */
additive = false;
/** True if this timeline sets values instantaneously and does not support interpolation between frames. */
instant = false;
constructor(frameCount, ...propertyIds) {
this.propertyIds = propertyIds;
this.frames = Utils.newFloatArray(frameCount * this.getFrameEntries());
}
getPropertyIds() {
return this.propertyIds;
}
/** The number of values stored per frame. */
getFrameEntries() {
return 1;
}
/** The number of frames in this timeline. */
getFrameCount() {
return this.frames.length / this.getFrameEntries();
}
/** The duration of the timeline in seconds, which is usually the highest time of all frames in the timeline. */
getDuration() {
return this.frames[this.frames.length - this.getFrameEntries()];
}
/** Linear search using the specified stride (default 1).
* @param time Must be >= the first value in `frames`.
* @return The index of the first value <= `time`. */
static search(frames, time, step = 1) {
const n = frames.length;
for (let i = step; i < n; i += step)
if (frames[i] > time)
return i - step;
return n - step;
}
}
export function isSlotTimeline(obj) {
return typeof obj === 'object' && obj !== null && typeof obj.slotIndex === 'number';
}
/** The base class for timelines that interpolate between frame values using stepped, linear, or a Bezier curve. */
export class CurveTimeline extends Timeline {
curves; // type, x, y, ...
constructor(frameCount, bezierCount, ...propertyIds) {
super(frameCount, ...propertyIds);
this.curves = Utils.newFloatArray(frameCount + bezierCount * 18 /*BEZIER_SIZE*/);
this.curves[frameCount - 1] = 1 /*STEPPED*/;
}
/** Sets the specified key frame to linear interpolation. */
setLinear(frame) {
this.curves[frame] = 0 /*LINEAR*/;
}
/** Sets the specified key frame to stepped interpolation. */
setStepped(frame) {
this.curves[frame] = 1 /*STEPPED*/;
}
/** Shrinks the storage for Bezier curves, for use when `bezierCount` (specified in the constructor) was larger
* than the actual number of Bezier curves. */
shrink(bezierCount) {
const size = this.getFrameCount() + bezierCount * 18 /*BEZIER_SIZE*/;
if (this.curves.length > size) {
const newCurves = Utils.newFloatArray(size);
Utils.arrayCopy(this.curves, 0, newCurves, 0, size);
this.curves = newCurves;
}
}
/** Stores the segments for the specified Bezier curve. For timelines that modify multiple values, there may be more than
* one curve per frame.
* @param bezier The ordinal of this Bezier curve for this timeline, between 0 and `bezierCount - 1` (specified
* in the constructor), inclusive.
* @param frame Between 0 and `frameCount - 1`, inclusive.
* @param value The index of the value for this frame that this curve is used for.
* @param time1 The time for the first key.
* @param value1 The value for the first key.
* @param cx1 The time for the first Bezier handle.
* @param cy1 The value for the first Bezier handle.
* @param cx2 The time of the second Bezier handle.
* @param cy2 The value for the second Bezier handle.
* @param time2 The time for the second key.
* @param value2 The value for the second key. */
setBezier(bezier, frame, value, time1, value1, cx1, cy1, cx2, cy2, time2, value2) {
const curves = this.curves;
let i = this.getFrameCount() + bezier * 18 /*BEZIER_SIZE*/;
if (value === 0)
curves[frame] = 2 /*BEZIER*/ + i;
const tmpx = (time1 - cx1 * 2 + cx2) * 0.03, tmpy = (value1 - cy1 * 2 + cy2) * 0.03;
const dddx = ((cx1 - cx2) * 3 - time1 + time2) * 0.006, dddy = ((cy1 - cy2) * 3 - value1 + value2) * 0.006;
let ddx = tmpx * 2 + dddx, ddy = tmpy * 2 + dddy;
let dx = (cx1 - time1) * 0.3 + tmpx + dddx * 0.16666667, dy = (cy1 - value1) * 0.3 + tmpy + dddy * 0.16666667;
let x = time1 + dx, y = value1 + dy;
for (let n = i + 18 /*BEZIER_SIZE*/; i < n; i += 2) {
curves[i] = x;
curves[i + 1] = y;
dx += ddx;
dy += ddy;
ddx += dddx;
ddy += dddy;
x += dx;
y += dy;
}
}
/** Returns the Bezier interpolated value for the specified time.
* @param frameIndex The index into {@link frames} for the values of the frame before `time`.
* @param valueOffset The offset from `frameIndex` to the value this curve is used for.
* @param i The index of the Bezier segments. See {@link getCurveType}. */
getBezierValue(time, frameIndex, valueOffset, i) {
const curves = this.curves;
if (curves[i] > time) {
const x = this.frames[frameIndex], y = this.frames[frameIndex + valueOffset];
return y + (time - x) / (curves[i] - x) * (curves[i + 1] - y);
}
const n = i + 18 /*BEZIER_SIZE*/;
for (i += 2; i < n; i += 2) {
if (curves[i] >= time) {
const x = curves[i - 2], y = curves[i - 1];
return y + (time - x) / (curves[i] - x) * (curves[i + 1] - y);
}
}
frameIndex += this.getFrameEntries();
const x = curves[n - 2], y = curves[n - 1];
return y + (time - x) / (this.frames[frameIndex] - x) * (this.frames[frameIndex + valueOffset] - y);
}
}
/** The base class for a {@link CurveTimeline} that sets one property with a curve. */
export class CurveTimeline1 extends CurveTimeline {
constructor(frameCount, bezierCount, propertyId) {
super(frameCount, bezierCount, propertyId);
}
getFrameEntries() {
return 2 /*ENTRIES*/;
}
/** Sets the time and value for the specified frame.
* @param frame Between 0 and `frameCount`, inclusive.
* @param time The frame time in seconds. */
setFrame(frame, time, value) {
frame <<= 1;
this.frames[frame] = time;
this.frames[frame + 1 /*VALUE*/] = value;
}
/** Returns the interpolated value for the specified time. */
getCurveValue(time) {
const frames = this.frames;
let i = frames.length - 2;
for (let ii = 2; ii <= i; ii += 2) {
if (frames[ii] > time) {
i = ii - 2;
break;
}
}
const curveType = this.curves[i >> 1];
switch (curveType) {
case 0 /*LINEAR*/: {
const before = frames[i], value = frames[i + 1 /*VALUE*/];
return value + (time - before) / (frames[i + 2 /*ENTRIES*/] - before) * (frames[i + 2 /*ENTRIES*/ + 1 /*VALUE*/] - value);
}
case 1 /*STEPPED*/:
return frames[i + 1 /*VALUE*/];
}
return this.getBezierValue(time, i, 1 /*VALUE*/, curveType - 2 /*BEZIER*/);
}
/** Returns the interpolated value for properties relative to the setup value. The timeline value is added to the setup
* value, rather than replacing it.
*
* See {@link Timeline.apply}.
* @param current The current value for the property.
* @param setup The setup value for the property. */
getRelativeValue(time, alpha, fromSetup, add, current, setup) {
if (time < this.frames[0])
return fromSetup ? setup : current;
const value = this.getCurveValue(time);
return fromSetup ? setup + value * alpha : current + (add ? value : value + setup - current) * alpha;
}
getAbsoluteValue(time, alpha, fromSetup, add, current, setup, value) {
if (value === undefined)
return this.getAbsoluteValue1(time, alpha, fromSetup, add, current, setup);
else
return this.getAbsoluteValue2(time, alpha, fromSetup, add, current, setup, value);
}
getAbsoluteValue1(time, alpha, fromSetup, add, current, setup) {
if (time < this.frames[0])
return fromSetup ? setup : current;
const value = this.getCurveValue(time);
return fromSetup ? setup + (add ? value : value - setup) * alpha : current + (add ? value : value - current) * alpha;
}
getAbsoluteValue2(time, alpha, fromSetup, add, current, setup, value) {
if (time < this.frames[0])
return fromSetup ? setup : current;
return fromSetup ? setup + (add ? value : value - setup) * alpha : current + (add ? value : value - current) * alpha;
}
/** Returns the interpolated value for scale properties. The timeline and setup values are multiplied and sign adjusted.
*
* See {@link Timeline.apply}.
* @param current The current value for the property.
* @param setup The setup value for the property. */
getScaleValue(time, alpha, fromSetup, add, out, current, setup) {
if (time < this.frames[0])
return fromSetup ? setup : current;
const value = this.getCurveValue(time) * setup;
if (alpha === 1 && !add)
return value;
let base = fromSetup ? setup : current;
if (add)
return base + (value - setup) * alpha;
if (out)
return base + (Math.abs(value) * Math.sign(base) - base) * alpha;
base = Math.abs(base) * Math.sign(value);
return base + (value - base) * alpha;
}
}
export function isBoneTimeline(obj) {
return typeof obj === 'object' && obj !== null && typeof obj.boneIndex === 'number';
}
/** The base class for timelines that change 1 bone property with a curve. */
export class BoneTimeline1 extends CurveTimeline1 {
boneIndex;
constructor(frameCount, bezierCount, boneIndex, property) {
super(frameCount, bezierCount, `${property}|${boneIndex}`);
this.boneIndex = boneIndex;
this.additive = true;
}
apply(skeleton, lastTime, time, events, alpha, fromSetup, add, out, appliedPose) {
const bone = skeleton.bones[this.boneIndex];
if (bone.active)
this.apply1(appliedPose ? bone.appliedPose : bone.pose, bone.data.setupPose, time, alpha, fromSetup, add, out);
}
}
/** The base class for timelines that change two bone properties with a curve. */
export class BoneTimeline2 extends CurveTimeline {
boneIndex;
/** @param bezierCount The maximum number of Bezier curves. See {@link shrink}.
* @param propertyIds Unique identifiers for the properties the timeline modifies. */
constructor(frameCount, bezierCount, boneIndex, property1, property2) {
super(frameCount, bezierCount, `${property1}|${boneIndex}`, `${property2}|${boneIndex}`);
this.boneIndex = boneIndex;
this.additive = true;
}
getFrameEntries() {
return 3 /*ENTRIES*/;
}
/** Sets the time and values for the specified frame.
* @param frame Between 0 and `frameCount`, inclusive.
* @param time The frame time in seconds. */
setFrame(frame, time, value1, value2) {
frame *= 3 /*ENTRIES*/;
this.frames[frame] = time;
this.frames[frame + 1 /*VALUE1*/] = value1;
this.frames[frame + 2 /*VALUE2*/] = value2;
}
apply(skeleton, lastTime, time, events, alpha, fromSetup, add, out, appliedPose) {
const bone = skeleton.bones[this.boneIndex];
if (bone.active)
this.apply1(appliedPose ? bone.appliedPose : bone.pose, bone.data.setupPose, time, alpha, fromSetup, add, out);
}
}
/** Changes {@link BonePose.rotation}. */
export class RotateTimeline extends BoneTimeline1 {
constructor(frameCount, bezierCount, boneIndex) {
super(frameCount, bezierCount, boneIndex, Property.rotate);
}
apply1(pose, setup, time, alpha, fromSetup, add, out) {
pose.rotation = this.getRelativeValue(time, alpha, fromSetup, add, pose.rotation, setup.rotation);
}
}
/** Changes {@link BonePose.x} and {@link BonePose.y}. */
export class TranslateTimeline extends BoneTimeline2 {
constructor(frameCount, bezierCount, boneIndex) {
super(frameCount, bezierCount, boneIndex, Property.x, Property.y);
}
apply1(pose, setup, time, alpha, fromSetup, add, out) {
const frames = this.frames;
if (time < frames[0]) {
if (fromSetup) {
pose.x = setup.x;
pose.y = setup.y;
}
return;
}
let x = 0, y = 0;
const i = Timeline.search(frames, time, 3 /*ENTRIES*/);
const curveType = this.curves[i / 3 /*ENTRIES*/];
switch (curveType) {
case 0 /*LINEAR*/: {
const before = frames[i];
x = frames[i + 1 /*VALUE1*/];
y = frames[i + 2 /*VALUE2*/];
const t = (time - before) / (frames[i + 3 /*ENTRIES*/] - before);
x += (frames[i + 3 /*ENTRIES*/ + 1 /*VALUE1*/] - x) * t;
y += (frames[i + 3 /*ENTRIES*/ + 2 /*VALUE2*/] - y) * t;
break;
}
case 1 /*STEPPED*/:
x = frames[i + 1 /*VALUE1*/];
y = frames[i + 2 /*VALUE2*/];
break;
default:
x = this.getBezierValue(time, i, 1 /*VALUE1*/, curveType - 2 /*BEZIER*/);
y = this.getBezierValue(time, i, 2 /*VALUE2*/, curveType + 18 /*BEZIER_SIZE*/ - 2 /*BEZIER*/);
}
if (fromSetup) {
pose.x = setup.x + x * alpha;
pose.y = setup.y + y * alpha;
}
else if (add) {
pose.x += x * alpha;
pose.y += y * alpha;
}
else {
pose.x += (setup.x + x - pose.x) * alpha;
pose.y += (setup.y + y - pose.y) * alpha;
}
}
}
/** Changes {@link BonePose.x}. */
export class TranslateXTimeline extends BoneTimeline1 {
constructor(frameCount, bezierCount, boneIndex) {
super(frameCount, bezierCount, boneIndex, Property.x);
}
apply1(pose, setup, time, alpha, fromSetup, add, out) {
pose.x = this.getRelativeValue(time, alpha, fromSetup, add, pose.x, setup.x);
}
}
/** Changes {@link BonePose.y}. */
export class TranslateYTimeline extends BoneTimeline1 {
constructor(frameCount, bezierCount, boneIndex) {
super(frameCount, bezierCount, boneIndex, Property.y);
}
apply1(pose, setup, time, alpha, fromSetup, add, out) {
pose.y = this.getRelativeValue(time, alpha, fromSetup, add, pose.y, setup.y);
}
}
/** Changes {@link BonePose.scaleX} and {@link BonePose.scaleY}. */
export class ScaleTimeline extends BoneTimeline2 {
constructor(frameCount, bezierCount, boneIndex) {
super(frameCount, bezierCount, boneIndex, Property.scaleX, Property.scaleY);
}
apply1(pose, setup, time, alpha, fromSetup, add, out) {
const frames = this.frames;
if (time < frames[0]) {
if (fromSetup) {
pose.scaleX = setup.scaleX;
pose.scaleY = setup.scaleY;
}
return;
}
let x, y;
const i = Timeline.search(frames, time, 3 /*ENTRIES*/);
const curveType = this.curves[i / 3 /*ENTRIES*/];
switch (curveType) {
case 0 /*LINEAR*/: {
const before = frames[i];
x = frames[i + 1 /*VALUE1*/];
y = frames[i + 2 /*VALUE2*/];
const t = (time - before) / (frames[i + 3 /*ENTRIES*/] - before);
x += (frames[i + 3 /*ENTRIES*/ + 1 /*VALUE1*/] - x) * t;
y += (frames[i + 3 /*ENTRIES*/ + 2 /*VALUE2*/] - y) * t;
break;
}
case 1 /*STEPPED*/:
x = frames[i + 1 /*VALUE1*/];
y = frames[i + 2 /*VALUE2*/];
break;
default:
x = this.getBezierValue(time, i, 1 /*VALUE1*/, curveType - 2 /*BEZIER*/);
y = this.getBezierValue(time, i, 2 /*VALUE2*/, curveType + 18 /*BEZIER_SIZE*/ - 2 /*BEZIER*/);
}
x *= setup.scaleX;
y *= setup.scaleY;
if (alpha === 1 && !add) {
pose.scaleX = x;
pose.scaleY = y;
}
else {
let bx = 0, by = 0;
if (fromSetup) {
bx = setup.scaleX;
by = setup.scaleY;
}
else {
bx = pose.scaleX;
by = pose.scaleY;
}
if (add) {
pose.scaleX = bx + (x - setup.scaleX) * alpha;
pose.scaleY = by + (y - setup.scaleY) * alpha;
}
else if (out) {
pose.scaleX = bx + (Math.abs(x) * Math.sign(bx) - bx) * alpha;
pose.scaleY = by + (Math.abs(y) * Math.sign(by) - by) * alpha;
}
else {
bx = Math.abs(bx) * Math.sign(x);
by = Math.abs(by) * Math.sign(y);
pose.scaleX = bx + (x - bx) * alpha;
pose.scaleY = by + (y - by) * alpha;
}
}
}
}
/** Changes a {@link BonePose.scaleX}. */
export class ScaleXTimeline extends BoneTimeline1 {
constructor(frameCount, bezierCount, boneIndex) {
super(frameCount, bezierCount, boneIndex, Property.scaleX);
}
apply1(pose, setup, time, alpha, fromSetup, add, out) {
pose.scaleX = this.getScaleValue(time, alpha, fromSetup, add, out, pose.scaleX, setup.scaleX);
}
}
/** Changes a {@link BonePose.scaleY}. */
export class ScaleYTimeline extends BoneTimeline1 {
constructor(frameCount, bezierCount, boneIndex) {
super(frameCount, bezierCount, boneIndex, Property.scaleY);
}
apply1(pose, setup, time, alpha, fromSetup, add, out) {
pose.scaleY = this.getScaleValue(time, alpha, fromSetup, add, out, pose.scaleY, setup.scaleY);
}
}
/** Changes {@link Bone.shearX} and {@link Bone.shearY}. */
export class ShearTimeline extends BoneTimeline2 {
constructor(frameCount, bezierCount, boneIndex) {
super(frameCount, bezierCount, boneIndex, Property.shearX, Property.shearY);
}
apply1(pose, setup, time, alpha, fromSetup, add, out) {
const frames = this.frames;
if (time < frames[0]) {
if (fromSetup) {
pose.shearX = setup.shearX;
pose.shearY = setup.shearY;
}
return;
}
let x = 0, y = 0;
const i = Timeline.search(frames, time, 3 /*ENTRIES*/);
const curveType = this.curves[i / 3 /*ENTRIES*/];
switch (curveType) {
case 0 /*LINEAR*/: {
const before = frames[i];
x = frames[i + 1 /*VALUE1*/];
y = frames[i + 2 /*VALUE2*/];
const t = (time - before) / (frames[i + 3 /*ENTRIES*/] - before);
x += (frames[i + 3 /*ENTRIES*/ + 1 /*VALUE1*/] - x) * t;
y += (frames[i + 3 /*ENTRIES*/ + 2 /*VALUE2*/] - y) * t;
break;
}
case 1 /*STEPPED*/:
x = frames[i + 1 /*VALUE1*/];
y = frames[i + 2 /*VALUE2*/];
break;
default:
x = this.getBezierValue(time, i, 1 /*VALUE1*/, curveType - 2 /*BEZIER*/);
y = this.getBezierValue(time, i, 2 /*VALUE2*/, curveType + 18 /*BEZIER_SIZE*/ - 2 /*BEZIER*/);
}
if (fromSetup) {
pose.shearX = setup.shearX + x * alpha;
pose.shearY = setup.shearY + y * alpha;
}
else if (add) {
pose.shearX += x * alpha;
pose.shearY += y * alpha;
}
else {
pose.shearX += (setup.shearX + x - pose.shearX) * alpha;
pose.shearY += (setup.shearY + y - pose.shearY) * alpha;
}
}
}
/** Changes {@link Bone.shearX} and {@link Bone.shearY}. */
export class ShearXTimeline extends BoneTimeline1 {
constructor(frameCount, bezierCount, boneIndex) {
super(frameCount, bezierCount, boneIndex, Property.shearX);
}
apply1(pose, setup, time, alpha, fromSetup, add, out) {
pose.shearX = this.getRelativeValue(time, alpha, fromSetup, add, pose.shearX, setup.shearX);
}
}
/** Changes {@link Bone.shearX} and {@link Bone.shearY}. */
export class ShearYTimeline extends BoneTimeline1 {
constructor(frameCount, bezierCount, boneIndex) {
super(frameCount, bezierCount, boneIndex, Property.shearY);
}
apply1(pose, setup, time, alpha, fromSetup, add, out) {
pose.shearY = this.getRelativeValue(time, alpha, fromSetup, add, pose.shearY, setup.shearY);
}
}
/** Changes {@link BonePose.inherit}. */
export class InheritTimeline extends Timeline {
boneIndex;
constructor(frameCount, boneIndex) {
super(frameCount, `${Property.inherit}|${boneIndex}`);
this.boneIndex = boneIndex;
this.instant = true;
}
getFrameEntries() {
return 2 /*ENTRIES*/;
}
/** Sets the inherit transform mode for the specified frame.
* @param frame Between 0 and `frameCount`, inclusive.
* @param time The frame time in seconds. */
setFrame(frame, time, inherit) {
frame *= 2 /*ENTRIES*/;
this.frames[frame] = time;
this.frames[frame + 1 /*INHERIT*/] = inherit;
}
apply(skeleton, lastTime, time, events, alpha, fromSetup, add, out, appliedPose) {
const bone = skeleton.bones[this.boneIndex];
if (!bone.active)
return;
const pose = appliedPose ? bone.appliedPose : bone.pose;
if (out) {
if (fromSetup)
pose.inherit = bone.data.setupPose.inherit;
}
else {
const frames = this.frames;
if (time < frames[0]) {
if (fromSetup)
pose.inherit = bone.data.setupPose.inherit;
}
else
pose.inherit = this.frames[Timeline.search(frames, time, 2 /*ENTRIES*/) + 1 /*INHERIT*/];
}
}
}
/** The base class for timelines that change any number of slot properties with a curve. */
export class SlotCurveTimeline extends CurveTimeline {
slotIndex;
constructor(frameCount, bezierCount, slotIndex, ...propertyIds) {
super(frameCount, bezierCount, ...propertyIds);
this.slotIndex = slotIndex;
}
apply(skeleton, lastTime, time, events, alpha, fromSetup, add, out, appliedPose) {
const slot = skeleton.slots[this.slotIndex];
if (slot.bone.active)
this.apply1(slot, appliedPose ? slot.appliedPose : slot.pose, time, alpha, fromSetup, add);
}
}
/** Changes {@link SlotPose.color}. */
export class RGBATimeline extends SlotCurveTimeline {
constructor(frameCount, bezierCount, slotIndex) {
super(frameCount, bezierCount, slotIndex, //
`${Property.rgb}|${slotIndex}`, //
`${Property.alpha}|${slotIndex}`);
}
getFrameEntries() {
return 5 /*ENTRIES*/;
}
/** Sets the time in seconds, red, green, blue, and alpha for the specified key frame. */
setFrame(frame, time, r, g, b, a) {
frame *= 5 /*ENTRIES*/;
this.frames[frame] = time;
this.frames[frame + 1 /*R*/] = r;
this.frames[frame + 2 /*G*/] = g;
this.frames[frame + 3 /*B*/] = b;
this.frames[frame + 4 /*A*/] = a;
}
apply1(slot, pose, time, alpha, fromSetup, add) {
const color = pose.color;
const frames = this.frames;
if (time < frames[0]) {
if (fromSetup)
color.setFromColor(slot.data.setupPose.color);
return;
}
let r = 0, g = 0, b = 0, a = 0;
const i = Timeline.search(frames, time, 5 /*ENTRIES*/);
const curveType = this.curves[i / 5 /*ENTRIES*/];
switch (curveType) {
case 0 /*LINEAR*/: {
const before = frames[i];
r = frames[i + 1 /*R*/];
g = frames[i + 2 /*G*/];
b = frames[i + 3 /*B*/];
a = frames[i + 4 /*A*/];
const t = (time - before) / (frames[i + 5 /*ENTRIES*/] - before);
r += (frames[i + 5 /*ENTRIES*/ + 1 /*R*/] - r) * t;
g += (frames[i + 5 /*ENTRIES*/ + 2 /*G*/] - g) * t;
b += (frames[i + 5 /*ENTRIES*/ + 3 /*B*/] - b) * t;
a += (frames[i + 5 /*ENTRIES*/ + 4 /*A*/] - a) * t;
break;
}
case 1 /*STEPPED*/:
r = frames[i + 1 /*R*/];
g = frames[i + 2 /*G*/];
b = frames[i + 3 /*B*/];
a = frames[i + 4 /*A*/];
break;
default:
r = this.getBezierValue(time, i, 1 /*R*/, curveType - 2 /*BEZIER*/);
g = this.getBezierValue(time, i, 2 /*G*/, curveType + 18 /*BEZIER_SIZE*/ - 2 /*BEZIER*/);
b = this.getBezierValue(time, i, 3 /*B*/, curveType + 18 /*BEZIER_SIZE*/ * 2 - 2 /*BEZIER*/);
a = this.getBezierValue(time, i, 4 /*A*/, curveType + 18 /*BEZIER_SIZE*/ * 3 - 2 /*BEZIER*/);
}
if (alpha === 1)
color.set(r, g, b, a);
else {
if (fromSetup) {
const setup = slot.data.setupPose.color;
color.set(setup.r + (r - setup.r) * alpha, setup.g + (g - setup.g) * alpha, setup.b + (b - setup.b) * alpha, setup.a + (a - setup.a) * alpha);
}
else
color.add((r - color.r) * alpha, (g - color.g) * alpha, (b - color.b) * alpha, (a - color.a) * alpha);
}
}
}
/** Changes RGB for a slot's {@link SlotPose.color}. */
export class RGBTimeline extends SlotCurveTimeline {
constructor(frameCount, bezierCount, slotIndex) {
super(frameCount, bezierCount, slotIndex, `${Property.rgb}|${slotIndex}`);
}
getFrameEntries() {
return 4 /*ENTRIES*/;
}
/** Sets the time in seconds, red, green, blue, and alpha for the specified key frame. */
setFrame(frame, time, r, g, b) {
frame <<= 2;
this.frames[frame] = time;
this.frames[frame + 1 /*R*/] = r;
this.frames[frame + 2 /*G*/] = g;
this.frames[frame + 3 /*B*/] = b;
}
apply1(slot, pose, time, alpha, fromSetup, add) {
const color = pose.color;
let r = 0, g = 0, b = 0;
const frames = this.frames;
if (time < frames[0]) {
if (fromSetup) {
const setup = slot.data.setupPose.color;
color.r = setup.r;
color.g = setup.g;
color.b = setup.b;
}
return;
}
const i = Timeline.search(frames, time, 4 /*ENTRIES*/);
const curveType = this.curves[i >> 2];
switch (curveType) {
case 0 /*LINEAR*/: {
const before = frames[i];
r = frames[i + 1 /*R*/];
g = frames[i + 2 /*G*/];
b = frames[i + 3 /*B*/];
const t = (time - before) / (frames[i + 4 /*ENTRIES*/] - before);
r += (frames[i + 4 /*ENTRIES*/ + 1 /*R*/] - r) * t;
g += (frames[i + 4 /*ENTRIES*/ + 2 /*G*/] - g) * t;
b += (frames[i + 4 /*ENTRIES*/ + 3 /*B*/] - b) * t;
break;
}
case 1 /*STEPPED*/:
r = frames[i + 1 /*R*/];
g = frames[i + 2 /*G*/];
b = frames[i + 3 /*B*/];
break;
default:
r = this.getBezierValue(time, i, 1 /*R*/, curveType - 2 /*BEZIER*/);
g = this.getBezierValue(time, i, 2 /*G*/, curveType + 18 /*BEZIER_SIZE*/ - 2 /*BEZIER*/);
b = this.getBezierValue(time, i, 3 /*B*/, curveType + 18 /*BEZIER_SIZE*/ * 2 - 2 /*BEZIER*/);
}
if (alpha !== 1) {
if (fromSetup) {
const setup = slot.data.setupPose.color;
r = setup.r + (r - setup.r) * alpha;
g = setup.g + (g - setup.g) * alpha;
b = setup.b + (b - setup.b) * alpha;
}
else {
r = color.r + (r - color.r) * alpha;
g = color.g + (g - color.g) * alpha;
b = color.b + (b - color.b) * alpha;
}
}
color.r = r < 0 ? 0 : (r > 1 ? 1 : r);
color.g = g < 0 ? 0 : (g > 1 ? 1 : g);
color.b = b < 0 ? 0 : (b > 1 ? 1 : b);
}
}
/** Changes alpha for a slot's {@link SlotPose.color}. */
export class AlphaTimeline extends CurveTimeline1 {
slotIndex = 0;
constructor(frameCount, bezierCount, slotIndex) {
super(frameCount, bezierCount, `${Property.alpha}|${slotIndex}`);
this.slotIndex = slotIndex;
}
apply(skeleton, lastTime, time, events, alpha, fromSetup, add, out, appliedPose) {
const slot = skeleton.slots[this.slotIndex];
if (!slot.bone.active)
return;
const color = (appliedPose ? slot.appliedPose : slot.pose).color;
let a = 0;
const frames = this.frames;
if (time < frames[0]) {
if (fromSetup)
color.a = slot.data.setupPose.color.a;
return;
}
a = this.getCurveValue(time);
if (alpha !== 1) {
if (fromSetup) {
const setup = slot.data.setupPose.color;
a = setup.a + (a - setup.a) * alpha;
}
else
a = color.a + (a - color.a) * alpha;
}
color.a = a < 0 ? 0 : (a > 1 ? 1 : a);
}
}
/** Changes {@link SlotPose.color} and {@link SlotPose.darkColor} for two color tinting. */
export class RGBA2Timeline extends SlotCurveTimeline {
constructor(frameCount, bezierCount, slotIndex) {
super(frameCount, bezierCount, slotIndex, //
`${Property.rgb}|${slotIndex}`, //
`${Property.alpha}|${slotIndex}`, //
`${Property.rgb2}|${slotIndex}`);
}
getFrameEntries() {
return 8 /*ENTRIES*/;
}
/** Sets the time in seconds, light, and dark colors for the specified key frame. */
setFrame(frame, time, r, g, b, a, r2, g2, b2) {
frame <<= 3;
this.frames[frame] = time;
this.frames[frame + 1 /*R*/] = r;
this.frames[frame + 2 /*G*/] = g;
this.frames[frame + 3 /*B*/] = b;
this.frames[frame + 4 /*A*/] = a;
this.frames[frame + 5 /*R2*/] = r2;
this.frames[frame + 6 /*G2*/] = g2;
this.frames[frame + 7 /*B2*/] = b2;
}
apply1(slot, pose, time, alpha, fromSetup, add) {
// biome-ignore lint/style/noNonNullAssertion: reference runtime
const light = pose.color, dark = pose.darkColor;
let r2 = 0, g2 = 0, b2 = 0;
const frames = this.frames;
if (time < frames[0]) {
if (fromSetup) {
const setup = slot.data.setupPose;
light.setFromColor(setup.color);
// biome-ignore lint/style/noNonNullAssertion: reference runtime
const setupDark = setup.darkColor;
dark.r = setupDark.r;
dark.g = setupDark.g;
dark.b = setupDark.b;
}
return;
}
let r = 0, g = 0, b = 0, a = 0;
const i = Timeline.search(frames, time, 8 /*ENTRIES*/);
const curveType = this.curves[i >> 3];
switch (curveType) {
case 0 /*LINEAR*/: {
const before = frames[i];
r = frames[i + 1 /*R*/];
g = frames[i + 2 /*G*/];
b = frames[i + 3 /*B*/];
a = frames[i + 4 /*A*/];
r2 = frames[i + 5 /*R2*/];
g2 = frames[i + 6 /*G2*/];
b2 = frames[i + 7 /*B2*/];
const t = (time - before) / (frames[i + 8 /*ENTRIES*/] - before);
r += (frames[i + 8 /*ENTRIES*/ + 1 /*R*/] - r) * t;
g += (frames[i + 8 /*ENTRIES*/ + 2 /*G*/] - g) * t;
b += (frames[i + 8 /*ENTRIES*/ + 3 /*B*/] - b) * t;
a += (frames[i + 8 /*ENTRIES*/ + 4 /*A*/] - a) * t;
r2 += (frames[i + 8 /*ENTRIES*/ + 5 /*R2*/] - r2) * t;
g2 += (frames[i + 8 /*ENTRIES*/ + 6 /*G2*/] - g2) * t;
b2 += (frames[i + 8 /*ENTRIES*/ + 7 /*B2*/] - b2) * t;
break;
}
case 1 /*STEPPED*/:
r = frames[i + 1 /*R*/];
g = frames[i + 2 /*G*/];
b = frames[i + 3 /*B*/];
a = frames[i + 4 /*A*/];
r2 = frames[i + 5 /*R2*/];
g2 = frames[i + 6 /*G2*/];
b2 = frames[i + 7 /*B2*/];
break;
default:
r = this.getBezierValue(time, i, 1 /*R*/, curveType - 2 /*BEZIER*/);
g = this.getBezierValue(time, i, 2 /*G*/, curveType + 18 /*BEZIER_SIZE*/ - 2 /*BEZIER*/);
b = this.getBezierValue(time, i, 3 /*B*/, curveType + 18 /*BEZIER_SIZE*/ * 2 - 2 /*BEZIER*/);
a = this.getBezierValue(time, i, 4 /*A*/, curveType + 18 /*BEZIER_SIZE*/ * 3 - 2 /*BEZIER*/);
r2 = this.getBezierValue(time, i, 5 /*R2*/, curveType + 18 /*BEZIER_SIZE*/ * 4 - 2 /*BEZIER*/);
g2 = this.getBezierValue(time, i, 6 /*G2*/, curveType + 18 /*BEZIER_SIZE*/ * 5 - 2 /*BEZIER*/);
b2 = this.getBezierValue(time, i, 7 /*B2*/, curveType + 18 /*BEZIER_SIZE*/ * 6 - 2 /*BEZIER*/);
}
if (alpha === 1)
light.set(r, g, b, a);
else if (fromSetup) {
const setupPose = slot.data.setupPose;
let setup = setupPose.color;
light.set(setup.r + (r - setup.r) * alpha, setup.g + (g - setup.g) * alpha, setup.b + (b - setup.b) * alpha, setup.a + (a - setup.a) * alpha);
// biome-ignore lint/style/noNonNullAssertion: reference runtime
setup = setupPose.darkColor;
r2 = setup.r + (r2 - setup.r) * alpha;
g2 = setup.g + (g2 - setup.g) * alpha;
b2 = setup.b + (b2 - setup.b) * alpha;
}
else {
light.add((r - light.r) * alpha, (g - light.g) * alpha, (b - light.b) * alpha, (a - light.a) * alpha);
r2 = dark.r + (r2 - dark.r) * alpha;
g2 = dark.g + (g2 - dark.g) * alpha;
b2 = dark.b + (b2 - dark.b) * alpha;
}
dark.r = r2 < 0 ? 0 : (r2 > 1 ? 1 : r2);
dark.g = g2 < 0 ? 0 : (g2 > 1 ? 1 : g2);
dark.b = b2 < 0 ? 0 : (b2 > 1 ? 1 : b2);
}
}
/** Changes {@link SlotPose.color} and {@link SlotPose.darkColor} for two color tinting. */
export class RGB2Timeline extends SlotCurveTimeline {
constructor(frameCount, bezierCount, slotIndex) {
super(frameCount, bezierCount, slotIndex, //
`${Property.rgb}|${slotIndex}`, //
`${Property.rgb2}|${slotIndex}`);
}
getFrameEntries() {
return 7 /*ENTRIES*/;
}
/** Sets the time in seconds, light, and dark colors for the specified key frame. */
setFrame(frame, time, r, g, b, r2, g2, b2) {
frame *= 7 /*ENTRIES*/;
this.frames[frame] = time;
this.frames[frame + 1 /*R*/] = r;
this.frames[frame + 2 /*G*/] = g;
this.frames[frame + 3 /*B*/] = b;
this.frames[frame + 4 /*R2*/] = r2;
this.frames[frame + 5 /*G2*/] = g2;
this.frames[frame + 6 /*B2*/] = b2;
}
apply1(slot, pose, time, alpha, fromSetup, add) {
// biome-ignore lint/style/noNonNullAssertion: reference runtime
const light = pose.color, dark = pose.darkColor;
let r = 0, g = 0, b = 0, r2 = 0, g2 = 0, b2 = 0;
const frames = this.frames;
if (time < frames[0]) {
if (fromSetup) {
const setup = slot.data.setupPose;
// biome-ignore lint/style/noNonNullAssertion: reference runtime
const setupLight = setup.color, setupDark = setup.darkColor;
light.r = setupLight.r;
light.g = setupLight.g;
light.b = setupLight.b;
dark.r = setupDark.r;
dark.g = setupDark.g;
dark.b = setupDark.b;
}
return;
}
const i = Timeline.search(frames, time, 7 /*ENTRIES*/);
const curveType = this.curves[i / 7 /*ENTRIES*/];
switch (curveType) {
case 0 /*LINEAR*/: {
const before = frames[i];
r = frames[i + 1 /*R*/];
g = frames[i + 2 /*G*/];
b = frames[i + 3 /*B*/];
r2 = frames[i + 4 /*R2*/];
g2 = frames[i + 5 /*G2*/];
b2 = frames[i + 6 /*B2*/];
const t = (time - before) / (frames[i + 7 /*ENTRIES*/] - before);
r += (frames[i + 7 /*ENTRIES*/ + 1 /*R*/] - r) * t;
g += (frames[i + 7 /*ENTRIES*/ + 2 /*G*/] - g) * t;
b += (frames[i + 7 /*ENTRIES*/ + 3 /*B*/] - b) * t;
r2 += (frames[i + 7 /*ENTRIES*/ + 4 /*R2*/] - r2) * t;
g2 += (frames[i + 7 /*ENTRIES*/ + 5 /*G2*/] - g2) * t;
b2 += (frames[i + 7 /*ENTRIES*/ + 6 /*B2*/] - b2) * t;
break;
}
case 1 /*STEPPED*/:
r = frames[i + 1 /*R*/];
g = frames[i + 2 /*G*/];
b = frames[i + 3 /*B*/];
r2 = frames[i + 4 /*R2*/];
g2 = frames[i + 5 /*G2*/];
b2 = frames[i + 6 /*B2*/];
break;
default:
r = this.getBezierValue(time, i, 1 /*R*/, curveType - 2 /*BEZIER*/);
g = this.getBezierValue(time, i, 2 /*G*/, curveType + 18 /*BEZIER_SIZE*/ - 2 /*BEZIER*/);
b = this.getBezierValue(time, i, 3 /*B*/, curveType + 18 /*BEZIER_SIZE*/ * 2 - 2 /*BEZIER*/);
r2 = this.getBezierValue(time, i, 4 /*R2*/, curveType + 18 /*BEZIER_SIZE*/ * 3 - 2 /*BEZIER*/);
g2 = this.getBezierValue(time, i, 5 /*G2*/, curveType + 18 /*BEZIER_SIZE*/ * 4 - 2 /*BEZIER*/);
b2 = this.getBezierValue(time, i, 6 /*B2*/, curveType + 18 /*BEZIER_SIZE*/ * 5 - 2 /*BEZIER*/);
}
if (alpha !== 1) {
if (fromSetup) {
const setupPose = slot.data.setupPose;
let setup = setupPose.color;
r = setup.r + (r - setup.r) * alpha;
g = setup.g + (g - setup.g) * alpha;
b = setup.b + (b - setup.b) * alpha;
// biome-ignore lint/style/noNonNullAssertion: reference runtime
setup = setupPose.darkColor;
r2 = setup.r + (r2 - setup.r) * alpha;
g2 = setup.g + (g2 - setup.g) * alpha;
b2 = setup.b + (b2 - setup.b) * alpha;
}
else {
r = light.r + (r - light.r) * alpha;
g = light.g + (g - light.g) * alpha;
b = light.b + (b - light.b) * alpha;
r2 = dark.r + (r2 - dark.r) * alpha;
g2 = dark.g + (g2 - dark.g) * alpha;
b2 = dark.b + (b2 - dark.b) * alpha;
}
}
light.r = r < 0 ? 0 : (r > 1 ? 1 : r);
light.g = g < 0 ? 0 : (g > 1 ? 1 : g);
light.b = b < 0 ? 0 : (b > 1 ? 1 : b);
dark.r = r2 < 0 ? 0 : (r2 > 1 ? 1 : r2);
dark.g = g2 < 0 ? 0 : (g2 > 1 ? 1 : g2);
dark.b = b2 < 0 ? 0 : (b2 > 1 ? 1 : b2);
}
}
/** Changes {@link SlotPose.ttachment}. */
export class AttachmentTimeline extends Timeline {
slotIndex = 0;
/** The attachment name for each key frame. May contain null values to clear the attachment. */
attachmentNames;
constructor(frameCount, slotIndex) {
super(frameCount, `${Property.attachment}|${slotIndex}`);
this.slotIndex = slotIndex;
this.attachmentNames = new Array(frameCount);
this.instant = true;
}
getFrameCount() {
return this.frames.length;
}
/** Sets the time in seconds and the attachment name for the specified key frame. */
setFrame(frame, time, attachmentName) {
this.frames[frame] = time;
this.attachmentNames[frame] = attachmentName;
}
apply(skeleton, lastTime, time, events, alpha, fromSetup, add, out, appliedPose) {
const slot = skeleton.slots[this.slotIndex];
if (!slot.bone.active)
return;
const pose = appliedPose ? slot.appliedPose : slot.pose;
if (out || time < this.frames[0]) {
if (fromSetup)