@esotericsoftware/spine-core
Version:
The official Spine Runtimes for the web.
1,179 lines • 337 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 { VertexAttachment } from "./attachments/Attachment.js";
import { StringSet, Utils, MathUtils } from "./Utils.js";
import { SequenceMode, SequenceModeValues } from "./attachments/Sequence.js";
/** A simple container for a list of timelines and a name. */
export class Animation {
/** The animation's name, which is unique across all animations in the skeleton. */
name;
timelines = [];
timelineIds = new StringSet();
/** The duration of the animation in seconds, which is the highest time of all keys in the timeline. */
duration;
constructor(name, timelines, duration) {
if (!name)
throw new Error("name cannot be null.");
this.name = name;
this.setTimelines(timelines);
this.duration = duration;
}
setTimelines(timelines) {
if (!timelines)
throw new Error("timelines cannot be null.");
this.timelines = timelines;
this.timelineIds.clear();
for (var i = 0; i < timelines.length; i++)
this.timelineIds.addAll(timelines[i].getPropertyIds());
}
hasTimeline(ids) {
for (let i = 0; i < ids.length; i++)
if (this.timelineIds.contains(ids[i]))
return true;
return false;
}
/** Applies all the animation's timelines to the specified skeleton.
*
* See Timeline {@link Timeline#apply(Skeleton, float, float, Array, float, MixBlend, MixDirection)}.
* @param loop If true, the animation repeats after {@link #getDuration()}.
* @param events May be null to ignore fired events. */
apply(skeleton, lastTime, time, loop, events, alpha, blend, direction) {
if (!skeleton)
throw new Error("skeleton cannot be null.");
if (loop && this.duration != 0) {
time %= this.duration;
if (lastTime > 0)
lastTime %= this.duration;
}
let timelines = this.timelines;
for (let i = 0, n = timelines.length; i < n; i++)
timelines[i].apply(skeleton, lastTime, time, events, alpha, blend, direction);
}
}
/** Controls how a timeline value is mixed with the setup pose value or current pose value when a timeline's `alpha`
* < 1.
*
* See Timeline {@link Timeline#apply(Skeleton, float, float, Array, float, MixBlend, MixDirection)}. */
export var MixBlend;
(function (MixBlend) {
/** Transitions from the setup value to the timeline value (the current value is not used). Before the first key, the setup
* value is set. */
MixBlend[MixBlend["setup"] = 0] = "setup";
/** Transitions from the current value to the timeline value. Before the first key, transitions from the current value to
* the setup value. Timelines which perform instant transitions, such as {@link DrawOrderTimeline} or
* {@link AttachmentTimeline}, use the setup value before the first key.
*
* `first` is intended for the first animations applied, not for animations layered on top of those. */
MixBlend[MixBlend["first"] = 1] = "first";
/** Transitions from the current value to the timeline value. No change is made before the first key (the current value is
* kept until the first key).
*
* `replace` is intended for animations layered on top of others, not for the first animations applied. */
MixBlend[MixBlend["replace"] = 2] = "replace";
/** Transitions from the current value to the current value plus the timeline value. No change is made before the first key
* (the current value is kept until the first key).
*
* `add` is intended for animations layered on top of others, not for the first animations applied. Properties
* keyed by additive animations must be set manually or by another animation before applying the additive animations, else
* the property values will increase continually. */
MixBlend[MixBlend["add"] = 3] = "add";
})(MixBlend || (MixBlend = {}));
/** Indicates whether a timeline's `alpha` is mixing out over time toward 0 (the setup or current pose value) or
* mixing in toward 1 (the timeline's value).
*
* See Timeline {@link Timeline#apply(Skeleton, float, float, Array, float, MixBlend, MixDirection)}. */
export var MixDirection;
(function (MixDirection) {
MixDirection[MixDirection["mixIn"] = 0] = "mixIn";
MixDirection[MixDirection["mixOut"] = 1] = "mixOut";
})(MixDirection || (MixDirection = {}));
const Property = {
rotate: 0,
x: 1,
y: 2,
scaleX: 3,
scaleY: 4,
shearX: 5,
shearY: 6,
inherit: 7,
rgb: 8,
alpha: 9,
rgb2: 10,
attachment: 11,
deform: 12,
event: 13,
drawOrder: 14,
ikConstraint: 15,
transformConstraint: 16,
pathConstraintPosition: 17,
pathConstraintSpacing: 18,
pathConstraintMix: 19,
physicsConstraintInertia: 20,
physicsConstraintStrength: 21,
physicsConstraintDamping: 22,
physicsConstraintMass: 23,
physicsConstraintWind: 24,
physicsConstraintGravity: 25,
physicsConstraintMix: 26,
physicsConstraintReset: 27,
sequence: 28,
};
/** The interface for all timelines. */
export class Timeline {
propertyIds;
frames;
constructor(frameCount, propertyIds) {
this.propertyIds = propertyIds;
this.frames = Utils.newFloatArray(frameCount * this.getFrameEntries());
}
getPropertyIds() {
return this.propertyIds;
}
getFrameEntries() {
return 1;
}
getFrameCount() {
return this.frames.length / this.getFrameEntries();
}
getDuration() {
return this.frames[this.frames.length - this.getFrameEntries()];
}
static search1(frames, time) {
let n = frames.length;
for (let i = 1; i < n; i++)
if (frames[i] > time)
return i - 1;
return n - 1;
}
static search(frames, time, step) {
let n = frames.length;
for (let i = step; i < n; i += step)
if (frames[i] > time)
return i - step;
return n - step;
}
}
/** The base class for timelines that use interpolation between key frame values. */
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 <code>bezierCount</code> (specified in the constructor) was larger
* than the actual number of Bezier curves. */
shrink(bezierCount) {
let size = this.getFrameCount() + bezierCount * 18 /*BEZIER_SIZE*/;
if (this.curves.length > size) {
let 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 <code>bezierCount - 1</code> (specified
* in the constructor), inclusive.
* @param frame Between 0 and <code>frameCount - 1</code>, 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) {
let curves = this.curves;
let i = this.getFrameCount() + bezier * 18 /*BEZIER_SIZE*/;
if (value == 0)
curves[frame] = 2 /*BEZIER*/ + i;
let tmpx = (time1 - cx1 * 2 + cx2) * 0.03, tmpy = (value1 - cy1 * 2 + cy2) * 0.03;
let 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 #getFrames()} for the values of the frame before <code>time</code>.
* @param valueOffset The offset from <code>frameIndex</code> to the value this curve is used for.
* @param i The index of the Bezier segments. See {@link #getCurveType(int)}. */
getBezierValue(time, frameIndex, valueOffset, i) {
let curves = this.curves;
if (curves[i] > time) {
let x = this.frames[frameIndex], y = this.frames[frameIndex + valueOffset];
return y + (time - x) / (curves[i] - x) * (curves[i + 1] - y);
}
let n = i + 18 /*BEZIER_SIZE*/;
for (i += 2; i < n; i += 2) {
if (curves[i] >= time) {
let x = curves[i - 2], y = curves[i - 1];
return y + (time - x) / (curves[i] - x) * (curves[i + 1] - y);
}
}
frameIndex += this.getFrameEntries();
let x = curves[n - 2], y = curves[n - 1];
return y + (time - x) / (this.frames[frameIndex] - x) * (this.frames[frameIndex + valueOffset] - y);
}
}
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 <code>frameCount</code>, 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) {
let frames = this.frames;
let i = frames.length - 2;
for (let ii = 2; ii <= i; ii += 2) {
if (frames[ii] > time) {
i = ii - 2;
break;
}
}
let curveType = this.curves[i >> 1];
switch (curveType) {
case 0 /*LINEAR*/:
let 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*/);
}
getRelativeValue(time, alpha, blend, current, setup) {
if (time < this.frames[0]) {
switch (blend) {
case MixBlend.setup:
return setup;
case MixBlend.first:
return current + (setup - current) * alpha;
}
return current;
}
let value = this.getCurveValue(time);
switch (blend) {
case MixBlend.setup:
return setup + value * alpha;
case MixBlend.first:
case MixBlend.replace:
value += setup - current;
}
return current + value * alpha;
}
getAbsoluteValue(time, alpha, blend, current, setup) {
if (time < this.frames[0]) {
switch (blend) {
case MixBlend.setup:
return setup;
case MixBlend.first:
return current + (setup - current) * alpha;
}
return current;
}
let value = this.getCurveValue(time);
if (blend == MixBlend.setup)
return setup + (value - setup) * alpha;
return current + (value - current) * alpha;
}
getAbsoluteValue2(time, alpha, blend, current, setup, value) {
if (time < this.frames[0]) {
switch (blend) {
case MixBlend.setup:
return setup;
case MixBlend.first:
return current + (setup - current) * alpha;
}
return current;
}
if (blend == MixBlend.setup)
return setup + (value - setup) * alpha;
return current + (value - current) * alpha;
}
getScaleValue(time, alpha, blend, direction, current, setup) {
const frames = this.frames;
if (time < frames[0]) {
switch (blend) {
case MixBlend.setup:
return setup;
case MixBlend.first:
return current + (setup - current) * alpha;
}
return current;
}
let value = this.getCurveValue(time) * setup;
if (alpha == 1) {
if (blend == MixBlend.add)
return current + value - setup;
return value;
}
// Mixing out uses sign of setup or current pose, else use sign of key.
if (direction == MixDirection.mixOut) {
switch (blend) {
case MixBlend.setup:
return setup + (Math.abs(value) * MathUtils.signum(setup) - setup) * alpha;
case MixBlend.first:
case MixBlend.replace:
return current + (Math.abs(value) * MathUtils.signum(current) - current) * alpha;
}
}
else {
let s = 0;
switch (blend) {
case MixBlend.setup:
s = Math.abs(setup) * MathUtils.signum(value);
return s + (value - s) * alpha;
case MixBlend.first:
case MixBlend.replace:
s = Math.abs(current) * MathUtils.signum(value);
return s + (value - s) * alpha;
}
}
return current + (value - setup) * alpha;
}
}
/** The base class for a {@link CurveTimeline} which sets two properties. */
export class CurveTimeline2 extends CurveTimeline {
/** @param bezierCount The maximum number of Bezier curves. See {@link #shrink(int)}.
* @param propertyIds Unique identifiers for the properties the timeline modifies. */
constructor(frameCount, bezierCount, propertyId1, propertyId2) {
super(frameCount, bezierCount, [propertyId1, propertyId2]);
}
getFrameEntries() {
return 3 /*ENTRIES*/;
}
/** Sets the time and values for the specified frame.
* @param frame Between 0 and <code>frameCount</code>, 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;
}
}
/** Changes a bone's local {@link Bone#rotation}. */
export class RotateTimeline extends CurveTimeline1 {
boneIndex = 0;
constructor(frameCount, bezierCount, boneIndex) {
super(frameCount, bezierCount, Property.rotate + "|" + boneIndex);
this.boneIndex = boneIndex;
}
apply(skeleton, lastTime, time, events, alpha, blend, direction) {
let bone = skeleton.bones[this.boneIndex];
if (bone.active)
bone.rotation = this.getRelativeValue(time, alpha, blend, bone.rotation, bone.data.rotation);
}
}
/** Changes a bone's local {@link Bone#x} and {@link Bone#y}. */
export class TranslateTimeline extends CurveTimeline2 {
boneIndex = 0;
constructor(frameCount, bezierCount, boneIndex) {
super(frameCount, bezierCount, Property.x + "|" + boneIndex, Property.y + "|" + boneIndex);
this.boneIndex = boneIndex;
}
apply(skeleton, lastTime, time, events, alpha, blend, direction) {
let bone = skeleton.bones[this.boneIndex];
if (!bone.active)
return;
let frames = this.frames;
if (time < frames[0]) {
switch (blend) {
case MixBlend.setup:
bone.x = bone.data.x;
bone.y = bone.data.y;
return;
case MixBlend.first:
bone.x += (bone.data.x - bone.x) * alpha;
bone.y += (bone.data.y - bone.y) * alpha;
}
return;
}
let x = 0, y = 0;
let i = Timeline.search(frames, time, 3 /*ENTRIES*/);
let curveType = this.curves[i / 3 /*ENTRIES*/];
switch (curveType) {
case 0 /*LINEAR*/:
let before = frames[i];
x = frames[i + 1 /*VALUE1*/];
y = frames[i + 2 /*VALUE2*/];
let 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*/);
}
switch (blend) {
case MixBlend.setup:
bone.x = bone.data.x + x * alpha;
bone.y = bone.data.y + y * alpha;
break;
case MixBlend.first:
case MixBlend.replace:
bone.x += (bone.data.x + x - bone.x) * alpha;
bone.y += (bone.data.y + y - bone.y) * alpha;
break;
case MixBlend.add:
bone.x += x * alpha;
bone.y += y * alpha;
}
}
}
/** Changes a bone's local {@link Bone#x}. */
export class TranslateXTimeline extends CurveTimeline1 {
boneIndex = 0;
constructor(frameCount, bezierCount, boneIndex) {
super(frameCount, bezierCount, Property.x + "|" + boneIndex);
this.boneIndex = boneIndex;
}
apply(skeleton, lastTime, time, events, alpha, blend, direction) {
let bone = skeleton.bones[this.boneIndex];
if (bone.active)
bone.x = this.getRelativeValue(time, alpha, blend, bone.x, bone.data.x);
}
}
/** Changes a bone's local {@link Bone#x}. */
export class TranslateYTimeline extends CurveTimeline1 {
boneIndex = 0;
constructor(frameCount, bezierCount, boneIndex) {
super(frameCount, bezierCount, Property.y + "|" + boneIndex);
this.boneIndex = boneIndex;
}
apply(skeleton, lastTime, time, events, alpha, blend, direction) {
let bone = skeleton.bones[this.boneIndex];
if (bone.active)
bone.y = this.getRelativeValue(time, alpha, blend, bone.y, bone.data.y);
}
}
/** Changes a bone's local {@link Bone#scaleX)} and {@link Bone#scaleY}. */
export class ScaleTimeline extends CurveTimeline2 {
boneIndex = 0;
constructor(frameCount, bezierCount, boneIndex) {
super(frameCount, bezierCount, Property.scaleX + "|" + boneIndex, Property.scaleY + "|" + boneIndex);
this.boneIndex = boneIndex;
}
apply(skeleton, lastTime, time, events, alpha, blend, direction) {
let bone = skeleton.bones[this.boneIndex];
if (!bone.active)
return;
let frames = this.frames;
if (time < frames[0]) {
switch (blend) {
case MixBlend.setup:
bone.scaleX = bone.data.scaleX;
bone.scaleY = bone.data.scaleY;
return;
case MixBlend.first:
bone.scaleX += (bone.data.scaleX - bone.scaleX) * alpha;
bone.scaleY += (bone.data.scaleY - bone.scaleY) * alpha;
}
return;
}
let x, y;
let i = Timeline.search(frames, time, 3 /*ENTRIES*/);
let curveType = this.curves[i / 3 /*ENTRIES*/];
switch (curveType) {
case 0 /*LINEAR*/:
let before = frames[i];
x = frames[i + 1 /*VALUE1*/];
y = frames[i + 2 /*VALUE2*/];
let 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 *= bone.data.scaleX;
y *= bone.data.scaleY;
if (alpha == 1) {
if (blend == MixBlend.add) {
bone.scaleX += x - bone.data.scaleX;
bone.scaleY += y - bone.data.scaleY;
}
else {
bone.scaleX = x;
bone.scaleY = y;
}
}
else {
let bx = 0, by = 0;
if (direction == MixDirection.mixOut) {
switch (blend) {
case MixBlend.setup:
bx = bone.data.scaleX;
by = bone.data.scaleY;
bone.scaleX = bx + (Math.abs(x) * MathUtils.signum(bx) - bx) * alpha;
bone.scaleY = by + (Math.abs(y) * MathUtils.signum(by) - by) * alpha;
break;
case MixBlend.first:
case MixBlend.replace:
bx = bone.scaleX;
by = bone.scaleY;
bone.scaleX = bx + (Math.abs(x) * MathUtils.signum(bx) - bx) * alpha;
bone.scaleY = by + (Math.abs(y) * MathUtils.signum(by) - by) * alpha;
break;
case MixBlend.add:
bone.scaleX += (x - bone.data.scaleX) * alpha;
bone.scaleY += (y - bone.data.scaleY) * alpha;
}
}
else {
switch (blend) {
case MixBlend.setup:
bx = Math.abs(bone.data.scaleX) * MathUtils.signum(x);
by = Math.abs(bone.data.scaleY) * MathUtils.signum(y);
bone.scaleX = bx + (x - bx) * alpha;
bone.scaleY = by + (y - by) * alpha;
break;
case MixBlend.first:
case MixBlend.replace:
bx = Math.abs(bone.scaleX) * MathUtils.signum(x);
by = Math.abs(bone.scaleY) * MathUtils.signum(y);
bone.scaleX = bx + (x - bx) * alpha;
bone.scaleY = by + (y - by) * alpha;
break;
case MixBlend.add:
bone.scaleX += (x - bone.data.scaleX) * alpha;
bone.scaleY += (y - bone.data.scaleY) * alpha;
}
}
}
}
}
/** Changes a bone's local {@link Bone#scaleX)} and {@link Bone#scaleY}. */
export class ScaleXTimeline extends CurveTimeline1 {
boneIndex = 0;
constructor(frameCount, bezierCount, boneIndex) {
super(frameCount, bezierCount, Property.scaleX + "|" + boneIndex);
this.boneIndex = boneIndex;
}
apply(skeleton, lastTime, time, events, alpha, blend, direction) {
let bone = skeleton.bones[this.boneIndex];
if (bone.active)
bone.scaleX = this.getScaleValue(time, alpha, blend, direction, bone.scaleX, bone.data.scaleX);
}
}
/** Changes a bone's local {@link Bone#scaleX)} and {@link Bone#scaleY}. */
export class ScaleYTimeline extends CurveTimeline1 {
boneIndex = 0;
constructor(frameCount, bezierCount, boneIndex) {
super(frameCount, bezierCount, Property.scaleY + "|" + boneIndex);
this.boneIndex = boneIndex;
}
apply(skeleton, lastTime, time, events, alpha, blend, direction) {
let bone = skeleton.bones[this.boneIndex];
if (bone.active)
bone.scaleY = this.getScaleValue(time, alpha, blend, direction, bone.scaleY, bone.data.scaleY);
}
}
/** Changes a bone's local {@link Bone#shearX} and {@link Bone#shearY}. */
export class ShearTimeline extends CurveTimeline2 {
boneIndex = 0;
constructor(frameCount, bezierCount, boneIndex) {
super(frameCount, bezierCount, Property.shearX + "|" + boneIndex, Property.shearY + "|" + boneIndex);
this.boneIndex = boneIndex;
}
apply(skeleton, lastTime, time, events, alpha, blend, direction) {
let bone = skeleton.bones[this.boneIndex];
if (!bone.active)
return;
let frames = this.frames;
if (time < frames[0]) {
switch (blend) {
case MixBlend.setup:
bone.shearX = bone.data.shearX;
bone.shearY = bone.data.shearY;
return;
case MixBlend.first:
bone.shearX += (bone.data.shearX - bone.shearX) * alpha;
bone.shearY += (bone.data.shearY - bone.shearY) * alpha;
}
return;
}
let x = 0, y = 0;
let i = Timeline.search(frames, time, 3 /*ENTRIES*/);
let curveType = this.curves[i / 3 /*ENTRIES*/];
switch (curveType) {
case 0 /*LINEAR*/:
let before = frames[i];
x = frames[i + 1 /*VALUE1*/];
y = frames[i + 2 /*VALUE2*/];
let 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*/);
}
switch (blend) {
case MixBlend.setup:
bone.shearX = bone.data.shearX + x * alpha;
bone.shearY = bone.data.shearY + y * alpha;
break;
case MixBlend.first:
case MixBlend.replace:
bone.shearX += (bone.data.shearX + x - bone.shearX) * alpha;
bone.shearY += (bone.data.shearY + y - bone.shearY) * alpha;
break;
case MixBlend.add:
bone.shearX += x * alpha;
bone.shearY += y * alpha;
}
}
}
/** Changes a bone's local {@link Bone#shearX} and {@link Bone#shearY}. */
export class ShearXTimeline extends CurveTimeline1 {
boneIndex = 0;
constructor(frameCount, bezierCount, boneIndex) {
super(frameCount, bezierCount, Property.shearX + "|" + boneIndex);
this.boneIndex = boneIndex;
}
apply(skeleton, lastTime, time, events, alpha, blend, direction) {
let bone = skeleton.bones[this.boneIndex];
if (bone.active)
bone.shearX = this.getRelativeValue(time, alpha, blend, bone.shearX, bone.data.shearX);
}
}
/** Changes a bone's local {@link Bone#shearX} and {@link Bone#shearY}. */
export class ShearYTimeline extends CurveTimeline1 {
boneIndex = 0;
constructor(frameCount, bezierCount, boneIndex) {
super(frameCount, bezierCount, Property.shearY + "|" + boneIndex);
this.boneIndex = boneIndex;
}
apply(skeleton, lastTime, time, events, alpha, blend, direction) {
let bone = skeleton.bones[this.boneIndex];
if (bone.active)
bone.shearY = this.getRelativeValue(time, alpha, blend, bone.shearY, bone.data.shearY);
}
}
export class InheritTimeline extends Timeline {
boneIndex = 0;
constructor(frameCount, boneIndex) {
super(frameCount, [Property.inherit + "|" + boneIndex]);
this.boneIndex = boneIndex;
}
getFrameEntries() {
return 2 /*ENTRIES*/;
}
/** Sets the transform mode for the specified frame.
* @param frame Between 0 and <code>frameCount</code>, 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, blend, direction) {
let bone = skeleton.bones[this.boneIndex];
if (!bone.active)
return;
if (direction == MixDirection.mixOut) {
if (blend == MixBlend.setup)
bone.inherit = bone.data.inherit;
return;
}
let frames = this.frames;
if (time < frames[0]) {
if (blend == MixBlend.setup || blend == MixBlend.first)
bone.inherit = bone.data.inherit;
return;
}
bone.inherit = this.frames[Timeline.search(frames, time, 2 /*ENTRIES*/) + 1 /*INHERIT*/];
}
}
/** Changes a slot's {@link Slot#color}. */
export class RGBATimeline extends CurveTimeline {
slotIndex = 0;
constructor(frameCount, bezierCount, slotIndex) {
super(frameCount, bezierCount, [
Property.rgb + "|" + slotIndex,
Property.alpha + "|" + slotIndex
]);
this.slotIndex = 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;
}
apply(skeleton, lastTime, time, events, alpha, blend, direction) {
let slot = skeleton.slots[this.slotIndex];
if (!slot.bone.active)
return;
let frames = this.frames;
let color = slot.color;
if (time < frames[0]) {
let setup = slot.data.color;
switch (blend) {
case MixBlend.setup:
color.setFromColor(setup);
return;
case MixBlend.first:
color.add((setup.r - color.r) * alpha, (setup.g - color.g) * alpha, (setup.b - color.b) * alpha, (setup.a - color.a) * alpha);
}
return;
}
let r = 0, g = 0, b = 0, a = 0;
let i = Timeline.search(frames, time, 5 /*ENTRIES*/);
let curveType = this.curves[i / 5 /*ENTRIES*/];
switch (curveType) {
case 0 /*LINEAR*/:
let before = frames[i];
r = frames[i + 1 /*R*/];
g = frames[i + 2 /*G*/];
b = frames[i + 3 /*B*/];
a = frames[i + 4 /*A*/];
let 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 (blend == MixBlend.setup)
color.setFromColor(slot.data.color);
color.add((r - color.r) * alpha, (g - color.g) * alpha, (b - color.b) * alpha, (a - color.a) * alpha);
}
}
}
/** Changes a slot's {@link Slot#color}. */
export class RGBTimeline extends CurveTimeline {
slotIndex = 0;
constructor(frameCount, bezierCount, slotIndex) {
super(frameCount, bezierCount, [
Property.rgb + "|" + slotIndex
]);
this.slotIndex = 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;
}
apply(skeleton, lastTime, time, events, alpha, blend, direction) {
let slot = skeleton.slots[this.slotIndex];
if (!slot.bone.active)
return;
let frames = this.frames;
let color = slot.color;
if (time < frames[0]) {
let setup = slot.data.color;
switch (blend) {
case MixBlend.setup:
color.r = setup.r;
color.g = setup.g;
color.b = setup.b;
return;
case MixBlend.first:
color.r += (setup.r - color.r) * alpha;
color.g += (setup.g - color.g) * alpha;
color.b += (setup.b - color.b) * alpha;
}
return;
}
let r = 0, g = 0, b = 0;
let i = Timeline.search(frames, time, 4 /*ENTRIES*/);
let curveType = this.curves[i >> 2];
switch (curveType) {
case 0 /*LINEAR*/:
let before = frames[i];
r = frames[i + 1 /*R*/];
g = frames[i + 2 /*G*/];
b = frames[i + 3 /*B*/];
let 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) {
color.r = r;
color.g = g;
color.b = b;
}
else {
if (blend == MixBlend.setup) {
let setup = slot.data.color;
color.r = setup.r;
color.g = setup.g;
color.b = setup.b;
}
color.r += (r - color.r) * alpha;
color.g += (g - color.g) * alpha;
color.b += (b - color.b) * alpha;
}
}
}
/** Changes a bone's local {@link Bone#shearX} and {@link Bone#shearY}. */
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, blend, direction) {
let slot = skeleton.slots[this.slotIndex];
if (!slot.bone.active)
return;
let color = slot.color;
if (time < this.frames[0]) {
let setup = slot.data.color;
switch (blend) {
case MixBlend.setup:
color.a = setup.a;
return;
case MixBlend.first:
color.a += (setup.a - color.a) * alpha;
}
return;
}
let a = this.getCurveValue(time);
if (alpha == 1)
color.a = a;
else {
if (blend == MixBlend.setup)
color.a = slot.data.color.a;
color.a += (a - color.a) * alpha;
}
}
}
/** Changes a slot's {@link Slot#color} and {@link Slot#darkColor} for two color tinting. */
export class RGBA2Timeline extends CurveTimeline {
slotIndex = 0;
constructor(frameCount, bezierCount, slotIndex) {
super(frameCount, bezierCount, [
Property.rgb + "|" + slotIndex,
Property.alpha + "|" + slotIndex,
Property.rgb2 + "|" + slotIndex
]);
this.slotIndex = 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;
}
apply(skeleton, lastTime, time, events, alpha, blend, direction) {
let slot = skeleton.slots[this.slotIndex];
if (!slot.bone.active)
return;
let frames = this.frames;
let light = slot.color, dark = slot.darkColor;
if (time < frames[0]) {
let setupLight = slot.data.color, setupDark = slot.data.darkColor;
switch (blend) {
case MixBlend.setup:
light.setFromColor(setupLight);
dark.r = setupDark.r;
dark.g = setupDark.g;
dark.b = setupDark.b;
return;
case MixBlend.first:
light.add((setupLight.r - light.r) * alpha, (setupLight.g - light.g) * alpha, (setupLight.b - light.b) * alpha, (setupLight.a - light.a) * alpha);
dark.r += (setupDark.r - dark.r) * alpha;
dark.g += (setupDark.g - dark.g) * alpha;
dark.b += (setupDark.b - dark.b) * alpha;
}
return;
}
let r = 0, g = 0, b = 0, a = 0, r2 = 0, g2 = 0, b2 = 0;
let i = Timeline.search(frames, time, 8 /*ENTRIES*/);
let curveType = this.curves[i >> 3];
switch (curveType) {
case 0 /*LINEAR*/:
let 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*/];
let 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);
dark.r = r2;
dark.g = g2;
dark.b = b2;
}
else {
if (blend == MixBlend.setup) {
light.setFromColor(slot.data.color);
let setupDark = slot.data.darkColor;
dark.r = setupDark.r;
dark.g = setupDark.g;
dark.b = setupDark.b;
}
light.add((r - light.r) * alpha, (g - light.g) * alpha, (b - light.b) * alpha, (a - light.a) * alpha);
dark.r += (r2 - dark.r) * alpha;
dark.g += (g2 - dark.g) * alpha;
dark.b += (b2 - dark.b) * alpha;
}
}
}
/** Changes a slot's {@link Slot#color} and {@link Slot#darkColor} for two color tinting. */
export class RGB2Timeline extends CurveTimeline {
slotIndex = 0;
constructor(frameCount, bezierCount, slotIndex) {
super(frameCount, bezierCount, [
Property.rgb + "|" + slotIndex,
Property.rgb2 + "|" + slotIndex
]);
this.slotIndex = 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;
}
apply(skeleton, lastTime, time, events, alpha, blend, direction) {
let slot = skeleton.slots[this.slotIndex];
if (!slot.bone.active)
return;
let frames = this.frames;
let light = slot.color, dark = slot.darkColor;
if (time < frames[0]) {
let setupLight = slot.data.color, setupDark = slot.data.darkColor;
switch (blend) {
case MixBlend.setup:
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;
case MixBlend.first:
light.r += (setupLight.r - light.r) * alpha;
light.g += (setupLight.g - light.g) * alpha;
light.b += (setupLight.b - light.b) * alpha;
dark.r += (setupDark.r - dark.r) * alpha;
dark.g += (setupDark.g - dark.g) * alpha;
dark.b += (setupDark.b - dark.b) * alpha;
}
return;
}
let r = 0, g = 0, b = 0, a = 0, r2 = 0, g2 = 0, b2 = 0;
let i = Timeline.search(frames, time, 7 /*ENTRIES*/);
let curveType = this.curves[i / 7 /*ENTRIES*/];
switch (curveType) {
case 0 /*LINEAR*/:
let 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*/];
let 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) {
light.r = r;
light.g = g;
light.b = b;
dark.r = r2;
dark.g = g2;
dark.b = b2;
}
else {
if (blend == MixBlend.setup) {
let setupLight = slot.data.color, setupDark = slot.data.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;
}
light.r += (r - light.r) * alpha;
light.g += (g - light.g) * alpha;
light.b += (b - light.b) * alpha;
dark.r += (r2 - dark.r) * alpha;
dark.g += (g2 - dark.g) * alpha;
dark.b += (b2 - dark.b) * alpha;
}
}
}
/** Changes a slot's {@link Slot#attachment}. */
export class AttachmentTimeline extends Timeline {
slotIndex = 0;
/** The attachment name for each key frame. May contain null values to clear the attachment. */
attachmentNames;
construc