@esotericsoftware/spine-core
Version:
The official Spine Runtimes for the web.
1,436 lines (1,428 loc) • 484 kB
JavaScript
// spine-core/src/Utils.ts
var IntSet = class {
array = [];
add(value) {
const contains = this.contains(value);
this.array[value | 0] = value | 0;
return !contains;
}
contains(value) {
return this.array[value | 0] !== void 0;
}
remove(value) {
this.array[value | 0] = void 0;
}
clear() {
this.array.length = 0;
}
};
var StringSet = class {
entries = {};
size = 0;
add(value) {
const contains = this.entries[value];
this.entries[value] = true;
if (!contains) {
this.size++;
return true;
}
return false;
}
addAll(values) {
const oldSize = this.size;
for (let i = 0, n = values.length; i < n; i++)
this.add(values[i]);
return oldSize !== this.size;
}
contains(value) {
return this.entries[value];
}
clear() {
this.entries = {};
this.size = 0;
}
};
var Color = class _Color {
constructor(r = 0, g = 0, b = 0, a = 0) {
this.r = r;
this.g = g;
this.b = b;
this.a = a;
}
static WHITE = new _Color(1, 1, 1, 1);
static RED = new _Color(1, 0, 0, 1);
static GREEN = new _Color(0, 1, 0, 1);
static BLUE = new _Color(0, 0, 1, 1);
static MAGENTA = new _Color(1, 0, 1, 1);
set(r, g, b, a) {
this.r = r;
this.g = g;
this.b = b;
this.a = a;
return this.clamp();
}
setFromColor(c) {
this.r = c.r;
this.g = c.g;
this.b = c.b;
this.a = c.a;
return this;
}
setFromString(hex) {
hex = hex.charAt(0) === "#" ? hex.substr(1) : hex;
this.r = parseInt(hex.substr(0, 2), 16) / 255;
this.g = parseInt(hex.substr(2, 2), 16) / 255;
this.b = parseInt(hex.substr(4, 2), 16) / 255;
this.a = hex.length !== 8 ? 1 : parseInt(hex.substr(6, 2), 16) / 255;
return this;
}
add(r, g, b, a) {
this.r += r;
this.g += g;
this.b += b;
this.a += a;
return this.clamp();
}
clamp() {
if (this.r < 0) this.r = 0;
else if (this.r > 1) this.r = 1;
if (this.g < 0) this.g = 0;
else if (this.g > 1) this.g = 1;
if (this.b < 0) this.b = 0;
else if (this.b > 1) this.b = 1;
if (this.a < 0) this.a = 0;
else if (this.a > 1) this.a = 1;
return this;
}
static rgba8888ToColor(color, value) {
color.r = ((value & 4278190080) >>> 24) / 255;
color.g = ((value & 16711680) >>> 16) / 255;
color.b = ((value & 65280) >>> 8) / 255;
color.a = (value & 255) / 255;
}
static rgb888ToColor(color, value) {
color.r = ((value & 16711680) >>> 16) / 255;
color.g = ((value & 65280) >>> 8) / 255;
color.b = (value & 255) / 255;
}
toRgb888() {
const hex = (x) => `0${(x * 255).toString(16)}`.slice(-2);
return Number(`0x${hex(this.r)}${hex(this.g)}${hex(this.b)}`);
}
static fromString(hex, color = new _Color()) {
return color.setFromString(hex);
}
};
var MathUtils = class _MathUtils {
static epsilon = 1e-5;
static epsilon2 = _MathUtils.epsilon * _MathUtils.epsilon;
// biome-ignore lint/suspicious/noApproximativeNumericConstant: reference runtime
static PI = 3.1415927;
static PI2 = _MathUtils.PI * 2;
static invPI2 = 1 / _MathUtils.PI2;
static radiansToDegrees = 180 / _MathUtils.PI;
static radDeg = _MathUtils.radiansToDegrees;
static degreesToRadians = _MathUtils.PI / 180;
static degRad = _MathUtils.degreesToRadians;
static clamp(value, min, max) {
if (value < min) return min;
if (value > max) return max;
return value;
}
static cosDeg(degrees) {
return Math.cos(degrees * _MathUtils.degRad);
}
static sinDeg(degrees) {
return Math.sin(degrees * _MathUtils.degRad);
}
static atan2Deg(y, x) {
return Math.atan2(y, x) * _MathUtils.radDeg;
}
static signum(value) {
return value > 0 ? 1 : value < 0 ? -1 : 0;
}
static toInt(x) {
return x > 0 ? Math.floor(x) : Math.ceil(x);
}
static cbrt(x) {
const y = Math.pow(Math.abs(x), 1 / 3);
return x < 0 ? -y : y;
}
static randomTriangular(min, max) {
return _MathUtils.randomTriangularWith(min, max, (min + max) * 0.5);
}
static randomTriangularWith(min, max, mode) {
const u = Math.random();
const d = max - min;
if (u <= (mode - min) / d) return min + Math.sqrt(u * d * (mode - min));
return max - Math.sqrt((1 - u) * d * (max - mode));
}
static isPowerOfTwo(value) {
return value && (value & value - 1) === 0;
}
};
var Interpolation = class _Interpolation {
static linear = new class extends _Interpolation {
applyInternal(a) {
return a;
}
}();
/** Aka "smoothstep". */
static smooth = new class extends _Interpolation {
applyInternal(a) {
return a * a * (3 - 2 * a);
}
}();
/** Slow, then fast. */
static slowFast = new class extends _Interpolation {
applyInternal(a) {
return a * a;
}
}();
/** Fast, then slow. */
static fastSlow = new class extends _Interpolation {
applyInternal(a) {
return (a - 1) * (a - 1) * -1 + 1;
}
}();
static circle = new class extends _Interpolation {
applyInternal(a) {
if (a <= 0.5) {
a *= 2;
return (1 - Math.sqrt(1 - a * a)) / 2;
}
a--;
a *= 2;
return (Math.sqrt(1 - a * a) + 1) / 2;
}
}();
apply(start, end, a) {
if (end === void 0 || a === void 0) return this.applyInternal(start);
return start + (end - start) * this.applyInternal(a);
}
};
var Pow = class extends Interpolation {
power = 2;
constructor(power) {
super();
this.power = power;
}
applyInternal(a) {
if (a <= 0.5) return Math.pow(a * 2, this.power) / 2;
return Math.pow((a - 1) * 2, this.power) / (this.power % 2 === 0 ? -2 : 2) + 1;
}
};
var PowOut = class extends Pow {
constructor(power) {
super(power);
}
applyInternal(a) {
return Math.pow(a - 1, this.power) * (this.power % 2 === 0 ? -1 : 1) + 1;
}
};
var Utils = class _Utils {
static SUPPORTS_TYPED_ARRAYS = typeof Float32Array !== "undefined";
static arrayCopy(source, sourceStart, dest, destStart, numElements) {
for (let i = sourceStart, j = destStart; i < sourceStart + numElements; i++, j++) {
dest[j] = source[i];
}
}
static arrayFill(array, fromIndex, toIndex, value) {
for (let i = fromIndex; i < toIndex; i++)
array[i] = value;
}
// biome-ignore lint/suspicious/noExplicitAny: ok any in this case
static setArraySize(array, size, value = 0) {
const oldSize = array.length;
if (oldSize === size) return array;
array.length = size;
if (oldSize < size) {
for (let i = oldSize; i < size; i++) array[i] = value;
}
return array;
}
// biome-ignore lint/suspicious/noExplicitAny: ok any in this case
static ensureArrayCapacity(array, size, value = 0) {
if (array.length >= size) return array;
return _Utils.setArraySize(array, size, value);
}
static newArray(size, defaultValue) {
const array = [];
for (let i = 0; i < size; i++) array[i] = defaultValue;
return array;
}
static newFloatArray(size) {
if (_Utils.SUPPORTS_TYPED_ARRAYS)
return new Float32Array(size);
else {
const array = [];
for (let i = 0; i < array.length; i++) array[i] = 0;
return array;
}
}
static newShortArray(size) {
if (_Utils.SUPPORTS_TYPED_ARRAYS)
return new Int16Array(size);
else {
const array = [];
for (let i = 0; i < array.length; i++) array[i] = 0;
return array;
}
}
static toFloatArray(array) {
return _Utils.SUPPORTS_TYPED_ARRAYS ? new Float32Array(array) : array;
}
static toSinglePrecision(value) {
return _Utils.SUPPORTS_TYPED_ARRAYS ? Math.fround(value) : value;
}
// This function is used to fix WebKit 602 specific issue described at https://esotericsoftware.com/forum/d/10109-ios-10-disappearing-graphics
static webkit602BugfixHelper(alpha) {
}
static contains(array, element, identity = true) {
for (let i = 0; i < array.length; i++)
if (array[i] === element) return true;
return false;
}
// biome-ignore lint/suspicious/noExplicitAny: ok any in this case
static enumValue(type, name) {
return type[name[0].toUpperCase() + name.slice(1)];
}
};
var DebugUtils = class {
static logBones(skeleton) {
for (let i = 0; i < skeleton.bones.length; i++) {
const bone = skeleton.bones[i].appliedPose;
console.log(`${bone.bone.data.name}, ${bone.a}, ${bone.b}, ${bone.c}, ${bone.d}, ${bone.worldX}, ${bone.worldY}`);
}
}
};
var Pool = class {
items = [];
instantiator;
constructor(instantiator) {
this.instantiator = instantiator;
}
obtain() {
return this.items.length > 0 ? this.items.pop() : this.instantiator();
}
free(item) {
item.reset?.();
this.items.push(item);
}
freeAll(items) {
for (let i = 0; i < items.length; i++)
this.free(items[i]);
}
clear() {
this.items.length = 0;
}
};
var Vector2 = class {
constructor(x = 0, y = 0) {
this.x = x;
this.y = y;
}
set(x, y) {
this.x = x;
this.y = y;
return this;
}
length() {
const x = this.x;
const y = this.y;
return Math.sqrt(x * x + y * y);
}
normalize() {
const len = this.length();
if (len !== 0) {
this.x /= len;
this.y /= len;
}
return this;
}
};
var TimeKeeper = class {
maxDelta = 0.064;
framesPerSecond = 0;
delta = 0;
totalTime = 0;
lastTime = Date.now() / 1e3;
frameCount = 0;
frameTime = 0;
update() {
const now = Date.now() / 1e3;
this.delta = now - this.lastTime;
this.frameTime += this.delta;
this.totalTime += this.delta;
if (this.delta > this.maxDelta) this.delta = this.maxDelta;
this.lastTime = now;
this.frameCount++;
if (this.frameTime > 1) {
this.framesPerSecond = this.frameCount / this.frameTime;
this.frameTime = 0;
this.frameCount = 0;
}
}
};
var WindowedMean = class {
values;
addedValues = 0;
lastValue = 0;
mean = 0;
dirty = true;
constructor(windowSize = 32) {
this.values = new Array(windowSize);
}
hasEnoughData() {
return this.addedValues >= this.values.length;
}
addValue(value) {
if (this.addedValues < this.values.length) this.addedValues++;
this.values[this.lastValue++] = value;
if (this.lastValue > this.values.length - 1) this.lastValue = 0;
this.dirty = true;
}
getMean() {
if (this.hasEnoughData()) {
if (this.dirty) {
let mean = 0;
for (let i = 0; i < this.values.length; i++)
mean += this.values[i];
this.mean = mean / this.values.length;
this.dirty = false;
}
return this.mean;
}
return 0;
}
};
// spine-core/src/Texture.ts
var Texture = class {
_image;
constructor(image) {
this._image = image;
}
getImage() {
return this._image;
}
};
var TextureFilter = /* @__PURE__ */ ((TextureFilter2) => {
TextureFilter2[TextureFilter2["Nearest"] = 9728] = "Nearest";
TextureFilter2[TextureFilter2["Linear"] = 9729] = "Linear";
TextureFilter2[TextureFilter2["MipMap"] = 9987] = "MipMap";
TextureFilter2[TextureFilter2["MipMapNearestNearest"] = 9984] = "MipMapNearestNearest";
TextureFilter2[TextureFilter2["MipMapLinearNearest"] = 9985] = "MipMapLinearNearest";
TextureFilter2[TextureFilter2["MipMapNearestLinear"] = 9986] = "MipMapNearestLinear";
TextureFilter2[TextureFilter2["MipMapLinearLinear"] = 9987] = "MipMapLinearLinear";
return TextureFilter2;
})(TextureFilter || {});
var TextureWrap = /* @__PURE__ */ ((TextureWrap2) => {
TextureWrap2[TextureWrap2["MirroredRepeat"] = 33648] = "MirroredRepeat";
TextureWrap2[TextureWrap2["ClampToEdge"] = 33071] = "ClampToEdge";
TextureWrap2[TextureWrap2["Repeat"] = 10497] = "Repeat";
return TextureWrap2;
})(TextureWrap || {});
var TextureRegion = class {
texture;
u = 0;
v = 0;
u2 = 0;
v2 = 0;
width = 0;
height = 0;
degrees = 0;
offsetX = 0;
offsetY = 0;
originalWidth = 0;
originalHeight = 0;
};
var FakeTexture = class extends Texture {
setFilters(minFilter, magFilter) {
}
setWraps(uWrap, vWrap) {
}
dispose() {
}
};
// spine-core/src/TextureAtlas.ts
var TextureAtlas = class {
pages = [];
regions = [];
constructor(atlasText) {
const reader = new TextureAtlasReader(atlasText);
const entry = new Array(4);
const pageFields = {};
pageFields.size = (page2) => {
page2.width = parseInt(entry[1]);
page2.height = parseInt(entry[2]);
};
pageFields.format = () => {
};
pageFields.filter = (page2) => {
page2.minFilter = Utils.enumValue(TextureFilter, entry[1]);
page2.magFilter = Utils.enumValue(TextureFilter, entry[2]);
};
pageFields.repeat = (page2) => {
if (entry[1].indexOf("x") !== -1) page2.uWrap = 10497 /* Repeat */;
if (entry[1].indexOf("y") !== -1) page2.vWrap = 10497 /* Repeat */;
};
pageFields.pma = (page2) => {
page2.pma = entry[1] === "true";
};
var regionFields = {};
regionFields.xy = (region) => {
region.x = parseInt(entry[1]);
region.y = parseInt(entry[2]);
};
regionFields.size = (region) => {
region.width = parseInt(entry[1]);
region.height = parseInt(entry[2]);
};
regionFields.bounds = (region) => {
region.x = parseInt(entry[1]);
region.y = parseInt(entry[2]);
region.width = parseInt(entry[3]);
region.height = parseInt(entry[4]);
};
regionFields.offset = (region) => {
region.offsetX = parseInt(entry[1]);
region.offsetY = parseInt(entry[2]);
};
regionFields.orig = (region) => {
region.originalWidth = parseInt(entry[1]);
region.originalHeight = parseInt(entry[2]);
};
regionFields.offsets = (region) => {
region.offsetX = parseInt(entry[1]);
region.offsetY = parseInt(entry[2]);
region.originalWidth = parseInt(entry[3]);
region.originalHeight = parseInt(entry[4]);
};
regionFields.rotate = (region) => {
const value = entry[1];
if (value === "true")
region.degrees = 90;
else if (value !== "false")
region.degrees = parseInt(value);
};
regionFields.index = (region) => {
region.index = parseInt(entry[1]);
};
let line = reader.readLine();
while (line && line.trim().length === 0)
line = reader.readLine();
while (true) {
if (!line || line.trim().length === 0) break;
if (reader.readEntry(entry, line) === 0) break;
line = reader.readLine();
}
let page = null;
let names = null;
let values = null;
while (true) {
if (line === null) break;
if (line.trim().length === 0) {
page = null;
line = reader.readLine();
} else if (!page) {
page = new TextureAtlasPage(line.trim());
while (true) {
if (reader.readEntry(entry, line = reader.readLine()) === 0) break;
const field = pageFields[entry[0]];
if (field) field(page);
}
this.pages.push(page);
} else {
const region = new TextureAtlasRegion(page, line);
while (true) {
const count = reader.readEntry(entry, line = reader.readLine());
if (count === 0) break;
const field = regionFields[entry[0]];
if (field)
field(region);
else {
if (!names) names = [];
if (!values) values = [];
names.push(entry[0]);
const entryValues = [];
for (let i = 0; i < count; i++)
entryValues.push(parseInt(entry[i + 1]));
values.push(entryValues);
}
}
if (region.originalWidth === 0 && region.originalHeight === 0) {
region.originalWidth = region.width;
region.originalHeight = region.height;
}
if (names && names.length > 0 && values && values.length > 0) {
region.names = names;
region.values = values;
names = null;
values = null;
}
region.u = region.x / page.width;
region.v = region.y / page.height;
if (region.degrees === 90) {
region.u2 = (region.x + region.height) / page.width;
region.v2 = (region.y + region.width) / page.height;
} else {
region.u2 = (region.x + region.width) / page.width;
region.v2 = (region.y + region.height) / page.height;
}
this.regions.push(region);
}
}
}
findRegion(name) {
for (let i = 0; i < this.regions.length; i++) {
if (this.regions[i].name === name) {
return this.regions[i];
}
}
return null;
}
setTextures(assetManager, pathPrefix = "") {
for (const page of this.pages)
page.setTexture(assetManager.get(pathPrefix + page.name));
}
dispose() {
for (let i = 0; i < this.pages.length; i++) {
this.pages[i].texture?.dispose();
}
}
};
var TextureAtlasReader = class {
lines;
index = 0;
constructor(text) {
this.lines = text.split(/\r\n|\r|\n/);
}
readLine() {
if (this.index >= this.lines.length)
return null;
return this.lines[this.index++];
}
readEntry(entry, line) {
if (!line) return 0;
line = line.trim();
if (line.length === 0) return 0;
const colon = line.indexOf(":");
if (colon === -1) return 0;
entry[0] = line.substr(0, colon).trim();
for (let i = 1, lastMatch = colon + 1; ; i++) {
const comma = line.indexOf(",", lastMatch);
if (comma === -1) {
entry[i] = line.substr(lastMatch).trim();
return i;
}
entry[i] = line.substr(lastMatch, comma - lastMatch).trim();
lastMatch = comma + 1;
if (i === 4) return 4;
}
}
};
var TextureAtlasPage = class {
name;
minFilter = 9728 /* Nearest */;
magFilter = 9728 /* Nearest */;
uWrap = 33071 /* ClampToEdge */;
vWrap = 33071 /* ClampToEdge */;
texture = null;
width = 0;
height = 0;
pma = false;
regions = [];
constructor(name) {
this.name = name;
}
setTexture(texture) {
this.texture = texture;
texture.setFilters(this.minFilter, this.magFilter);
texture.setWraps(this.uWrap, this.vWrap);
for (const region of this.regions)
region.texture = texture;
}
};
var TextureAtlasRegion = class extends TextureRegion {
page;
name;
x = 0;
y = 0;
offsetX = 0;
offsetY = 0;
originalWidth = 0;
originalHeight = 0;
index = 0;
degrees = 0;
names = null;
values = null;
constructor(page, name) {
super();
this.page = page;
this.name = name;
page.regions.push(this);
}
};
// spine-core/src/attachments/Attachment.ts
var Attachment = class _Attachment {
static empty = [];
name;
/** Timelines for the timeline attachment are also applied to this attachment.
* @return May be null if no attachment-specific timelines should be applied. */
timelineAttachment;
/** Slots that can have attachments whose {@link timelineAttachment} is this attachment. */
timelineSlots = _Attachment.empty;
constructor(name) {
if (!name) throw new Error("name cannot be null.");
this.name = name;
this.timelineAttachment = this;
}
/** Returns true if the {@code slotIndex} or any {@link timelineSlots} have an attachment whose {@link timelineAttachment} is
* this attachment.
* @param slots The {@link Skeleton.slots}.
* @param slotIndex The timeline's primary slot index. */
isTimelineActive(slots, slotIndex, appliedPose) {
let slot = slots[slotIndex];
if (slot.bone.isActive()) {
const other = (appliedPose ? slot.getAppliedPose() : slot.getPose()).getAttachment();
if (other != null && other.timelineAttachment === this) return true;
}
for (let i = 0, n = this.timelineSlots.length; i < n; i++) {
slot = slots[this.timelineSlots[i]];
if (!slot.bone.isActive()) continue;
const other = (appliedPose ? slot.getAppliedPose() : slot.getPose()).getAttachment();
if (other != null && other.timelineAttachment === this) return true;
}
return false;
}
};
var VertexAttachment = class _VertexAttachment extends Attachment {
static nextID = 0;
/** The unique ID for this attachment. */
id = _VertexAttachment.nextID++;
/** The bones that affect the {@link vertices}. The entries are, for each vertex, the number of bones affecting the vertex
* followed by that many bone indices, which is {@link Skeleton.getBones} index. Null if this attachment has no weights. */
bones = null;
/** The vertex positions in the bone's coordinate system. For a non-weighted attachment, the values are `x,y`
* entries for each vertex. For a weighted attachment, the values are `x,y,weight` triplets for each bone affecting
* each vertex. */
vertices = [];
/** The maximum number of world vertex values that can be output by
* {@link computeWorldVertices} using the `count` parameter. */
worldVerticesLength = 0;
constructor(name) {
super(name);
}
/** Transforms the attachment's local {@link vertices} to world coordinates. If {@link SlotPose.getDeform} is not empty, it
* is used to deform the vertices.
*
* See <a href="https://esotericsoftware.com/spine-runtime-skeletons#World-transforms">World transforms</a> in the Spine
* Runtimes Guide.
* @param start The index of the first {@link vertices} value to transform. Each vertex has 2 values, x and y.
* @param count The number of world vertex values to output. Must be <= {@link worldVerticesLength} - `start`.
* @param worldVertices The output world vertices. Must have a length >= `offset` + `count` *
* `stride` / 2.
* @param offset The `worldVertices` index to begin writing values.
* @param stride The number of `worldVertices` entries between the value pairs written. */
computeWorldVertices(skeleton, slot, start, count, worldVertices, offset, stride) {
count = offset + (count >> 1) * stride;
const deformArray = slot.appliedPose.deform;
let vertices = this.vertices;
const bones = this.bones;
if (!bones) {
if (deformArray.length > 0) vertices = deformArray;
const bone = slot.bone.appliedPose;
const x = bone.worldX;
const y = bone.worldY;
const a = bone.a, b = bone.b, c = bone.c, d = bone.d;
for (let v2 = start, w = offset; w < count; v2 += 2, w += stride) {
const vx = vertices[v2], vy = vertices[v2 + 1];
worldVertices[w] = vx * a + vy * b + x;
worldVertices[w + 1] = vx * c + vy * d + y;
}
return;
}
let v = 0, skip = 0;
for (let i = 0; i < start; i += 2) {
const n = bones[v];
v += n + 1;
skip += n;
}
const skeletonBones = skeleton.bones;
if (deformArray.length === 0) {
for (let w = offset, b = skip * 3; w < count; w += stride) {
let wx = 0, wy = 0;
let n = bones[v++];
n += v;
for (; v < n; v++, b += 3) {
const bone = skeletonBones[bones[v]].appliedPose;
const vx = vertices[b], vy = vertices[b + 1], weight = vertices[b + 2];
wx += (vx * bone.a + vy * bone.b + bone.worldX) * weight;
wy += (vx * bone.c + vy * bone.d + bone.worldY) * weight;
}
worldVertices[w] = wx;
worldVertices[w + 1] = wy;
}
} else {
const deform = deformArray;
for (let w = offset, b = skip * 3, f = skip << 1; w < count; w += stride) {
let wx = 0, wy = 0;
let n = bones[v++];
n += v;
for (; v < n; v++, b += 3, f += 2) {
const bone = skeletonBones[bones[v]].appliedPose;
const vx = vertices[b] + deform[f], vy = vertices[b + 1] + deform[f + 1], weight = vertices[b + 2];
wx += (vx * bone.a + vy * bone.b + bone.worldX) * weight;
wy += (vx * bone.c + vy * bone.d + bone.worldY) * weight;
}
worldVertices[w] = wx;
worldVertices[w + 1] = wy;
}
}
}
/** Does not copy id (generated) or name (set on construction). **/
copyTo(attachment) {
if (this.bones) {
attachment.bones = [];
Utils.arrayCopy(this.bones, 0, attachment.bones, 0, this.bones.length);
} else
attachment.bones = null;
if (this.vertices) {
attachment.vertices = Utils.newFloatArray(this.vertices.length);
Utils.arrayCopy(this.vertices, 0, attachment.vertices, 0, this.vertices.length);
}
attachment.worldVerticesLength = this.worldVerticesLength;
attachment.timelineAttachment = this.timelineAttachment;
attachment.timelineSlots = this.timelineSlots;
}
};
// spine-core/src/attachments/MeshAttachment.ts
var MeshAttachment = class _MeshAttachment extends VertexAttachment {
sequence;
/** The UV pair for each vertex, normalized within the texture region. */
regionUVs = [];
/** Triplets of vertex indices which describe the mesh's triangulation. */
triangles = [];
/** The number of entries at the beginning of {@link vertices} that make up the mesh hull. */
hullLength = 0;
/** The name of the texture region for this attachment. */
path;
/** The color to tint the mesh. */
color = new Color(1, 1, 1, 1);
sourceMesh = null;
/** Vertex index pairs describing edges for controlling triangulation, or null if nonessential data was not exported. Mesh
* triangles do not never cross edges. Triangulation is not performed at runtime. */
edges = [];
/** The width of the mesh's image. Available only when nonessential data was exported. */
width = 0;
/** The height of the mesh's image. Available only when nonessential data was exported. */
height = 0;
tempColor = new Color(0, 0, 0, 0);
constructor(name, sequence) {
super(name);
this.sequence = sequence;
}
copy() {
if (this.sourceMesh) return this.newLinkedMesh();
const copy = new _MeshAttachment(this.name, this.sequence.copy());
copy.path = this.path;
copy.color.setFromColor(this.color);
this.copyTo(copy);
copy.regionUVs = [];
Utils.arrayCopy(this.regionUVs, 0, copy.regionUVs, 0, this.regionUVs.length);
copy.triangles = [];
Utils.arrayCopy(this.triangles, 0, copy.triangles, 0, this.triangles.length);
copy.hullLength = this.hullLength;
if (this.edges) {
copy.edges = [];
Utils.arrayCopy(this.edges, 0, copy.edges, 0, this.edges.length);
}
copy.width = this.width;
copy.height = this.height;
return copy;
}
updateSequence() {
this.sequence.update(this);
}
/** The source mesh if this is a linked mesh, else null. A linked mesh shares the {@link bones}, {@link vertices},
* {@link regionUVs}, {@link triangles}, {@link hullLength}, {@link edges}, {@link width}, and {@link height} with the
* source mesh, but may have a different {@link name} or {@link path}, and therefore a different texture region. */
getSourceMesh() {
return this.sourceMesh;
}
setSourceMesh(sourceMesh) {
this.sourceMesh = sourceMesh;
if (sourceMesh) {
this.bones = sourceMesh.bones;
this.vertices = sourceMesh.vertices;
this.worldVerticesLength = sourceMesh.worldVerticesLength;
this.regionUVs = sourceMesh.regionUVs;
this.triangles = sourceMesh.triangles;
this.hullLength = sourceMesh.hullLength;
this.worldVerticesLength = sourceMesh.worldVerticesLength;
this.edges = sourceMesh.edges;
this.width = sourceMesh.width;
this.height = sourceMesh.height;
}
}
/** Returns a new mesh with the {@link sourceMesh} set to this mesh's source mesh, if any, else to this mesh. **/
newLinkedMesh() {
const copy = new _MeshAttachment(this.name, this.sequence.copy());
copy.timelineAttachment = this.timelineAttachment;
copy.path = this.path;
copy.color.setFromColor(this.color);
copy.setSourceMesh(this.sourceMesh ? this.sourceMesh : this);
copy.updateSequence();
return copy;
}
/** Computes {@link Sequence.getUVs | UVs} for a mesh attachment.
* @param uvs Output array for the computed UVs, same length as regionUVs. */
static computeUVs(region, regionUVs, uvs) {
if (!region) throw new Error("Region not set.");
const n = uvs.length;
let u = region.u, v = region.v, width = 0, height = 0;
if (region instanceof TextureAtlasRegion) {
const page = region.page;
const textureWidth = page.width, textureHeight = page.height;
switch (region.degrees) {
case 90:
u -= (region.originalHeight - region.offsetY - region.height) / textureWidth;
v -= (region.originalWidth - region.offsetX - region.width) / textureHeight;
width = region.originalHeight / textureWidth;
height = region.originalWidth / textureHeight;
for (let i = 0; i < n; i += 2) {
uvs[i] = u + regionUVs[i + 1] * width;
uvs[i + 1] = v + (1 - regionUVs[i]) * height;
}
return;
case 180:
u -= (region.originalWidth - region.offsetX - region.width) / textureWidth;
v -= region.offsetY / textureHeight;
width = region.originalWidth / textureWidth;
height = region.originalHeight / textureHeight;
for (let i = 0; i < n; i += 2) {
uvs[i] = u + (1 - regionUVs[i]) * width;
uvs[i + 1] = v + (1 - regionUVs[i + 1]) * height;
}
return;
case 270:
u -= region.offsetY / textureWidth;
v -= region.offsetX / textureHeight;
width = region.originalHeight / textureWidth;
height = region.originalWidth / textureHeight;
for (let i = 0; i < n; i += 2) {
uvs[i] = u + (1 - regionUVs[i + 1]) * width;
uvs[i + 1] = v + regionUVs[i] * height;
}
return;
default:
u -= region.offsetX / textureWidth;
v -= (region.originalHeight - region.offsetY - region.height) / textureHeight;
width = region.originalWidth / textureWidth;
height = region.originalHeight / textureHeight;
}
} else if (!region) {
u = v = 0;
width = height = 1;
} else {
width = region.u2 - u;
height = region.v2 - v;
}
for (let i = 0; i < n; i += 2) {
uvs[i] = u + regionUVs[i] * width;
uvs[i + 1] = v + regionUVs[i + 1] * height;
}
}
};
// spine-core/src/attachments/RegionAttachment.ts
var RegionAttachment = class _RegionAttachment extends Attachment {
sequence;
/** The local x translation. */
x = 0;
/** The local y translation. */
y = 0;
/** The local scaleX. */
scaleX = 1;
/** The local scaleY. */
scaleY = 1;
/** The local rotation in degrees, counter clockwise. */
rotation = 0;
/** The width of the region attachment in Spine. */
width = 0;
/** The height of the region attachment in Spine. */
height = 0;
/** The name of the texture region for this attachment. */
path;
/** The color to tint the region attachment. */
color = new Color(1, 1, 1, 1);
tempColor = new Color(1, 1, 1, 1);
constructor(name, sequence) {
super(name);
this.sequence = sequence;
}
copy() {
const copy = new _RegionAttachment(this.name, this.sequence.copy());
copy.path = this.path;
copy.x = this.x;
copy.y = this.y;
copy.scaleX = this.scaleX;
copy.scaleY = this.scaleY;
copy.rotation = this.rotation;
copy.width = this.width;
copy.height = this.height;
copy.color.setFromColor(this.color);
return copy;
}
/** Transforms the attachment's four vertices to world coordinates.
*
* See <a href="http://esotericsoftware.com/spine-runtime-skeletons#World-transforms">World transforms</a> in the Spine
* Runtimes Guide.
* @param worldVertices The output world vertices. Must have a length >= `offset` + 8.
* @param offset The `worldVertices` index to begin writing values.
* @param stride The number of `worldVertices` entries between the value pairs written. */
computeWorldVertices(slot, vertexOffsets, worldVertices, offset, stride) {
const bone = slot.bone.appliedPose;
const x = bone.worldX, y = bone.worldY;
const a = bone.a, b = bone.b, c = bone.c, d = bone.d;
let offsetX = vertexOffsets[0];
let offsetY = vertexOffsets[1];
worldVertices[offset] = offsetX * a + offsetY * b + x;
worldVertices[offset + 1] = offsetX * c + offsetY * d + y;
offset += stride;
offsetX = vertexOffsets[2];
offsetY = vertexOffsets[3];
worldVertices[offset] = offsetX * a + offsetY * b + x;
worldVertices[offset + 1] = offsetX * c + offsetY * d + y;
offset += stride;
offsetX = vertexOffsets[4];
offsetY = vertexOffsets[5];
worldVertices[offset] = offsetX * a + offsetY * b + x;
worldVertices[offset + 1] = offsetX * c + offsetY * d + y;
offset += stride;
offsetX = vertexOffsets[6];
offsetY = vertexOffsets[7];
worldVertices[offset] = offsetX * a + offsetY * b + x;
worldVertices[offset + 1] = offsetX * c + offsetY * d + y;
}
getOffsets(pose) {
return this.sequence.offsets[this.sequence.resolveIndex(pose)];
}
updateSequence() {
this.sequence.update(this);
}
/** Computes {@link Sequence.getUVs | UVs} and {@link Sequence.getOffsets | offsets} for a region attachment.
* @param uvs Output array for the computed UVs, length of 8.
* @param offset Output array for the computed vertex offsets, length of 8. */
static computeUVs(region, x, y, scaleX, scaleY, rotation, width, height, offset, uvs) {
if (!region) throw new Error("Region not set.");
const regionScaleX = width / region.originalWidth * scaleX;
const regionScaleY = height / region.originalHeight * scaleY;
const localX = -width / 2 * scaleX + region.offsetX * regionScaleX;
const localY = -height / 2 * scaleY + region.offsetY * regionScaleY;
const localX2 = localX + region.width * regionScaleX;
const localY2 = localY + region.height * regionScaleY;
const radians = rotation * MathUtils.degRad;
const cos = Math.cos(radians);
const sin = Math.sin(radians);
const localXCos = localX * cos + x;
const localXSin = localX * sin;
const localYCos = localY * cos + y;
const localYSin = localY * sin;
const localX2Cos = localX2 * cos + x;
const localX2Sin = localX2 * sin;
const localY2Cos = localY2 * cos + y;
const localY2Sin = localY2 * sin;
offset[0] = localXCos - localYSin;
offset[1] = localYCos + localXSin;
offset[2] = localXCos - localY2Sin;
offset[3] = localY2Cos + localXSin;
offset[4] = localX2Cos - localY2Sin;
offset[5] = localY2Cos + localX2Sin;
offset[6] = localX2Cos - localYSin;
offset[7] = localYCos + localX2Sin;
if (region == null) {
uvs[0] = 0;
uvs[1] = 0;
uvs[2] = 0;
uvs[3] = 1;
uvs[4] = 1;
uvs[5] = 1;
uvs[6] = 1;
uvs[7] = 0;
} else {
uvs[1] = region.v2;
uvs[2] = region.u;
uvs[5] = region.v;
uvs[6] = region.u2;
if (region.degrees === 90) {
uvs[0] = region.u2;
uvs[3] = region.v2;
uvs[4] = region.u;
uvs[7] = region.v;
} else {
uvs[0] = region.u;
uvs[3] = region.v;
uvs[4] = region.u2;
uvs[7] = region.v2;
}
}
}
static X1 = 0;
static Y1 = 1;
static C1R = 2;
static C1G = 3;
static C1B = 4;
static C1A = 5;
static U1 = 6;
static V1 = 7;
static X2 = 8;
static Y2 = 9;
static C2R = 10;
static C2G = 11;
static C2B = 12;
static C2A = 13;
static U2 = 14;
static V2 = 15;
static X3 = 16;
static Y3 = 17;
static C3R = 18;
static C3G = 19;
static C3B = 20;
static C3A = 21;
static U3 = 22;
static V3 = 23;
static X4 = 24;
static Y4 = 25;
static C4R = 26;
static C4G = 27;
static C4B = 28;
static C4A = 29;
static U4 = 30;
static V4 = 31;
};
// spine-core/src/attachments/Sequence.ts
var Sequence = class _Sequence {
static _nextID = 0;
id = _Sequence.nextID();
/** The list of texture regions this sequence will display. */
regions;
pathSuffix;
uvs;
/** Returns vertex offsets from the center of a {@link RegionAttachment}. Invalid to call for a {@link MeshAttachment}. */
offsets;
/** The starting number for the numeric {@link getPath | path} suffix. */
start = 0;
/** The minimum number of digits in the numeric {@link getPath | path} suffix, for zero padding. 0 for no zero
* padding. */
digits = 0;
/** The index of the region to show for the setup pose. */
setupIndex = 0;
/** @param count The number of texture regions this sequence will display.
* @param pathSuffix If true, the {@link getPath | path} has a numeric suffix. If false, all regions will use the
* same path, so `count` should be 1. */
constructor(count, pathSuffix) {
this.regions = new Array(count);
this.pathSuffix = pathSuffix;
}
copy() {
const regionCount = this.regions.length;
const copy = new _Sequence(regionCount, this.pathSuffix);
Utils.arrayCopy(this.regions, 0, copy.regions, 0, regionCount);
copy.start = this.start;
copy.digits = this.digits;
copy.setupIndex = this.setupIndex;
if (this.uvs != null) {
const length = this.uvs[0].length;
copy.uvs = [];
for (let i = 0; i < regionCount; i++) {
copy.uvs[i] = Utils.newFloatArray(length);
Utils.arrayCopy(this.uvs[i], 0, copy.uvs[i], 0, length);
}
}
if (this.offsets != null) {
copy.offsets = [];
for (let i = 0; i < regionCount; i++) {
copy.offsets[i] = [];
Utils.arrayCopy(this.offsets[i], 0, copy.offsets[i], 0, 8);
}
}
return copy;
}
/** Computes UVs and offsets for the specified attachment. Must be called if the regions or attachment properties are
* changed. */
update(attachment) {
const regionCount = this.regions.length;
if (attachment instanceof RegionAttachment) {
this.uvs = [];
this.offsets = [];
for (let i = 0; i < regionCount; i++) {
this.uvs[i] = Utils.newFloatArray(8);
this.offsets[i] = [];
RegionAttachment.computeUVs(
this.regions[i],
attachment.x,
attachment.y,
attachment.scaleX,
attachment.scaleY,
attachment.rotation,
attachment.width,
attachment.height,
this.offsets[i],
this.uvs[i]
);
}
} else if (attachment instanceof MeshAttachment) {
const regionUVs = attachment.regionUVs;
this.uvs = [];
this.offsets = void 0;
for (let i = 0; i < regionCount; i++) {
this.uvs[i] = Utils.newFloatArray(regionUVs.length);
MeshAttachment.computeUVs(this.regions[i], regionUVs, this.uvs[i]);
}
}
}
/** Returns the {@link regions} index for the {@link SlotPose.getSequenceIndex}. */
resolveIndex(pose) {
let index = pose.sequenceIndex;
if (index === -1) index = this.setupIndex;
if (index >= this.regions.length) index = this.regions.length - 1;
return index;
}
/** Returns the UVs for the specified index. {@link regions Regions} must be populated and {@link update} called
* before calling this method. */
getUVs(index) {
return this.uvs[index];
}
/** Returns true if the {@link getPath | path} has a numeric suffix. */
hasPathSuffix() {
return this.pathSuffix;
}
/** Returns the specified base path with an optional numeric suffix for the specified index. */
getPath(basePath, index) {
if (!this.pathSuffix) return basePath;
let result = basePath;
const frame = (this.start + index).toString();
for (let i = this.digits - frame.length; i > 0; i--)
result += "0";
result += frame;
return result;
}
static nextID() {
return _Sequence._nextID++;
}
};
var SequenceMode = /* @__PURE__ */ ((SequenceMode2) => {
SequenceMode2[SequenceMode2["hold"] = 0] = "hold";
SequenceMode2[SequenceMode2["once"] = 1] = "once";
SequenceMode2[SequenceMode2["loop"] = 2] = "loop";
SequenceMode2[SequenceMode2["pingpong"] = 3] = "pingpong";
SequenceMode2[SequenceMode2["onceReverse"] = 4] = "onceReverse";
SequenceMode2[SequenceMode2["loopReverse"] = 5] = "loopReverse";
SequenceMode2[SequenceMode2["pingpongReverse"] = 6] = "pingpongReverse";
return SequenceMode2;
})(SequenceMode || {});
var SequenceModeValues = [
0 /* hold */,
1 /* once */,
2 /* loop */,
3 /* pingpong */,
4 /* onceReverse */,
5 /* loopReverse */,
6 /* pingpongReverse */
];
// spine-core/src/Animation.ts
var Animation = class {
/** 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 = /* @__PURE__ */ 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);
}
};
var Property = /* @__PURE__ */ ((Property2) => {
Property2[Property2["rotate"] = 0] = "rotate";
Property2[Property2["x"] = 1] = "x";
Property2[Property2["y"] = 2] = "y";
Property2[Property2["scaleX"] = 3] = "scaleX";
Property2[Property2["scaleY"] = 4] = "scaleY";
Property2[Property2["shearX"] = 5] = "shearX";
Property2[Property2["shearY"] = 6] = "shearY";
Property2[Property2["inherit"] = 7] = "inherit";
Property2[Property2["rgb"] = 8] = "rgb";
Property2[Property2["alpha"] = 9] = "alpha";
Property2[Property2["rgb2"] = 10] = "rgb2";
Property2[Property2["attachment"] = 11] = "attachment";
Property2[Property2["deform"] = 12] = "deform";
Property2[Property2["event"] = 13] = "event";
Property2[Property2["drawOrder"] = 14] = "drawOrder";
Property2[Property2["ikConstraint"] = 15] = "ikConstraint";
Property2[Property2["transformConstraint"] = 16] = "transformConstraint";
Property2[Property2["pathConstraintPosition"] = 17] = "pathConstraintPosition";
Property2[Property2["pathConstraintSpacing"] = 18] = "pathConstraintSpacing";
Property2[Property2["pathConstraintMix"] = 19] = "pathConstraintMix";
Property2[Property2["physicsConstraintInertia"] = 20] = "physicsConstraintInertia";
Property2[Property2["physicsConstraintStrength"] = 21] = "physicsConstraintStrength";
Property2[Property2["physicsConstraintDamping"] = 22] = "physicsConstraintDamping";
Property2[Property2["physicsConstraintMass"] = 23] = "physicsConstraintMass";
Property2[Property2["physicsConstraintWind"] = 24] = "physicsConstraintWind";
Property2[Property2["physicsConstraintGravity"] = 25] = "physicsConstraintGravity";
Property2[Property2["physicsConstraintMix"] = 26] = "physicsConstraintMix";
Property2[Property2["physicsConstraintReset"] = 27] = "physicsConstraintReset";
Property2[Property2["sequence"] = 28] = "sequence";
Property2[Property2["sliderTime"] = 29] = "sliderTime";
Property2[Property2["sliderMix"] = 30] = "sliderMix";
return Property2;
})(Property || {});
var Timeline = class {
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;
}
};
function isSlotTimeline(obj) {
return typeof obj === "object" && obj !== null && typeof obj.slotIndex === "number";
}
var CurveTimeline = class 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;
}
/** Sets the specified key frame to linear interpolation. */
setLinear(frame) {
this.curves[frame] = 0;
}
/** Sets the specified key frame to stepped interpolation. */
setStepped(frame) {
this.curves[frame] = 1;
}
/** 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;
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.