UNPKG

@esotericsoftware/spine-core

Version:
1,436 lines (1,428 loc) 484 kB
// 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.