完全离线运行
+官方运行时全部拷贝在插件本地 lib/ 目录,无 CDN、无网络请求。断网、内网环境同样可用。
+ * entry.mixDuration = 0.25;+ * + * Alternatively, use {@link setMixDuration} to set both the mix duration and recompute the delay:
+ * entry.delay = entry.previous.getTrackComplete() - entry.mixDuration + 0; + *
+ entry.setMixDuration(0.25f, 0); // mixDuration, delay + *+ */ + mixDuration = 0; + totalAlpha = 0; + mixInterpolation = Interpolation.linear; + /** Sets both {@link getMixDuration} and {@link getDelay}. + * @param delay If > 0, sets {@link getDelay}. If <= 0, the delay set is the duration of the previous track entry minus + * the specified mix duration plus the specified `delay` (ie the mix ends at (when `delay` = + * 0) or before (when `delay` < 0) the previous track entry duration). If the previous entry is + * looping, its next loop completion is used instead of its duration. */ + setMixDuration(mixDuration, delay) { + this.mixDuration = mixDuration; + if (delay !== void 0) { + if (delay <= 0) delay = this.previous == null ? 0 : Math.max(delay + this.previous.getTrackComplete() - mixDuration, 0); + this.delay = delay; + } + } + /** The interpolation to apply to the mix percentage ({@link mixTime} / {@link mixDuration}) when mixing from the previous + * animation to this animation. Defaults to linear. */ + setMixInterpolation(mixInterpolation) { + if (!mixInterpolation) throw new Error("mixInterpolation cannot be null."); + this.mixInterpolation = mixInterpolation; + } + mix() { + if (this.mixDuration === 0) return 1; + let mix = this.mixTime / this.mixDuration; + if (mix >= 1) return 1; + if (this.mixInterpolation === Interpolation.linear) return mix; + mix = this.mixInterpolation.apply(mix); + if (mix < 0) return 0; + if (mix > 1) return 1; + return mix; + } + /** For each timeline: + * - Bits 0-1: MixFrom. + * - Bit 2, HOLD: 0 = mix out using alphaMix, 1 = apply full alpha to prevent dipping. Timeline is first on its track to + * set the property and the next entry (mixingTo) also sets it. When held, timelineHoldMix's mix controls how the hold fades + * out (for 3+ entry chains where the chain eventually stops setting the property). */ + timelineMode = []; + timelineHoldMix = []; + timelinesRotation = []; + reset() { + this.next = null; + this.previous = null; + this.mixingFrom = null; + this.mixingTo = null; + this.mixInterpolation = Interpolation.linear; + this.animation = null; + this.listener = null; + this.timelineMode.length = 0; + this.timelineHoldMix.length = 0; + this.timelinesRotation.length = 0; + } + /** Uses {@link trackTime} to compute the `animationTime`, which is always between {@link animationStart} and + * {@link animationEnd}. When `trackTime` is 0, `animationTime` is equal to the + * `animationStart` time. */ + getAnimationTime() { + if (!this.loop) return Math.min(this.trackTime + this.animationStart, this.animationEnd); + const duration = this.animationEnd - this.animationStart; + if (duration === 0) return this.animationStart; + return this.trackTime % duration + this.animationStart; + } + setAnimationLast(animationLast) { + this.animationLast = animationLast; + this.nextAnimationLast = animationLast; + } + /** Returns true if at least one loop has been completed. + * + * See {@link AnimationStateListener.complete}. */ + isComplete() { + return this.trackTime >= this.animationEnd - this.animationStart; + } + /** When {@link shortestRotation} is false, this clears the directions for mixing this entry's rotation. This can be useful + * to avoid bones rotating the long way around when using {@link getAlpha} and starting animations on other tracks. + * + * Mixing involves finding a rotation between two others. There are two possible solutions: the short or the long way + * around. When the two rotations change over time, which direction is the short or long way can also change. If the short + * way was always chosen, bones flip to the other side when that direction became the long way. TrackEntry chooses the short + * way the first time it is applied and remembers that direction. Resetting that direction makes it choose a new short way + * on the next apply. */ + resetRotationDirections() { + this.timelinesRotation.length = 0; + } + /** If this track entry is non-looping, this is the track time in seconds when {@link animationEnd} is reached, or the + * current {@link trackTime} if it has already been reached. + * + * If this track entry is looping, this is the track time when this animation will reach its next {@link animationEnd} (the + * next loop completion). */ + getTrackComplete() { + const duration = this.animationEnd - this.animationStart; + if (duration !== 0) { + if (this.loop) return duration * (1 + (this.trackTime / duration | 0)); + if (this.trackTime < duration) return duration; + } + return this.trackTime; + } + /** Returns true if this track entry has been applied at least once. + * + * See {@link AnimationState.apply}. */ + wasApplied() { + return this.nextTrackLast !== -1; + } + /** Returns true if there is a {@link next} track entry and it will become the current track entry during the next + * {@link AnimationState.update}. */ + isNextReady() { + return this.next != null && this.nextTrackLast - this.next.delay >= 0; + } + }; + var EventQueue = class { + objects = []; + drainDisabled = false; + animState; + constructor(animState) { + this.animState = animState; + } + start(entry) { + this.objects.push(0 /* start */); + this.objects.push(entry); + this.animState.animationsChanged = true; + } + interrupt(entry) { + this.objects.push(1 /* interrupt */); + this.objects.push(entry); + } + end(entry) { + this.objects.push(2 /* end */); + this.objects.push(entry); + this.animState.animationsChanged = true; + } + dispose(entry) { + this.objects.push(3 /* dispose */); + this.objects.push(entry); + } + complete(entry) { + this.objects.push(4 /* complete */); + this.objects.push(entry); + } + event(entry, event) { + this.objects.push(5 /* event */); + this.objects.push(entry); + this.objects.push(event); + } + drain() { + if (this.drainDisabled) return; + this.drainDisabled = true; + for (let i = 0; i < this.objects.length; i += 2) { + const objects = this.objects; + const type = objects[i]; + const entry = objects[i + 1]; + const listeners = this.animState.listeners.slice(); + switch (type) { + case 0 /* start */: + if (entry.listener?.start) entry.listener.start(entry); + for (let ii = 0; ii < listeners.length; ii++) { + const listener = listeners[ii]; + if (listener.start) listener.start(entry); + } + break; + case 1 /* interrupt */: + if (entry.listener?.interrupt) entry.listener.interrupt(entry); + for (let ii = 0; ii < listeners.length; ii++) { + const listener = listeners[ii]; + if (listener.interrupt) listener.interrupt(entry); + } + break; + // biome-ignore lint/suspicious/noFallthroughSwitchClause: reference runtime does fall through + case 2 /* end */: + if (entry.listener?.end) entry.listener.end(entry); + for (let ii = 0; ii < listeners.length; ii++) { + const listener = listeners[ii]; + if (listener.end) listener.end(entry); + } + // Fall through. + case 3 /* dispose */: + if (entry.listener?.dispose) entry.listener.dispose(entry); + for (let ii = 0; ii < listeners.length; ii++) { + const listener = listeners[ii]; + if (listener.dispose) listener.dispose(entry); + } + this.animState.trackEntryPool.free(entry); + break; + case 4 /* complete */: + if (entry.listener?.complete) entry.listener.complete(entry); + for (let ii = 0; ii < listeners.length; ii++) { + const listener = listeners[ii]; + if (listener.complete) listener.complete(entry); + } + break; + case 5 /* event */: { + const event = objects[i++ + 2]; + if (entry.listener?.event) entry.listener.event(entry, event); + for (let ii = 0; ii < listeners.length; ii++) { + const listener = listeners[ii]; + if (listener.event) listener.event(entry, event); + } + break; + } + } + } + this.clear(); + this.drainDisabled = false; + } + clear() { + this.objects.length = 0; + } + }; + var EventType = /* @__PURE__ */ ((EventType2) => { + EventType2[EventType2["start"] = 0] = "start"; + EventType2[EventType2["interrupt"] = 1] = "interrupt"; + EventType2[EventType2["end"] = 2] = "end"; + EventType2[EventType2["dispose"] = 3] = "dispose"; + EventType2[EventType2["complete"] = 4] = "complete"; + EventType2[EventType2["event"] = 5] = "event"; + return EventType2; + })(EventType || {}); + var AnimationStateAdapter = class { + start(entry) { + } + interrupt(entry) { + } + end(entry) { + } + dispose(entry) { + } + complete(entry) { + } + event(entry, event) { + } + }; + var CURRENT = 0; + var SETUP = 1; + var FIRST = 2; + var MODE = 3; + var HOLD = 4; + var ATTACH_SETUP = 1; + var ATTACH_RETAIN = 2; + + // spine-core/src/AnimationStateData.ts + var AnimationStateData = class { + /** The SkeletonData to look up animations when they are specified by name. */ + skeletonData; + animationToMixTime = {}; + /** The mix duration to use when no mix duration has been defined between two animations. */ + defaultMix = 0; + constructor(skeletonData) { + if (!skeletonData) throw new Error("skeletonData cannot be null."); + this.skeletonData = skeletonData; + } + setMix(from, to, duration) { + if (typeof from === "string") + return this.setMix1(from, to, duration); + return this.setMix2(from, to, duration); + } + setMix1(fromName, toName, duration) { + const from = this.skeletonData.findAnimation(fromName); + if (!from) throw new Error(`Animation not found: ${fromName}`); + const to = this.skeletonData.findAnimation(toName); + if (!to) throw new Error(`Animation not found: ${toName}`); + this.setMix2(from, to, duration); + } + setMix2(from, to, duration) { + if (!from) throw new Error("from cannot be null."); + if (!to) throw new Error("to cannot be null."); + const key = `${from.name}.${to.name}`; + this.animationToMixTime[key] = duration; + } + /** Returns the mix duration to use when changing from the specified animation to the other on the same track, or the + * {@link defaultMix} if no mix duration has been set. */ + getMix(from, to) { + const key = `${from.name}.${to.name}`; + const value = this.animationToMixTime[key]; + return value === void 0 ? this.defaultMix : value; + } + }; + + // spine-core/src/AssetManagerBase.ts + var AssetManagerBase = class { + constructor(textureLoader, pathPrefix = "", downloader = new Downloader(), cache = new AssetCache()) { + this.textureLoader = textureLoader; + this.pathPrefix = pathPrefix; + this.downloader = downloader; + this.cache = cache; + } + errors = {}; + toLoad = 0; + loaded = 0; + texturePmaInfo = {}; + start(path) { + this.toLoad++; + return this.pathPrefix + path; + } + success(callback, path, asset) { + this.toLoad--; + this.loaded++; + this.cache.assets[path] = asset; + this.cache.assetsRefCount[path] = (this.cache.assetsRefCount[path] || 0) + 1; + if (callback) callback(path, asset); + } + error(callback, path, message) { + this.toLoad--; + this.loaded++; + this.errors[path] = message; + if (callback) callback(path, message); + } + loadAll() { + const promise = new Promise((resolve, reject) => { + const check = () => { + if (this.isLoadingComplete()) { + if (this.hasErrors()) reject(this.errors); + else resolve(this); + return; + } + requestAnimationFrame(check); + }; + requestAnimationFrame(check); + }); + return promise; + } + setRawDataURI(path, data) { + this.downloader.rawDataUris[this.pathPrefix + path] = data; + } + loadBinary(path, success = () => { + }, error = () => { + }) { + path = this.start(path); + if (this.reuseAssets(path, success, error)) return; + this.cache.assetsLoaded[path] = new Promise((resolve, reject) => { + this.downloader.downloadBinary(path, (data) => { + this.success(success, path, data); + resolve(data); + }, (status, responseText) => { + const errorMsg = `Couldn't load binary ${path}: status ${status}, ${responseText}`; + this.error(error, path, errorMsg); + reject(errorMsg); + }); + }); + } + loadText(path, success = () => { + }, error = () => { + }) { + path = this.start(path); + this.downloader.downloadText(path, (data) => { + this.success(success, path, data); + }, (status, responseText) => { + this.error(error, path, `Couldn't load text ${path}: status ${status}, ${responseText}`); + }); + } + loadJson(path, success = () => { + }, error = () => { + }) { + path = this.start(path); + if (this.reuseAssets(path, success, error)) return; + this.cache.assetsLoaded[path] = new Promise((resolve, reject) => { + this.downloader.downloadJson(path, (data) => { + this.success(success, path, data); + resolve(data); + }, (status, responseText) => { + const errorMsg = `Couldn't load JSON ${path}: status ${status}, ${responseText}`; + this.error(error, path, errorMsg); + reject(errorMsg); + }); + }); + } + reuseAssets(path, success = () => { + }, error = () => { + }) { + const loadedStatus = this.cache.getAsset(path); + const alreadyExistsOrLoading = loadedStatus !== void 0; + if (alreadyExistsOrLoading) { + this.cache.assetsLoaded[path] = loadedStatus.then((data) => { + data = data instanceof Image || data instanceof ImageBitmap ? this.textureLoader(data) : data; + this.success(success, path, data); + return data; + }).catch((errorMsg) => { + this.error(error, path, errorMsg); + return void 0; + }); + } + return alreadyExistsOrLoading; + } + loadTexture(path, success = () => { + }, error = () => { + }) { + path = this.start(path); + if (this.reuseAssets(path, success, error)) return; + const pma = this.texturePmaInfo[path]; + this.cache.assetsLoaded[path] = new Promise((resolve, reject) => { + const isBrowser = !!(typeof window !== "undefined" && typeof navigator !== "undefined" && window.document); + const isWebWorker = !isBrowser; + if (isWebWorker) { + fetch(path, { mode: "cors" }).then((response) => { + if (response.ok) return response.blob(); + const errorMsg = `Couldn't load image: ${path}`; + this.error(error, path, `Couldn't load image: ${path}`); + reject(errorMsg); + }).then((blob) => { + return blob ? createImageBitmap(blob, { premultiplyAlpha: "none", colorSpaceConversion: "none" }) : null; + }).then((bitmap) => { + if (bitmap) { + const texture = this.createTexture(path, pma, bitmap); + this.success(success, path, texture); + resolve(texture); + } + ; + }); + } else { + const image = new Image(); + image.crossOrigin = "anonymous"; + image.onload = () => { + const texture = this.createTexture(path, pma, image); + this.success(success, path, texture); + resolve(texture); + }; + image.onerror = () => { + const errorMsg = `Couldn't load image: ${path}`; + this.error(error, path, errorMsg); + reject(errorMsg); + }; + if (this.downloader.rawDataUris[path]) path = this.downloader.rawDataUris[path]; + image.src = path; + } + }); + } + loadTextureAtlas(path, success = () => { + }, error = () => { + }, fileAlias) { + const index = path.lastIndexOf("/"); + const parent = index >= 0 ? path.substring(0, index + 1) : ""; + path = this.start(path); + if (this.reuseAssets(path, success, error)) return; + this.cache.assetsLoaded[path] = new Promise((resolve, reject) => { + this.downloader.downloadText(path, (atlasText) => { + try { + const atlas = this.createTextureAtlas(atlasText, parent, path, fileAlias); + let toLoad = atlas.pages.length, abort = false; + if (toLoad === 0) { + this.success(success, path, atlas); + resolve(atlas); + return; + } + for (const page of atlas.pages) { + this.loadTexture( + this.texturePath(parent, page.name, fileAlias), + (imagePath, texture) => { + if (!abort) { + page.setTexture(texture); + if (--toLoad === 0) { + this.success(success, path, atlas); + resolve(atlas); + } + } + }, + (imagePath, message) => { + if (!abort) { + const errorMsg = `Couldn't load texture ${path} page image: ${imagePath}`; + this.error(error, path, errorMsg); + reject(errorMsg); + } + abort = true; + } + ); + } + } catch (e) { + const errorMsg = `Couldn't parse texture atlas ${path}: ${e.message}`; + this.error(error, path, errorMsg); + reject(errorMsg); + } + }, (status, responseText) => { + const errorMsg = `Couldn't load texture atlas ${path}: status ${status}, ${responseText}`; + this.error(error, path, errorMsg); + reject(errorMsg); + }); + }); + } + loadTextureAtlasButNoTextures(path, success = () => { + }, error = () => { + }) { + const index = path.lastIndexOf("/"); + const parent = index >= 0 ? path.substring(0, index + 1) : ""; + path = this.start(path); + if (this.reuseAssets(path, success, error)) return; + this.cache.assetsLoaded[path] = new Promise((resolve, reject) => { + this.downloader.downloadText(path, (atlasText) => { + try { + const atlas = this.createTextureAtlas(atlasText, parent, path); + this.success(success, path, atlas); + resolve(atlas); + } catch (e) { + const errorMsg = `Couldn't parse texture atlas ${path}: ${e.message}`; + this.error(error, path, errorMsg); + reject(errorMsg); + } + }, (status, responseText) => { + const errorMsg = `Couldn't load texture atlas ${path}: status ${status}, ${responseText}`; + this.error(error, path, errorMsg); + reject(errorMsg); + }); + }); + } + async loadBinaryAsync(path) { + return new Promise((resolve, reject) => { + this.loadBinary( + path, + (_, binary) => resolve(binary), + (_, message) => reject(message) + ); + }); + } + async loadJsonAsync(path) { + return new Promise((resolve, reject) => { + this.loadJson( + path, + (_, object) => resolve(object), + (_, message) => reject(message) + ); + }); + } + async loadTextureAsync(path) { + return new Promise((resolve, reject) => { + this.loadTexture( + path, + (_, texture) => resolve(texture), + (_, message) => reject(message) + ); + }); + } + async loadTextureAtlasAsync(path) { + return new Promise((resolve, reject) => { + this.loadTextureAtlas( + path, + (_, atlas) => resolve(atlas), + (_, message) => reject(message) + ); + }); + } + async loadTextureAtlasButNoTexturesAsync(path) { + return new Promise((resolve, reject) => { + this.loadTextureAtlasButNoTextures( + path, + (_, atlas) => resolve(atlas), + (_, message) => reject(message) + ); + }); + } + setCache(cache) { + this.cache = cache; + } + get(path) { + return this.cache.assets[this.pathPrefix + path]; + } + require(path) { + path = this.pathPrefix + path; + const asset = this.cache.assets[path]; + if (asset) return asset; + const error = this.errors[path]; + throw Error(`Asset not found: ${path}${error ? ` +${error}` : ""}`); + } + remove(path) { + path = this.pathPrefix + path; + const asset = this.cache.assets[path]; + if (asset.dispose) asset.dispose(); + delete this.cache.assets[path]; + delete this.cache.assetsRefCount[path]; + delete this.cache.assetsLoaded[path]; + return asset; + } + removeAll() { + for (const path in this.cache.assets) { + const asset = this.cache.assets[path]; + if (asset.dispose) asset.dispose(); + } + this.cache.assets = {}; + this.cache.assetsLoaded = {}; + this.cache.assetsRefCount = {}; + } + isLoadingComplete() { + return this.toLoad === 0; + } + getToLoad() { + return this.toLoad; + } + getLoaded() { + return this.loaded; + } + dispose() { + this.removeAll(); + } + // dispose asset only if it's not used by others + disposeAsset(path) { + const asset = this.cache.assets[path]; + if (asset instanceof TextureAtlas) { + asset.dispose(); + return; + } + this.disposeAssetInternal(path); + } + hasErrors() { + return Object.keys(this.errors).length > 0; + } + getErrors() { + return this.errors; + } + disposeAssetInternal(path) { + if (this.cache.assetsRefCount[path] > 0 && --this.cache.assetsRefCount[path] === 0) { + return this.remove(path); + } + } + createTextureAtlas(atlasText, parentPath, path, fileAlias) { + const atlas = new TextureAtlas(atlasText); + atlas.dispose = () => { + if (this.cache.assetsRefCount[path] <= 0) return; + this.disposeAssetInternal(path); + for (const page of atlas.pages) { + page.texture?.dispose(); + } + }; + for (const page of atlas.pages) { + const texturePath = this.texturePath(parentPath, page.name, fileAlias); + this.texturePmaInfo[this.pathPrefix + texturePath] = page.pma; + } + return atlas; + } + createTexture(path, pma, image) { + const texture = this.textureLoader(image, pma); + const textureDispose = texture.dispose.bind(texture); + texture.dispose = () => { + if (this.disposeAssetInternal(path)) textureDispose(); + }; + return texture; + } + texturePath(parentPath, pageName, fileAlias) { + if (!fileAlias) return parentPath + pageName; + return fileAlias[pageName]; + } + }; + var AssetCache = class _AssetCache { + assets = {}; + assetsRefCount = {}; + assetsLoaded = {}; + static AVAILABLE_CACHES = /* @__PURE__ */ new Map(); + static getCache(id) { + const cache = _AssetCache.AVAILABLE_CACHES.get(id); + if (cache) return cache; + const newCache = new _AssetCache(); + _AssetCache.AVAILABLE_CACHES.set(id, newCache); + return newCache; + } + async addAsset(path, asset) { + this.assetsLoaded[path] = Promise.resolve(asset); + this.assets[path] = asset; + return asset; + } + getAsset(path) { + return this.assetsLoaded[path]; + } + }; + var Downloader = class { + callbacks = {}; + rawDataUris = {}; + dataUriToString(dataUri) { + if (!dataUri.startsWith("data:")) { + throw new Error("Not a data URI."); + } + let base64Idx = dataUri.indexOf("base64,"); + if (base64Idx !== -1) { + base64Idx += "base64,".length; + return atob(dataUri.substr(base64Idx)); + } else { + return dataUri.substr(dataUri.indexOf(",") + 1); + } + } + base64ToUint8Array(base64) { + var binary_string = window.atob(base64); + var len = binary_string.length; + var bytes = new Uint8Array(len); + for (let i = 0; i < len; i++) { + bytes[i] = binary_string.charCodeAt(i); + } + return bytes; + } + dataUriToUint8Array(dataUri) { + if (!dataUri.startsWith("data:")) { + throw new Error("Not a data URI."); + } + let base64Idx = dataUri.indexOf("base64,"); + if (base64Idx === -1) throw new Error("Not a binary data URI."); + base64Idx += "base64,".length; + return this.base64ToUint8Array(dataUri.substr(base64Idx)); + } + downloadText(url, success, error) { + if (this.start(url, success, error)) return; + const rawDataUri = this.rawDataUris[url]; + if (rawDataUri && !rawDataUri.includes(".")) { + try { + this.finish(url, 200, this.dataUriToString(rawDataUri)); + } catch (e) { + this.finish(url, 400, JSON.stringify(e)); + } + return; + } + const request = new XMLHttpRequest(); + request.overrideMimeType("text/html"); + request.open("GET", rawDataUri ? rawDataUri : url, true); + const done = () => { + this.finish(url, request.status, request.responseText); + }; + request.onload = done; + request.onerror = done; + request.send(); + } + downloadJson(url, success, error) { + this.downloadText(url, (data) => { + success(JSON.parse(data)); + }, error); + } + downloadBinary(url, success, error) { + if (this.start(url, success, error)) return; + const rawDataUri = this.rawDataUris[url]; + if (rawDataUri && !rawDataUri.includes(".")) { + try { + this.finish(url, 200, this.dataUriToUint8Array(rawDataUri)); + } catch (e) { + this.finish(url, 400, JSON.stringify(e)); + } + return; + } + const request = new XMLHttpRequest(); + request.open("GET", rawDataUri ? rawDataUri : url, true); + request.responseType = "arraybuffer"; + const onerror = () => { + this.finish(url, request.status, request.response); + }; + request.onload = () => { + if (request.status === 200 || request.status === 0) + this.finish(url, 200, new Uint8Array(request.response)); + else + onerror(); + }; + request.onerror = onerror; + request.send(); + } + start(url, success, error) { + let callbacks = this.callbacks[url]; + try { + if (callbacks) return true; + this.callbacks[url] = callbacks = []; + } finally { + callbacks.push(success, error); + } + } + finish(url, status, data) { + const callbacks = this.callbacks[url]; + delete this.callbacks[url]; + if (status === 200 || status === 0) { + for (let i = 0, n = callbacks.length; i < n; i += 2) + callbacks[i](data); + } else { + for (let i = 1, n = callbacks.length; i < n; i += 2) + callbacks[i](status, data); + } + } + }; + + // spine-core/src/attachments/BoundingBoxAttachment.ts + var BoundingBoxAttachment = class _BoundingBoxAttachment extends VertexAttachment { + color = new Color(1, 1, 1, 1); + constructor(name) { + super(name); + } + copy() { + const copy = new _BoundingBoxAttachment(this.name); + this.copyTo(copy); + copy.color.setFromColor(this.color); + return copy; + } + }; + + // spine-core/src/attachments/ClippingAttachment.ts + var ClippingAttachment = class _ClippingAttachment extends VertexAttachment { + /** Clipping is performed between the clipping attachment's slot and the end slot. If null, clipping is done until the end of + * the skeleton's rendering. */ + endSlot = null; + /** When true the clipping polygon is treated as convex for more efficient clipping. If the polygon deforms to concave then the + * convex hull is used. When false the clipping polygon can be concave and if so has an additional CPU cost. Inverse clipping + * always uses convex. */ + convex = false; + /** When false, everything inside the clipping polygon is visible. When true, everything outside the clipping polygon is + * visible and clipping is convex. */ + inverse = false; + // Nonessential. + /** The color of the clipping polygon as it was in Spine. Available only when nonessential data was exported. Clipping polygons + * are not usually rendered at runtime. */ + color = new Color(0.2275, 0.2275, 0.8078, 1); + // ce3a3aff + constructor(name) { + super(name); + } + copy() { + const copy = new _ClippingAttachment(this.name); + this.copyTo(copy); + copy.endSlot = this.endSlot; + copy.convex = this.convex; + copy.inverse = this.inverse; + copy.color.setFromColor(this.color); + return copy; + } + }; + + // spine-core/src/attachments/PathAttachment.ts + var PathAttachment = class _PathAttachment extends VertexAttachment { + /** The lengths along the path in the setup pose from the start of the path to the end of each Bezier curve. */ + lengths = []; + /** If true, the start and end knots are connected. */ + closed = false; + /** If true, additional calculations are performed to make computing positions along the path more accurate so movement along + * the path has a constant speed. */ + constantSpeed = false; + /** The color of the path as it was in Spine. Available only when nonessential data was exported. Paths are not usually + * rendered at runtime. */ + color = new Color(1, 1, 1, 1); + constructor(name) { + super(name); + } + copy() { + const copy = new _PathAttachment(this.name); + this.copyTo(copy); + copy.lengths = []; + Utils.arrayCopy(this.lengths, 0, copy.lengths, 0, this.lengths.length); + copy.closed = this.closed; + copy.constantSpeed = this.constantSpeed; + copy.color.setFromColor(this.color); + return copy; + } + }; + + // spine-core/src/attachments/PointAttachment.ts + var PointAttachment = class _PointAttachment extends VertexAttachment { + /** The local x position. */ + x = 0; + /** The local y position. */ + y = 0; + /** The local rotation in degrees, counter clockwise. */ + rotation = 0; + /** The color of the point attachment as it was in Spine. Available only when nonessential data was exported. Point attachments + * are not usually rendered at runtime. */ + color = new Color(0.38, 0.94, 0, 1); + constructor(name) { + super(name); + } + /** Computes the world position from the local position. */ + computeWorldPosition(bone, point) { + point.x = this.x * bone.a + this.y * bone.b + bone.worldX; + point.y = this.x * bone.c + this.y * bone.d + bone.worldY; + return point; + } + /** Computes the world rotation from the local rotation. */ + computeWorldRotation(bone) { + const r = this.rotation * MathUtils.degRad, cos = Math.cos(r), sin = Math.sin(r); + const x = cos * bone.a + sin * bone.b; + const y = cos * bone.c + sin * bone.d; + return MathUtils.atan2Deg(y, x); + } + copy() { + const copy = new _PointAttachment(this.name); + copy.x = this.x; + copy.y = this.y; + copy.rotation = this.rotation; + copy.color.setFromColor(this.color); + return copy; + } + }; + + // spine-core/src/AtlasAttachmentLoader.ts + var AtlasAttachmentLoader = class { + atlas; + allowMissingRegions; + constructor(atlas, allowMissingRegions = false) { + this.atlas = atlas; + this.allowMissingRegions = allowMissingRegions; + } + /** Sets each {@link Sequence.regions} by calling {@link findRegion} for each texture region using + * {@link Sequence.getPath}. */ + findRegions(name, basePath, sequence) { + const regions = sequence.regions; + for (let i = 0, n = regions.length; i < n; i++) + regions[i] = this.findRegion(name, sequence.getPath(basePath, i)); + } + /** Looks for the region with the specified path. If not found and {@link allowMissingRegions} is false, an error is + * raised. */ + findRegion(name, path) { + const region = this.atlas.findRegion(path); + if (!region && !this.allowMissingRegions) + throw new Error(`Region not found in atlas: ${path} (attachment: ${name})`); + return region; + } + newRegionAttachment(skin, placeholder, name, path, sequence) { + this.findRegions(name, path, sequence); + return new RegionAttachment(name, sequence); + } + newMeshAttachment(skin, placeholder, name, path, sequence) { + this.findRegions(name, path, sequence); + return new MeshAttachment(name, sequence); + } + newBoundingBoxAttachment(skin, placeholder, name) { + return new BoundingBoxAttachment(name); + } + newPathAttachment(skin, placeholder, name) { + return new PathAttachment(name); + } + newPointAttachment(skin, placeholder, name) { + return new PointAttachment(name); + } + newClippingAttachment(skin, placeholder, name) { + return new ClippingAttachment(name); + } + }; + + // spine-core/src/PosedData.ts + var PosedData = class { + name; + setupPose; + /** When true, {@link Skeleton.updateWorldTransform} only updates this constraint if the {@link Skeleton.skin} + * contains this constraint. + * + * See {@link Skin.constraints}. */ + skinRequired = false; + constructor(name, setupPose) { + if (name == null) throw new Error("name cannot be null."); + this.name = name; + this.setupPose = setupPose; + } + }; + + // spine-core/src/BoneData.ts + var BoneData = class _BoneData extends PosedData { + /** The index of the bone in {@link Skeleton.bones}. */ + index = 0; + /** The parent bone, or null if this bone is the root. */ + parent = null; + /** The bone's length. */ + length = 0; + // Nonessential. + /** The color of the bone as it was in Spine. Available only when nonessential data was exported. Bones are not usually + * rendered at runtime. */ + color = new Color(); + /** The bone icon name as it was in Spine, or null if nonessential data was not exported. */ + icon; + /** The bone icon's display size scale, or 1 if nonessential data was not exported. */ + iconSize = 1; + /** The bone icon's display rotation in degrees, or 0 if nonessential data was not exported. */ + iconRotation = 0; + /** False if the bone was hidden in Spine and nonessential data was exported. Does not affect runtime rendering. */ + visible = false; + constructor(index, name, parent) { + super(name, new BonePose()); + if (index < 0) throw new Error("index must be >= 0."); + if (!name) throw new Error("name cannot be null."); + this.index = index; + this.parent = parent; + } + copy(parent) { + const copy = new _BoneData(this.index, this.name, parent); + copy.length = this.length; + copy.setupPose.set(this.setupPose); + return copy; + } + }; + var Inherit = /* @__PURE__ */ ((Inherit2) => { + Inherit2[Inherit2["Normal"] = 0] = "Normal"; + Inherit2[Inherit2["OnlyTranslation"] = 1] = "OnlyTranslation"; + Inherit2[Inherit2["NoRotationOrReflection"] = 2] = "NoRotationOrReflection"; + Inherit2[Inherit2["NoScale"] = 3] = "NoScale"; + Inherit2[Inherit2["NoScaleOrReflection"] = 4] = "NoScaleOrReflection"; + return Inherit2; + })(Inherit || {}); + + // spine-core/src/BonePose.ts + var BonePose = class { + bone; + /** The local x translation. */ + x = 0; + /** The local y translation. */ + y = 0; + /** The local rotation in degrees, counter clockwise. */ + rotation = 0; + /** The local scaleX. */ + scaleX = 0; + /** The local scaleY. */ + scaleY = 0; + /** The local shearX. */ + shearX = 0; + /** The local shearY. */ + shearY = 0; + inherit = 0 /* Normal */; + /** The world transform `[a b][c d]` x-axis x component. */ + a = 0; + /** The world transform `[a b][c d]` y-axis x component. */ + b = 0; + /** The world transform `[a b][c d]` x-axis y component. */ + c = 0; + /** The world transform `[a b][c d]` y-axis y component. */ + d = 0; + /** The world X position. If changed, {@link updateLocalTransform} should be called. */ + worldY = 0; + /** The world Y position. If changed, {@link updateLocalTransform} should be called. */ + worldX = 0; + world = 0; + local = 0; + set(pose) { + if (pose == null) throw new Error("pose cannot be null."); + this.x = pose.x; + this.y = pose.y; + this.rotation = pose.rotation; + this.scaleX = pose.scaleX; + this.scaleY = pose.scaleY; + this.shearX = pose.shearX; + this.shearY = pose.shearY; + this.inherit = pose.inherit; + } + setPosition(x, y) { + this.x = x; + this.y = y; + } + setScale(scaleOrX, scaleY) { + this.scaleX = scaleOrX; + this.scaleY = scaleY === void 0 ? scaleOrX : scaleY; + } + /** Determines how parent world transforms affect this bone. */ + getInherit() { + return this.inherit; + } + setInherit(inherit) { + if (inherit == null) throw new Error("inherit cannot be null."); + this.inherit = inherit; + } + /** Called by {@link Skeleton.updateCache} to compute the world transform, if needed. */ + update(skeleton, physics) { + if (this.world !== skeleton._update) this.updateWorldTransform(skeleton); + } + /** Computes the world transform using the parent bone's world transform and this applied local pose. Child bones are not + * updated. + * + * See World transforms in the Spine + * Runtimes Guide. */ + updateWorldTransform(skeleton) { + if (this.local === skeleton._update) + this.updateLocalTransform(skeleton); + else + this.world = skeleton._update; + const rotation = this.rotation; + const scaleX = this.scaleX; + const scaleY = this.scaleY; + const shearX = this.shearX; + const shearY = this.shearY; + if (!this.bone.parent) { + const sx = skeleton.scaleX, sy = skeleton.scaleY; + const rx = (rotation + shearX) * MathUtils.degRad; + const ry = (rotation + 90 + shearY) * MathUtils.degRad; + this.a = Math.cos(rx) * scaleX * sx; + this.b = Math.cos(ry) * scaleY * sx; + this.c = Math.sin(rx) * scaleX * sy; + this.d = Math.sin(ry) * scaleY * sy; + this.worldX = this.x * sx + skeleton.x; + this.worldY = this.y * sy + skeleton.y; + return; + } + const parent = this.bone.parent.appliedPose; + let pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d; + this.worldX = pa * this.x + pb * this.y + parent.worldX; + this.worldY = pc * this.x + pd * this.y + parent.worldY; + switch (this.inherit) { + case 0 /* Normal */: { + const rx = (rotation + shearX) * MathUtils.degRad; + const ry = (rotation + 90 + shearY) * MathUtils.degRad; + const la = Math.cos(rx) * scaleX; + const lb = Math.cos(ry) * scaleY; + const lc = Math.sin(rx) * scaleX; + const ld = Math.sin(ry) * scaleY; + this.a = pa * la + pb * lc; + this.b = pa * lb + pb * ld; + this.c = pc * la + pd * lc; + this.d = pc * lb + pd * ld; + return; + } + case 1 /* OnlyTranslation */: { + const sx = skeleton.scaleX, sy = skeleton.scaleY; + const rx = (rotation + shearX) * MathUtils.degRad; + const ry = (rotation + 90 + shearY) * MathUtils.degRad; + this.a = Math.cos(rx) * scaleX * sx; + this.b = Math.cos(ry) * scaleY * sx; + this.c = Math.sin(rx) * scaleX * sy; + this.d = Math.sin(ry) * scaleY * sy; + break; + } + case 2 /* NoRotationOrReflection */: { + const sx = skeleton.scaleX, sy = skeleton.scaleY, sxi = 1 / sx, syi = 1 / sy; + pa *= sxi; + pc *= syi; + let s = pa * pa + pc * pc; + let r = 0; + if (s > MathUtils.epsilon2) { + s = Math.abs(pa * pd * syi - pb * sxi * pc) / s; + pb = pc * s; + pd = pa * s; + r = rotation - MathUtils.atan2Deg(pc, pa); + } else { + pa = 0; + pc = 0; + r = rotation - 90 + MathUtils.atan2Deg(pd, pb); + } + const rx = (r + shearX) * MathUtils.degRad; + const ry = (r + shearY + 90) * MathUtils.degRad; + const la = Math.cos(rx) * scaleX; + const lb = Math.cos(ry) * scaleY; + const lc = Math.sin(rx) * scaleX; + const ld = Math.sin(ry) * scaleY; + this.a = (pa * la - pb * lc) * sx; + this.b = (pa * lb - pb * ld) * sx; + this.c = (pc * la + pd * lc) * sy; + this.d = (pc * lb + pd * ld) * sy; + break; + } + case 3 /* NoScale */: + case 4 /* NoScaleOrReflection */: { + const sx = skeleton.scaleX, sy = skeleton.scaleY, sxi = 1 / sx, syi = 1 / sy; + const r = rotation * MathUtils.degRad, cos = Math.cos(r), sin = Math.sin(r); + let za = (pa * cos + pb * sin) * sxi; + let zc = (pc * cos + pd * sin) * syi; + const s = 1 / Math.sqrt(za * za + zc * zc); + za *= s; + zc *= s; + let zb = -zc, zd = za; + if (this.inherit === 3 /* NoScale */ && pa * pd - pb * pc < 0 !== (sx < 0 !== sy < 0)) { + zb = -zb; + zd = -zd; + } + const rx = shearX * MathUtils.degRad; + const ry = (90 + shearY) * MathUtils.degRad; + const la = Math.cos(rx) * scaleX; + const lb = Math.cos(ry) * scaleY; + const lc = Math.sin(rx) * scaleX; + const ld = Math.sin(ry) * scaleY; + this.a = (za * la + zb * lc) * sx; + this.b = (za * lb + zb * ld) * sx; + this.c = (zc * la + zd * lc) * sy; + this.d = (zc * lb + zd * ld) * sy; + break; + } + } + } + /** Computes the local transform values from the world transform. + * + * If the world transform is modified (by a constraint, {@link rotateWorld}, etc) then this method should be called so + * the local transform matches the world transform. The local transform may be needed by other code (eg to apply another + * constraint). + * + * Some information is ambiguous in the world transform, such as -1,-1 scale versus 180 rotation. The local transform after + * calling this method is equivalent to the local transform used to compute the world transform, but may not be identical. */ + updateLocalTransform(skeleton) { + this.local = 0; + this.world = skeleton._update; + const sx = skeleton.scaleX, sy = skeleton.scaleY; + if (!this.bone.parent) { + const sxi = 1 / sx, syi = 1 / sy; + this.x = (this.worldX - skeleton.x) * sxi; + this.y = (this.worldY - skeleton.y) * syi; + this.set5(this.a * sxi, this.b * sxi, this.c * syi, this.d * syi, 0); + return; + } + const parent = this.bone.parent.appliedPose; + let pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d; + const pad = pa * pd - pb * pc, pid = 1 / (pa * pd - pb * pc); + const ia = pd * pid, ib = pb * pid, ic = pc * pid, id = pa * pid; + const dx = this.worldX - parent.worldX, dy = this.worldY - parent.worldY; + this.x = dx * ia - dy * ib; + this.y = dy * id - dx * ic; + switch (this.inherit) { + case 0 /* Normal */: + this.set5(ia * this.a - ib * this.c, ia * this.b - ib * this.d, id * this.c - ic * this.a, id * this.d - ic * this.b, 0); + break; + case 1 /* OnlyTranslation */: { + const sxi = 1 / sx, syi = 1 / sy; + this.set5(this.a * sxi, this.b * sxi, this.c * syi, this.d * syi, 0); + break; + } + case 2 /* NoRotationOrReflection */: { + const sxi = 1 / sx, syi = 1 / sy; + pa *= sxi; + pc *= syi; + const wa = this.a * sxi, wb = this.b * sxi, wc = this.c * syi, wd = this.d * syi; + const s = 1 / (pa * pa + pc * pc), det = 1 / Math.abs(pad * sxi * syi); + this.set5( + (pa * wa + pc * wc) * s, + (pa * wb + pc * wd) * s, + (pa * wc - pc * wa) * det, + (pa * wd - pc * wb) * det, + MathUtils.atan2Deg(pc, pa) + ); + break; + } + case 3 /* NoScale */: + case 4 /* NoScaleOrReflection */: { + const sxi = 1 / sx, syi = 1 / sy; + const wa = this.a * sxi, wb = this.b * sxi, wc = this.c * syi, wd = this.d * syi; + let tx = pd * this.a - pb * this.c, ty = pa * this.c - pc * this.a; + if (pad < 0) { + tx = -tx; + ty = -ty; + } + let r = MathUtils.atan2Deg(ty, tx); + this.rotation = r; + r *= MathUtils.degRad; + const cos = Math.cos(r), sin = Math.sin(r); + let za = (pa * cos + pb * sin) * sxi; + let zc = (pc * cos + pd * sin) * syi; + const s = 1 / Math.sqrt(za * za + zc * zc); + za *= s; + zc *= s; + const si = this.inherit === 3 /* NoScale */ && pad < 0 !== (sx < 0 !== sy < 0) ? -1 : 1; + this.set4(za * wa + zc * wc, za * wb + zc * wd, (za * wc - zc * wa) * si, (za * wd - zc * wb) * si); + } + } + } + set4(ra, rb, rc, rd) { + const x = ra * ra + rc * rc, y = rb * rb + rd * rd; + if (x > MathUtils.epsilon2) { + this.shearX = MathUtils.atan2Deg(rc, ra); + this.scaleX = Math.sqrt(x); + } else { + this.shearX = 0; + this.scaleX = 0; + } + this.scaleY = Math.sqrt(y); + if (y > MathUtils.epsilon2) { + this.shearY = MathUtils.atan2Deg(rd, rb); + if (ra * rd - rb * rc < 0) { + this.scaleY = -this.scaleY; + this.shearY += 90; + } else + this.shearY -= 90; + if (this.shearY > 180) + this.shearY -= 360; + else if (this.shearY <= -180) + this.shearY += 360; + } else + this.shearY = 0; + } + set5(ra, rb, rc, rd, ro) { + this.shearX = 0; + const x = ra * ra + rc * rc, y = rb * rb + rd * rd; + if (x > MathUtils.epsilon2) { + const r = MathUtils.atan2Deg(rc, ra); + this.rotation = r + ro; + this.scaleX = Math.sqrt(x); + this.scaleY = Math.sqrt(y); + if (y > MathUtils.epsilon2) { + this.shearY = MathUtils.atan2Deg(rd, rb); + if (ra * rd - rb * rc < 0) { + this.scaleY = -this.scaleY; + this.shearY += 90 - r; + } else + this.shearY -= 90 + r; + if (this.shearY > 180) + this.shearY -= 360; + else if (this.shearY <= -180) + this.shearY += 360; + } else + this.shearY = 0; + } else { + this.scaleX = 0; + this.scaleY = Math.sqrt(y); + this.shearY = 0; + this.rotation = y > MathUtils.epsilon2 ? MathUtils.atan2Deg(rd, rb) - 90 + ro : ro; + } + } + /** If the world transform has been modified by constraints and the local transform no longer matches, + * {@link updateLocalTransform} is called. Call this after {@link Skeleton.updateWorldTransform} before + * using the applied local transform. */ + validateLocalTransform(skeleton) { + if (this.local === skeleton._update) this.updateLocalTransform(skeleton); + } + modifyLocal(skeleton) { + if (this.local === skeleton._update) this.updateLocalTransform(skeleton); + this.world = 0; + this.resetWorld(skeleton, skeleton._update); + } + modifyWorld(skeleton) { + const update = skeleton._update; + this.local = update; + this.world = update; + this.resetWorld(skeleton, update); + } + resetWorld(skeleton, update) { + const children = this.bone.children; + for (let i = 0, n = children.length; i < n; i++) { + const child = children[i].appliedPose; + if (child.world === update) { + if (child.local === update) child.updateLocalTransform(skeleton); + child.world = 0; + child.resetWorld(skeleton, update); + } + } + } + /** The world rotation for the X axis, calculated using {@link a} and {@link c}. This is the direction the bone is + * pointing. */ + getWorldRotationX() { + return MathUtils.atan2Deg(this.c, this.a); + } + /** The world rotation for the Y axis, calculated using {@link b} and {@link d}. */ + getWorldRotationY() { + return MathUtils.atan2Deg(this.d, this.b); + } + /** The magnitude (always positive) of the world scale X, calculated using {@link a} and {@link c}. */ + getWorldScaleX() { + return Math.sqrt(this.a * this.a + this.c * this.c); + } + /** The magnitude (always positive) of the world scale Y, calculated using {@link b} and {@link d}. */ + getWorldScaleY() { + return Math.sqrt(this.b * this.b + this.d * this.d); + } + // public Matrix3 getWorldTransform (Matrix3 worldTransform) { + // if (worldTransform == null) throw new IllegalArgumentException("worldTransform cannot be null."); + // float[] val = worldTransform.val; + // val[M00] = a; + // val[M01] = b; + // val[M10] = c; + // val[M11] = d; + // val[M02] = worldX; + // val[M12] = worldY; + // val[M20] = 0; + // val[M21] = 0; + // val[M22] = 1; + // return worldTransform; + // } + /** Transforms a point from world coordinates to the bone's local coordinates. */ + worldToLocal(world) { + if (world == null) throw new Error("world cannot be null."); + const det = this.a * this.d - this.b * this.c; + const x = world.x - this.worldX, y = world.y - this.worldY; + world.x = (x * this.d - y * this.b) / det; + world.y = (y * this.a - x * this.c) / det; + return world; + } + /** Transforms a point from the bone's local coordinates to world coordinates. */ + localToWorld(local) { + if (local == null) throw new Error("local cannot be null."); + const x = local.x, y = local.y; + local.x = x * this.a + y * this.b + this.worldX; + local.y = x * this.c + y * this.d + this.worldY; + return local; + } + /** Transforms a point from world coordinates to the parent bone's local coordinates. */ + worldToParent(world) { + if (world == null) throw new Error("world cannot be null."); + return this.bone.parent == null ? world : this.bone.parent.appliedPose.worldToLocal(world); + } + /** Transforms a point from the parent bone's coordinates to world coordinates. */ + parentToWorld(world) { + if (world == null) throw new Error("world cannot be null."); + return this.bone.parent == null ? world : this.bone.parent.appliedPose.localToWorld(world); + } + /** Transforms a world rotation to a local rotation. */ + worldToLocalRotation(worldRotation) { + worldRotation *= MathUtils.degRad; + const sin = Math.sin(worldRotation), cos = Math.cos(worldRotation); + return MathUtils.atan2Deg(this.a * sin - this.c * cos, this.d * cos - this.b * sin) + this.rotation - this.shearX; + } + /** Transforms a local rotation to a world rotation. */ + localToWorldRotation(localRotation) { + localRotation = (localRotation - this.rotation - this.shearX) * MathUtils.degRad; + const sin = Math.sin(localRotation), cos = Math.cos(localRotation); + return MathUtils.atan2Deg(cos * this.c + sin * this.d, cos * this.a + sin * this.b); + } + /** Rotates the world transform the specified amount. */ + rotateWorld(degrees) { + degrees *= MathUtils.degRad; + const sin = Math.sin(degrees), cos = Math.cos(degrees); + const ra = this.a, rb = this.b; + this.a = cos * ra - sin * this.c; + this.b = cos * rb - sin * this.d; + this.c = sin * ra + cos * this.c; + this.d = sin * rb + cos * this.d; + } + }; + + // spine-core/src/Posed.ts + var Posed = class { + /** The constraint's setup pose data. */ + data; + pose; + constrainedPose; + appliedPose; + constructor(data, pose, constrainedPose) { + if (data == null) throw new Error("data cannot be null."); + this.data = data; + this.pose = pose; + this.constrainedPose = constrainedPose; + this.appliedPose = pose; + } + /** Sets the unconstrained pose to the setup pose. */ + setupPose() { + this.pose.set(this.data.setupPose); + } + /** The setup pose data. May be shared with multiple instances. */ + getData() { + return this.data; + } + /** The unconstrained pose for this object, set by animations and application code. */ + getPose() { + return this.pose; + } + /** The pose to use for rendering. If no constraints modify this pose, this is the same as {@link pose}. Otherwise it is a + * copy of {@link pose} modified by constraints. */ + getAppliedPose() { + return this.appliedPose; + } + /** Sets the applied pose to the unconstrained pose, for when no constraints will modify the pose. */ + unconstrained() { + this.appliedPose = this.pose; + } + /** Sets the applied pose to the constrained pose, in anticipation of the applied pose being modified by constraints. */ + constrained() { + this.appliedPose = this.constrainedPose; + } + /** Sets the constrained pose to the unconstrained pose, as a starting point for constraints to be applied. */ + resetConstrained() { + this.constrainedPose.set(this.pose); + } + }; + + // spine-core/src/PosedActive.ts + var PosedActive = class extends Posed { + active = false; + constructor(data, pose, constrained) { + super(data, pose, constrained); + this.setupPose(); + } + /** Returns false when this constraint won't be updated by + * {@link Skeleton.updateWorldTransform} because a skin is required and the + * {@link Skeleton.skin active skin} does not contain this item. See {@link Skin.bones}, {@link Skin.constraints}, + * {@link PosedData.skinRequired}, and {@link Skeleton.updateCache}. */ + isActive() { + return this.active; + } + }; + + // spine-core/src/Bone.ts + var Bone = class _Bone extends PosedActive { + /** The parent bone, or null if this is the root bone. */ + parent = null; + /** The immediate children of this bone. */ + children = []; + sorted = false; + constructor(data, parent) { + super(data, new BonePose(), new BonePose()); + this.parent = parent; + this.appliedPose.bone = this; + this.constrainedPose.bone = this; + } + /** Copy constructor. Does not copy the {@link children} bones. */ + copy(parent) { + const copy = new _Bone(this.data, parent); + copy.pose.set(this.pose); + return copy; + } + }; + + // spine-core/src/Constraint.ts + var Constraint = class extends PosedActive { + constructor(data, pose, constrained) { + super(data, pose, constrained); + } + isSourceActive() { + return true; + } + }; + + // spine-core/src/DrawOrder.ts + var DrawOrder = class { + _setupPose; + /** The unconstrained draw order, set by animations and application code. */ + pose; + constrainedPose; + /** The constrained draw order for rendering. If no constraints modify the draw order, this is the same as {@link pose}. + * Otherwise it is a copy of {@link pose} modified by constraints. */ + appliedPose; + constructor(setupPose) { + this._setupPose = setupPose; + this.pose = [...setupPose]; + this.constrainedPose = []; + this.appliedPose = this.pose; + } + /** Sets the unconstrained draw order to the setup pose order. */ + setupPose() { + this.pose.length = this._setupPose.length; + Utils.arrayCopy(this._setupPose, 0, this.pose, 0, this._setupPose.length); + } + /** Sets the applied pose to the unconstrained pose, for when no constraints will modify the draw order. */ + unconstrained() { + this.appliedPose = this.pose; + } + /** Sets the applied pose to the constrained pose, in anticipation of the applied pose being modified by constraints. */ + constrained() { + this.appliedPose = this.constrainedPose; + } + /** Copies the unconstrained pose to the constrained pose, as a starting point for constraints to be applied. */ + resetConstrained() { + this.constrainedPose.length = this.pose.length; + Utils.arrayCopy(this.pose, 0, this.constrainedPose, 0, this.pose.length); + } + }; + + // spine-core/src/ConstraintData.ts + var ConstraintData = class extends PosedData { + constructor(name, setup) { + super(name, setup); + } + }; + var ScaleYMode = /* @__PURE__ */ ((ScaleYMode2) => { + ScaleYMode2[ScaleYMode2["None"] = 0] = "None"; + ScaleYMode2[ScaleYMode2["Uniform"] = 1] = "Uniform"; + ScaleYMode2[ScaleYMode2["Volume"] = 2] = "Volume"; + return ScaleYMode2; + })(ScaleYMode || {}); + + // spine-core/src/Event.ts + var Event = class { + /** The animation time this event was keyed, or -1 for the setup pose. */ + time = 0; + data; + /** The integer payload for this event. */ + intValue = 0; + /** The float payload for this event. */ + floatValue = 0; + stringValue = null; + /** If an audio path is set, the volume for the audio. */ + volume = 0; + /** If an audio path is set, the left/right balance for the audio. */ + balance = 0; + constructor(time, data) { + if (!data) throw new Error("data cannot be null."); + this.time = time; + this.data = data; + } + }; + + // spine-core/src/EventData.ts + var EventData = class { + /** The name of the event, unique across all events in the skeleton. + * + * See {@link SkeletonData.findEvent}. */ + name; + _audioPath = null; + /** Path to an audio file relative to the audio folder as defined in Spine. */ + get audioPath() { + return this._audioPath; + } + set audioPath(audioPath) { + if (audioPath == null) throw new Error("audioPath cannot be null."); + this._audioPath = audioPath; + } + /** The setup values that are shared by all events with this data. */ + setupPose = new Event(-1, this); + constructor(name) { + this.name = name; + } + }; + + // spine-core/src/IkConstraintPose.ts + var IkConstraintPose = class { + /** For two bone IK, controls the bend direction of the IK bones, either 1 or -1. */ + bendDirection = 0; + /** For one bone IK, when true and the target is too close, the bone is scaled to reach it. */ + compress = false; + /** When true and the target is out of range, the parent bone is scaled to reach it. + * + * For two bone IK: 1) the child bone's local Y translation is set to 0, 2) stretch is not applied if {@link softness} is > 0, + * and 3) if the parent bone has local nonuniform scale, stretch is not applied. */ + stretch = false; + /** A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. + * + * For two bone IK: if the parent bone has local nonuniform scale, the child bone's local Y translation is set to 0. */ + mix = 0; + /** For two bone IK, the target bone's distance from the maximum reach of the bones where rotation begins to slow. The bones + * will not straighten completely until the target is this far out of range. */ + softness = 0; + set(pose) { + this.mix = pose.mix; + this.softness = pose.softness; + this.bendDirection = pose.bendDirection; + this.compress = pose.compress; + this.stretch = pose.stretch; + } + }; + + // spine-core/src/IkConstraint.ts + var IkConstraint = class _IkConstraint extends Constraint { + /** The 1 or 2 bones that will be modified by this IK constraint. */ + bones; + /** The bone that is the IK target. */ + target; + constructor(data, skeleton) { + super(data, new IkConstraintPose(), new IkConstraintPose()); + if (!skeleton) throw new Error("skeleton cannot be null."); + this.bones = []; + for (const boneData of data.bones) + this.bones.push(skeleton.bones[boneData.index].constrainedPose); + this.target = skeleton.bones[data.target.index]; + } + copy(skeleton) { + var copy = new _IkConstraint(this.data, skeleton); + copy.pose.set(this.pose); + return copy; + } + update(skeleton, physics) { + const p = this.appliedPose; + if (p.mix === 0) return; + const target = this.target.appliedPose; + const bones = this.bones; + switch (bones.length) { + case 1: + _IkConstraint.apply(skeleton, bones[0], target.worldX, target.worldY, p.compress, p.stretch, this.data.scaleYMode, p.mix); + break; + case 2: + _IkConstraint.apply( + skeleton, + bones[0], + bones[1], + target.worldX, + target.worldY, + p.bendDirection, + p.stretch, + this.data.scaleYMode, + p.softness, + p.mix + ); + break; + } + } + sort(skeleton) { + skeleton.sortBone(this.target); + const parent = this.bones[0].bone; + skeleton.sortBone(parent); + skeleton._updateCache.push(this); + parent.sorted = false; + skeleton.sortReset(parent.children); + skeleton.constrained(parent); + if (this.bones.length > 1) skeleton.constrained(this.bones[1].bone); + } + isSourceActive() { + return this.target.active; + } + static apply(skeleton, boneOrParent, targetXorChild, targetYOrTargetX, compressOrTargetY, stretchOrBendDir, scaleYModeOrStretch, mixOrScaleYMode, softness, mix) { + if (typeof targetXorChild === "number") + _IkConstraint.apply1(skeleton, boneOrParent, targetXorChild, targetYOrTargetX, compressOrTargetY, stretchOrBendDir, scaleYModeOrStretch, mixOrScaleYMode); + else + _IkConstraint.apply2( + skeleton, + boneOrParent, + targetXorChild, + targetYOrTargetX, + compressOrTargetY, + stretchOrBendDir, + scaleYModeOrStretch, + mixOrScaleYMode, + softness, + mix + ); + } + static apply1(skeleton, bone, targetX, targetY, compress, stretch, scaleYMode, mix) { + bone.modifyLocal(skeleton); + const p = bone.bone.parent.appliedPose; + let pa = p.a, pb = p.b, pc = p.c, pd = p.d; + let rotationIK = -bone.shearX - bone.rotation, tx = 0, ty = 0; + switch (bone.inherit) { + case 1 /* OnlyTranslation */: + tx = (targetX - bone.worldX) * MathUtils.signum(skeleton.scaleX); + ty = (targetY - bone.worldY) * MathUtils.signum(skeleton.scaleY); + break; + // biome-ignore lint/suspicious/noFallthroughSwitchClause: reference runtime + case 2 /* NoRotationOrReflection */: { + const s = Math.abs(pa * pd - pb * pc) / Math.max(MathUtils.epsilon, pa * pa + pc * pc); + const sa = pa / skeleton.scaleX; + const sc = pc / skeleton.scaleY; + pb = -sc * s * skeleton.scaleX; + pd = sa * s * skeleton.scaleY; + rotationIK += MathUtils.atan2Deg(sc, sa); + } + // Fall through + default: { + const x = targetX - p.worldX, y = targetY - p.worldY; + const d = pa * pd - pb * pc; + if (Math.abs(d) <= MathUtils.epsilon) { + tx = 0; + ty = 0; + } else { + tx = (x * pd - y * pb) / d - bone.x; + ty = (y * pa - x * pc) / d - bone.y; + } + } + } + rotationIK += MathUtils.atan2Deg(ty, tx); + if (bone.scaleX < 0) rotationIK += 180; + if (rotationIK > 180) + rotationIK -= 360; + else if (rotationIK <= -180) + rotationIK += 360; + bone.rotation += rotationIK * mix; + if (compress || stretch) { + switch (bone.inherit) { + case 3 /* NoScale */: + case 4 /* NoScaleOrReflection */: + tx = targetX - bone.worldX; + ty = targetY - bone.worldY; + } + const b = bone.bone.data.length * bone.scaleX; + if (b > MathUtils.epsilon) { + const dd = tx * tx + ty * ty; + if (compress && dd < b * b || stretch && dd > b * b) { + const s = (Math.sqrt(dd) / b - 1) * mix + 1; + bone.scaleX *= s; + switch (scaleYMode) { + case 1 /* Uniform */: + bone.scaleY *= s; + break; + case 2 /* Volume */: + bone.scaleY /= s < 0.7 ? 0.25 + 0.642857 * s : s; + } + } + } + } + } + /** Applies 2 bone IK. The target is specified in the world coordinate system. + * @param child A direct descendant of the parent bone. */ + static apply2(skeleton, parent, child, targetX, targetY, bendDir, stretch, scaleYMode, softness, mix) { + if (parent.inherit !== 0 /* Normal */ || child.inherit !== 0 /* Normal */) return; + parent.modifyLocal(skeleton); + child.modifyLocal(skeleton); + let px = parent.x, py = parent.y, psx = parent.scaleX, psy = parent.scaleY, csx = child.scaleX; + let os1 = 0, os2 = 0, s2 = 0; + if (psx < 0) { + psx = -psx; + os1 = 180; + s2 = -1; + } else { + os1 = 0; + s2 = 1; + } + if (psy < 0) { + psy = -psy; + s2 = -s2; + } + if (csx < 0) { + csx = -csx; + os2 = 180; + } else + os2 = 0; + let cwx = 0, cwy = 0, a = parent.a, b = parent.b, c = parent.c, d = parent.d; + const u = Math.abs(psx - psy) <= MathUtils.epsilon; + if (!u || stretch) { + child.y = 0; + cwx = a * child.x + parent.worldX; + cwy = c * child.x + parent.worldY; + } else { + cwx = a * child.x + b * child.y + parent.worldX; + cwy = c * child.x + d * child.y + parent.worldY; + } + const pp = parent.bone.parent.appliedPose; + a = pp.a; + b = pp.b; + c = pp.c; + d = pp.d; + let id = a * d - b * c, x = cwx - pp.worldX, y = cwy - pp.worldY; + id = Math.abs(id) <= MathUtils.epsilon ? 0 : 1 / id; + const dx = (x * d - y * b) * id - px, dy = (y * a - x * c) * id - py; + let l1 = Math.sqrt(dx * dx + dy * dy), l2 = child.bone.data.length * csx, a1, a2; + if (l1 < MathUtils.epsilon) { + _IkConstraint.apply(skeleton, parent, targetX, targetY, false, stretch, 0 /* None */, mix); + child.rotation = 0; + return; + } + x = targetX - pp.worldX; + y = targetY - pp.worldY; + let tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py; + let dd = tx * tx + ty * ty; + if (softness !== 0) { + softness *= psx * (csx + 1) * 0.5; + const td = Math.sqrt(dd), sd = td - l1 - l2 * psx + softness; + if (sd > 0) { + let p = Math.min(1, sd / (softness * 2)) - 1; + p = (sd - softness * (1 - p * p)) / td; + tx -= p * tx; + ty -= p * ty; + dd = tx * tx + ty * ty; + } + } + outer: + if (u) { + l2 *= psx; + let cos = (dd - l1 * l1 - l2 * l2) / (2 * l1 * l2); + if (cos < -1) { + cos = -1; + a2 = Math.PI * bendDir; + } else if (cos > 1) { + cos = 1; + a2 = 0; + if (stretch) { + a = (Math.sqrt(dd) / (l1 + l2) - 1) * mix + 1; + parent.scaleX *= a; + switch (scaleYMode) { + case 1 /* Uniform */: + parent.scaleY *= a; + break; + case 2 /* Volume */: + parent.scaleY /= a < 0.7 ? 0.25 + 0.642857 * a : a; + } + } + } else + a2 = Math.acos(cos) * bendDir; + a = l1 + l2 * cos; + b = l2 * Math.sin(a2); + a1 = Math.atan2(ty * a - tx * b, tx * a + ty * b); + } else { + a = psx * l2; + b = psy * l2; + const aa = a * a, bb = b * b, ta = Math.atan2(ty, tx); + c = bb * l1 * l1 + aa * dd - aa * bb; + const c1 = -2 * bb * l1, c2 = bb - aa; + d = c1 * c1 - 4 * c2 * c; + if (d >= 0) { + let q = Math.sqrt(d); + if (c1 < 0) q = -q; + q = -(c1 + q) * 0.5; + let r0 = q / c2, r1 = c / q; + const r = Math.abs(r0) < Math.abs(r1) ? r0 : r1; + r0 = dd - r * r; + if (r0 >= 0) { + y = Math.sqrt(r0) * bendDir; + a1 = ta - Math.atan2(y, r); + a2 = Math.atan2(y / psy, (r - l1) / psx); + break outer; + } + } + let minAngle = MathUtils.PI, minX = l1 - a, minDist = minX * minX, minY = 0; + let maxAngle = 0, maxX = l1 + a, maxDist = maxX * maxX, maxY = 0; + c = -a * l1 / (aa - bb); + if (c >= -1 && c <= 1) { + c = Math.acos(c); + x = a * Math.cos(c) + l1; + y = b * Math.sin(c); + d = x * x + y * y; + if (d < minDist) { + minAngle = c; + minDist = d; + minX = x; + minY = y; + } + if (d > maxDist) { + maxAngle = c; + maxDist = d; + maxX = x; + maxY = y; + } + } + if (dd <= (minDist + maxDist) * 0.5) { + a1 = ta - Math.atan2(minY * bendDir, minX); + a2 = minAngle * bendDir; + } else { + a1 = ta - Math.atan2(maxY * bendDir, maxX); + a2 = maxAngle * bendDir; + } + } + const os = Math.atan2(child.y, child.x) * s2; + a1 = (a1 - os) * MathUtils.radDeg + os1 - parent.rotation; + if (a1 > 180) + a1 -= 360; + else if (a1 <= -180) + a1 += 360; + parent.rotation += a1 * mix; + a2 = ((a2 + os) * MathUtils.radDeg - child.shearX) * s2 + os2 - child.rotation; + if (a2 > 180) + a2 -= 360; + else if (a2 <= -180) + a2 += 360; + child.rotation += a2 * mix; + } + }; + + // spine-core/src/IkConstraintData.ts + var IkConstraintData = class extends ConstraintData { + /** The bones that are constrained by this IK constraint. */ + bones = []; + _target = null; + /** The bone that is the IK target. */ + set target(boneData) { + this._target = boneData; + } + get target() { + if (!this._target) throw new Error("target cannot be null."); + return this._target; + } + /** Determines how the {@link BonePose.scaleY} changes when {@link IkConstraintPose.compress} or + * {@link IkConstraintPose.stretch} set {@link BonePose.scaleX}. */ + _scaleYMode = 0 /* None */; + set scaleYMode(scaleYMode) { + this._scaleYMode = scaleYMode; + } + get scaleYMode() { + if (this._scaleYMode == null) throw new Error("scaleYMode cannot be null."); + return this._scaleYMode; + } + constructor(name) { + super(name, new IkConstraintPose()); + } + create(skeleton) { + return new IkConstraint(this, skeleton); + } + }; + + // spine-core/src/PathConstraintPose.ts + var PathConstraintPose = class { + /** The position along the path. */ + position = 0; + /** The spacing between bones. */ + spacing = 0; + /** A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. */ + mixRotate = 0; + /** A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. */ + mixX = 0; + /** A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. */ + mixY = 0; + set(pose) { + this.position = pose.position; + this.spacing = pose.spacing; + this.mixRotate = pose.mixRotate; + this.mixX = pose.mixX; + this.mixY = pose.mixY; + } + }; + + // spine-core/src/PathConstraintData.ts + var PathConstraintData = class extends ConstraintData { + /** The bones that will be modified by this path constraint. */ + bones = []; + /** The slot whose path attachment will be used to constrained the bones. */ + set slot(slotData) { + this._slot = slotData; + } + get slot() { + if (!this._slot) throw new Error("SlotData not set."); + else return this._slot; + } + _slot = null; + /** The mode for positioning the first bone on the path. */ + positionMode = 0 /* Fixed */; + /** The mode for positioning the bones after the first bone on the path. */ + spacingMode = 1 /* Fixed */; + /** The mode for adjusting the rotation of the bones. */ + rotateMode = 1 /* Chain */; + /** An offset added to the constrained bone rotation. */ + offsetRotation = 0; + constructor(name) { + super(name, new PathConstraintPose()); + } + create(skeleton) { + return new PathConstraint(this, skeleton); + } + }; + var PositionMode = /* @__PURE__ */ ((PositionMode2) => { + PositionMode2[PositionMode2["Fixed"] = 0] = "Fixed"; + PositionMode2[PositionMode2["Percent"] = 1] = "Percent"; + return PositionMode2; + })(PositionMode || {}); + var SpacingMode = /* @__PURE__ */ ((SpacingMode2) => { + SpacingMode2[SpacingMode2["Length"] = 0] = "Length"; + SpacingMode2[SpacingMode2["Fixed"] = 1] = "Fixed"; + SpacingMode2[SpacingMode2["Percent"] = 2] = "Percent"; + SpacingMode2[SpacingMode2["Proportional"] = 3] = "Proportional"; + return SpacingMode2; + })(SpacingMode || {}); + var RotateMode = /* @__PURE__ */ ((RotateMode2) => { + RotateMode2[RotateMode2["Tangent"] = 0] = "Tangent"; + RotateMode2[RotateMode2["Chain"] = 1] = "Chain"; + RotateMode2[RotateMode2["ChainScale"] = 2] = "ChainScale"; + return RotateMode2; + })(RotateMode || {}); + + // spine-core/src/PathConstraint.ts + var PathConstraint = class _PathConstraint extends Constraint { + static NONE = -1; + static BEFORE = -2; + static AFTER = -3; + /** The path constraint's setup pose data. */ + data; + /** The bones that will be modified by this path constraint. */ + bones; + /** The slot whose path attachment will be used to constrained the bones. */ + slot; + spaces = []; + positions = []; + world = []; + curves = []; + lengths = []; + segments = []; + constructor(data, skeleton) { + super(data, new PathConstraintPose(), new PathConstraintPose()); + if (!skeleton) throw new Error("skeleton cannot be null."); + this.data = data; + this.bones = []; + for (const boneData of this.data.bones) + this.bones.push(skeleton.bones[boneData.index].constrainedPose); + this.slot = skeleton.slots[data.slot.index]; + } + copy(skeleton) { + var copy = new _PathConstraint(this.data, skeleton); + copy.pose.set(this.pose); + return copy; + } + update(skeleton, physics) { + const attachment = this.slot.appliedPose.attachment; + if (!(attachment instanceof PathAttachment)) return; + const p = this.appliedPose; + const mixRotate = p.mixRotate, mixX = p.mixX, mixY = p.mixY; + if (mixRotate === 0 && mixX === 0 && mixY === 0) return; + const data = this.data; + const tangents = data.rotateMode === 0 /* Tangent */, scale = data.rotateMode === 2 /* ChainScale */; + const bones = this.bones; + const boneCount = bones.length, spacesCount = tangents ? boneCount : boneCount + 1; + const spaces = Utils.setArraySize(this.spaces, spacesCount), lengths = scale ? this.lengths = Utils.setArraySize(this.lengths, boneCount) : []; + const spacing = p.spacing; + switch (data.spacingMode) { + case 2 /* Percent */: + if (scale) { + for (let i = 0, n = spacesCount - 1; i < n; i++) { + const bone = bones[i]; + const setupLength = bone.bone.data.length; + const x = setupLength * bone.a, y = setupLength * bone.c; + lengths[i] = Math.sqrt(x * x + y * y); + } + } + Utils.arrayFill(spaces, 1, spacesCount, spacing); + break; + case 3 /* Proportional */: { + let sum = 0; + for (let i = 0, n = spacesCount - 1; i < n; ) { + const bone = bones[i]; + const setupLength = bone.bone.data.length; + if (setupLength < MathUtils.epsilon) { + if (scale) lengths[i] = 0; + spaces[++i] = spacing; + } else { + const x = setupLength * bone.a, y = setupLength * bone.c; + const length = Math.sqrt(x * x + y * y); + if (scale) lengths[i] = length; + spaces[++i] = length; + sum += length; + } + } + if (sum > 0) { + sum = spacesCount / sum * spacing; + for (let i = 1; i < spacesCount; i++) + spaces[i] *= sum; + } + break; + } + default: { + const lengthSpacing = data.spacingMode === 0 /* Length */; + for (let i = 0, n = spacesCount - 1; i < n; ) { + const bone = bones[i]; + const setupLength = bone.bone.data.length; + if (setupLength < MathUtils.epsilon) { + if (scale) lengths[i] = 0; + spaces[++i] = spacing; + } else { + const x = setupLength * bone.a, y = setupLength * bone.c; + const length = Math.sqrt(x * x + y * y); + if (scale) lengths[i] = length; + spaces[++i] = (lengthSpacing ? Math.max(0, setupLength + spacing) : spacing) * length / setupLength; + } + } + } + } + const positions = this.computeWorldPositions(skeleton, attachment, spacesCount, tangents); + let boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation; + let tip = false; + if (offsetRotation === 0) + tip = data.rotateMode === 1 /* Chain */; + else { + tip = false; + const bone = this.slot.bone.appliedPose; + offsetRotation *= bone.a * bone.d - bone.b * bone.c > 0 ? MathUtils.degRad : -MathUtils.degRad; + } + for (let i = 0, ip = 3; i < boneCount; i++, ip += 3) { + const bone = bones[i]; + bone.modifyWorld(skeleton); + bone.worldX += (boneX - bone.worldX) * mixX; + bone.worldY += (boneY - bone.worldY) * mixY; + const x = positions[ip], y = positions[ip + 1], dx = x - boneX, dy = y - boneY; + if (scale) { + const length = lengths[i]; + if (length !== 0) { + const s = (Math.sqrt(dx * dx + dy * dy) / length - 1) * mixRotate + 1; + bone.a *= s; + bone.c *= s; + } + } + boneX = x; + boneY = y; + if (mixRotate > 0) { + let a = bone.a, b = bone.b, c = bone.c, d = bone.d, r = 0, cos = 0, sin = 0; + if (tangents) + r = positions[ip - 1]; + else if (spaces[i + 1] === 0) + r = positions[ip + 2]; + else + r = Math.atan2(dy, dx); + r -= Math.atan2(c, a); + if (tip) { + cos = Math.cos(r); + sin = Math.sin(r); + const length = bone.bone.data.length; + boneX += (length * (cos * a - sin * c) - dx) * mixRotate; + boneY += (length * (sin * a + cos * c) - dy) * mixRotate; + } else { + r += offsetRotation; + } + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) + r += MathUtils.PI2; + r *= mixRotate; + cos = Math.cos(r); + sin = Math.sin(r); + bone.a = cos * a - sin * c; + bone.b = cos * b - sin * d; + bone.c = sin * a + cos * c; + bone.d = sin * b + cos * d; + } + } + } + computeWorldPositions(skeleton, path, spacesCount, tangents) { + const slot = this.slot; + let position = this.appliedPose.position; + let spaces = this.spaces, out = Utils.setArraySize(this.positions, spacesCount * 3 + 2), world = this.world; + const closed = path.closed; + let verticesLength = path.worldVerticesLength, curveCount = verticesLength / 6, prevCurve = _PathConstraint.NONE; + if (!path.constantSpeed) { + const lengths = path.lengths; + curveCount -= closed ? 1 : 2; + const pathLength2 = lengths[curveCount]; + if (this.data.positionMode === 1 /* Percent */) position *= pathLength2; + let multiplier2; + switch (this.data.spacingMode) { + case 2 /* Percent */: + multiplier2 = pathLength2; + break; + case 3 /* Proportional */: + multiplier2 = pathLength2 / spacesCount; + break; + default: + multiplier2 = 1; + } + world = Utils.setArraySize(this.world, 8); + for (let i = 0, o = 0, curve = 0; i < spacesCount; i++, o += 3) { + const space = spaces[i] * multiplier2; + position += space; + let p = position; + if (closed) { + p %= pathLength2; + if (p < 0) p += pathLength2; + curve = 0; + } else if (p < 0) { + if (prevCurve !== _PathConstraint.BEFORE) { + prevCurve = _PathConstraint.BEFORE; + path.computeWorldVertices(skeleton, slot, 2, 4, world, 0, 2); + } + this.addBeforePosition(p, world, 0, out, o); + continue; + } else if (p > pathLength2) { + if (prevCurve !== _PathConstraint.AFTER) { + prevCurve = _PathConstraint.AFTER; + path.computeWorldVertices(skeleton, slot, verticesLength - 6, 4, world, 0, 2); + } + this.addAfterPosition(p - pathLength2, world, 0, out, o); + continue; + } + for (; ; curve++) { + const length = lengths[curve]; + if (p > length) continue; + if (curve === 0) + p /= length; + else { + const prev = lengths[curve - 1]; + p = (p - prev) / (length - prev); + } + break; + } + if (curve !== prevCurve) { + prevCurve = curve; + if (closed && curve === curveCount) { + path.computeWorldVertices(skeleton, slot, verticesLength - 4, 4, world, 0, 2); + path.computeWorldVertices(skeleton, slot, 0, 4, world, 4, 2); + } else + path.computeWorldVertices(skeleton, slot, curve * 6 + 2, 8, world, 0, 2); + } + this.addCurvePosition( + p, + world[0], + world[1], + world[2], + world[3], + world[4], + world[5], + world[6], + world[7], + out, + o, + tangents || i > 0 && space === 0 + ); + } + return out; + } + if (closed) { + verticesLength += 2; + world = Utils.setArraySize(this.world, verticesLength); + path.computeWorldVertices(skeleton, slot, 2, verticesLength - 4, world, 0, 2); + path.computeWorldVertices(skeleton, slot, 0, 2, world, verticesLength - 4, 2); + world[verticesLength - 2] = world[0]; + world[verticesLength - 1] = world[1]; + } else { + curveCount--; + verticesLength -= 4; + world = Utils.setArraySize(this.world, verticesLength); + path.computeWorldVertices(skeleton, slot, 2, verticesLength, world, 0, 2); + } + const curves = Utils.setArraySize(this.curves, curveCount); + let pathLength = 0; + let x1 = world[0], y1 = world[1], cx1 = 0, cy1 = 0, cx2 = 0, cy2 = 0, x2 = 0, y2 = 0; + let tmpx = 0, tmpy = 0, dddfx = 0, dddfy = 0, ddfx = 0, ddfy = 0, dfx = 0, dfy = 0; + for (let i = 0, w = 2; i < curveCount; i++, w += 6) { + cx1 = world[w]; + cy1 = world[w + 1]; + cx2 = world[w + 2]; + cy2 = world[w + 3]; + x2 = world[w + 4]; + y2 = world[w + 5]; + tmpx = (x1 - cx1 * 2 + cx2) * 0.1875; + tmpy = (y1 - cy1 * 2 + cy2) * 0.1875; + dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.09375; + dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.09375; + ddfx = tmpx * 2 + dddfx; + ddfy = tmpy * 2 + dddfy; + dfx = (cx1 - x1) * 0.75 + tmpx + dddfx * 0.16666667; + dfy = (cy1 - y1) * 0.75 + tmpy + dddfy * 0.16666667; + pathLength += Math.sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + pathLength += Math.sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx; + dfy += ddfy; + pathLength += Math.sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx + dddfx; + dfy += ddfy + dddfy; + pathLength += Math.sqrt(dfx * dfx + dfy * dfy); + curves[i] = pathLength; + x1 = x2; + y1 = y2; + } + if (this.data.positionMode === 1 /* Percent */) position *= pathLength; + let multiplier; + switch (this.data.spacingMode) { + case 2 /* Percent */: + multiplier = pathLength; + break; + case 3 /* Proportional */: + multiplier = pathLength / spacesCount; + break; + default: + multiplier = 1; + } + const segments = this.segments; + let curveLength = 0; + for (let i = 0, o = 0, curve = 0, segment = 0; i < spacesCount; i++, o += 3) { + const space = spaces[i] * multiplier; + position += space; + let p = position; + if (closed) { + p %= pathLength; + if (p < 0) p += pathLength; + curve = 0; + segment = 0; + } else if (p < 0) { + this.addBeforePosition(p, world, 0, out, o); + continue; + } else if (p > pathLength) { + this.addAfterPosition(p - pathLength, world, verticesLength - 4, out, o); + continue; + } + for (; ; curve++) { + const length = curves[curve]; + if (p > length) continue; + if (curve === 0) + p /= length; + else { + const prev = curves[curve - 1]; + p = (p - prev) / (length - prev); + } + break; + } + if (curve !== prevCurve) { + prevCurve = curve; + let ii = curve * 6; + x1 = world[ii]; + y1 = world[ii + 1]; + cx1 = world[ii + 2]; + cy1 = world[ii + 3]; + cx2 = world[ii + 4]; + cy2 = world[ii + 5]; + x2 = world[ii + 6]; + y2 = world[ii + 7]; + tmpx = (x1 - cx1 * 2 + cx2) * 0.03; + tmpy = (y1 - cy1 * 2 + cy2) * 0.03; + dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 6e-3; + dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 6e-3; + ddfx = tmpx * 2 + dddfx; + ddfy = tmpy * 2 + dddfy; + dfx = (cx1 - x1) * 0.3 + tmpx + dddfx * 0.16666667; + dfy = (cy1 - y1) * 0.3 + tmpy + dddfy * 0.16666667; + curveLength = Math.sqrt(dfx * dfx + dfy * dfy); + segments[0] = curveLength; + for (ii = 1; ii < 8; ii++) { + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + curveLength += Math.sqrt(dfx * dfx + dfy * dfy); + segments[ii] = curveLength; + } + dfx += ddfx; + dfy += ddfy; + curveLength += Math.sqrt(dfx * dfx + dfy * dfy); + segments[8] = curveLength; + dfx += ddfx + dddfx; + dfy += ddfy + dddfy; + curveLength += Math.sqrt(dfx * dfx + dfy * dfy); + segments[9] = curveLength; + segment = 0; + } + p *= curveLength; + for (; ; segment++) { + const length = segments[segment]; + if (p > length) continue; + if (segment === 0) + p /= length; + else { + const prev = segments[segment - 1]; + p = segment + (p - prev) / (length - prev); + } + break; + } + this.addCurvePosition(p * 0.1, x1, y1, cx1, cy1, cx2, cy2, x2, y2, out, o, tangents || i > 0 && space === 0); + } + return out; + } + addBeforePosition(p, temp, i, out, o) { + const x1 = temp[i], y1 = temp[i + 1], dx = temp[i + 2] - x1, dy = temp[i + 3] - y1, r = Math.atan2(dy, dx); + out[o] = x1 + p * Math.cos(r); + out[o + 1] = y1 + p * Math.sin(r); + out[o + 2] = r; + } + addAfterPosition(p, temp, i, out, o) { + const x1 = temp[i + 2], y1 = temp[i + 3], dx = x1 - temp[i], dy = y1 - temp[i + 1], r = Math.atan2(dy, dx); + out[o] = x1 + p * Math.cos(r); + out[o + 1] = y1 + p * Math.sin(r); + out[o + 2] = r; + } + addCurvePosition(p, x1, y1, cx1, cy1, cx2, cy2, x2, y2, out, o, tangents) { + if (p === 0 || Number.isNaN(p)) { + out[o] = x1; + out[o + 1] = y1; + out[o + 2] = Math.atan2(cy1 - y1, cx1 - x1); + return; + } + const tt = p * p, ttt = tt * p, u = 1 - p, uu = u * u, uuu = uu * u; + const ut = u * p, ut3 = ut * 3, uut3 = u * ut3, utt3 = ut3 * p; + const x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt, y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt; + out[o] = x; + out[o + 1] = y; + if (tangents) { + if (p < 1e-3) + out[o + 2] = Math.atan2(cy1 - y1, cx1 - x1); + else + out[o + 2] = Math.atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt)); + } + } + sort(skeleton) { + const slotIndex = this.slot.data.index; + const slotBone = this.slot.bone; + if (skeleton.skin != null) this.sortPathSlot(skeleton, skeleton.skin, slotIndex, slotBone); + if (skeleton.data.defaultSkin != null && skeleton.data.defaultSkin !== skeleton.skin) + this.sortPathSlot(skeleton, skeleton.data.defaultSkin, slotIndex, slotBone); + this.sortPath(skeleton, this.slot.pose.attachment, slotBone); + const bones = this.bones; + const boneCount = this.bones.length; + for (let i = 0; i < boneCount; i++) { + const bone = bones[i].bone; + skeleton.sortBone(bone); + skeleton.constrained(bone); + } + skeleton._updateCache.push(this); + for (let i = 0; i < boneCount; i++) + skeleton.sortReset(bones[i].bone.children); + for (let i = 0; i < boneCount; i++) + bones[i].bone.sorted = true; + } + sortPathSlot(skeleton, skin, slotIndex, slotBone) { + const entries = skin.getAttachments(); + for (let i = 0, n = entries.length; i < n; i++) { + const entry = entries[i]; + if (entry.slotIndex === slotIndex) this.sortPath(skeleton, entry.attachment, slotBone); + } + } + sortPath(skeleton, attachment, slotBone) { + if (!(attachment instanceof PathAttachment)) return; + const pathBones = attachment.bones; + if (pathBones == null) + skeleton.sortBone(slotBone); + else { + const bones = skeleton.bones; + for (let i = 0, n = pathBones.length; i < n; ) { + let nn = pathBones[i++]; + nn += i; + while (i < nn) + skeleton.sortBone(bones[pathBones[i++]]); + } + } + } + isSourceActive() { + return this.slot.bone.active; + } + }; + + // spine-core/src/Physics.ts + var Physics = /* @__PURE__ */ ((Physics2) => { + Physics2[Physics2["none"] = 0] = "none"; + Physics2[Physics2["reset"] = 1] = "reset"; + Physics2[Physics2["update"] = 2] = "update"; + Physics2[Physics2["pose"] = 3] = "pose"; + return Physics2; + })(Physics || {}); + + // spine-core/src/PhysicsConstraintPose.ts + var PhysicsConstraintPose = class { + /** Controls how much bone movement is converted into physics movement. */ + inertia = 0; + /** The amount of force used to return properties to the unconstrained value. */ + strength = 0; + /** Reduces the speed of physics movements, with more of a reduction at higher speeds. */ + damping = 0; + /** Determines susceptibility to acceleration. */ + massInverse = 0; + /** Applies a constant force along the {@link Skeleton.windX}, {@link Skeleton.windY} vector. */ + wind = 0; + /** Applies a constant force along the {@link Skeleton.gravityX}, {@link Skeleton.gravityY} vector. */ + gravity = 0; + /** A percentage (0+) that controls the mix between the constrained and unconstrained poses. */ + mix = 0; + set(pose) { + this.inertia = pose.inertia; + this.strength = pose.strength; + this.damping = pose.damping; + this.massInverse = pose.massInverse; + this.wind = pose.wind; + this.gravity = pose.gravity; + this.mix = pose.mix; + } + }; + + // spine-core/src/SlotPose.ts + var SlotPose = class { + /** The color used to tint the slot's attachment. If {@link darkColor} is set, this is used as the light color for two color + * tinting. */ + color = new Color(1, 1, 1, 1); + /** The dark color used to tint the slot's attachment for two color tinting, or null if two color tinting is not used. The dark + * color's alpha is not used. */ + darkColor = null; + /** The current attachment for the slot, or null if the slot has no attachment. */ + attachment = null; + // Not used in setup pose. + /** The index of the texture region to display when the slot's attachment has a {@link Sequence}. -1 represents the + * {@link Sequence.getSetupIndex}. */ + sequenceIndex = 0; + /** Values to deform the slot's attachment. For an unweighted mesh, the entries are local positions for each vertex. For a + * weighted mesh, the entries are an offset for each vertex which will be added to the mesh's local vertex positions. + * + * See {@link VertexAttachment.computeWorldVertices} and + * {@link DeformTimeline}. */ + deform = []; + SlotPose() { + } + set(pose) { + if (pose == null) throw new Error("pose cannot be null."); + this.color.setFromColor(pose.color); + if (this.darkColor != null && pose.darkColor != null) this.darkColor.setFromColor(pose.darkColor); + this.attachment = pose.attachment; + this.sequenceIndex = pose.sequenceIndex; + this.deform.length = 0; + this.deform.push(...pose.deform); + } + /** The current attachment for the slot, or null if the slot has no attachment. */ + getAttachment() { + return this.attachment; + } + /** Sets the slot's attachment and, if the attachment changed, resets {@link sequenceIndex} and clears the {@link deform}. + * The deform is not cleared if the old attachment has the same {@link VertexAttachment.getTimelineAttachment} as the + * specified attachment. */ + setAttachment(attachment) { + if (this.attachment === attachment) return; + if (!(attachment instanceof VertexAttachment) || !(this.attachment instanceof VertexAttachment) || attachment.timelineAttachment !== this.attachment.timelineAttachment) { + this.deform.length = 0; + } + this.attachment = attachment; + this.sequenceIndex = -1; + } + }; + + // spine-core/src/Slot.ts + var Slot = class _Slot extends Posed { + skeleton; + /** The bone this slot belongs to. */ + bone; + attachmentState = 0; + constructor(data, skeleton) { + super(data, new SlotPose(), new SlotPose()); + if (!skeleton) throw new Error("skeleton cannot be null."); + this.skeleton = skeleton; + this.bone = skeleton.bones[data.boneData.index]; + if (data.setupPose.darkColor != null) { + this.pose.darkColor = new Color(); + this.constrainedPose.darkColor = new Color(); + } + this.setupPose(); + } + /** Copy constructor. */ + copy(slot, bone, skeleton) { + const copy = new _Slot(slot.data, this.skeleton); + if (this.data.setupPose.darkColor != null) { + copy.pose.darkColor = new Color(); + copy.constrainedPose.darkColor = new Color(); + } + copy.pose.set(slot.pose); + return copy; + } + setupPose() { + this.pose.color.setFromColor(this.data.setupPose.color); + if (this.pose.darkColor) this.pose.darkColor.setFromColor(this.data.setupPose.darkColor); + this.pose.sequenceIndex = this.data.setupPose.sequenceIndex; + if (!this.data.attachmentName) + this.pose.setAttachment(null); + else { + this.pose.attachment = null; + this.pose.setAttachment(this.skeleton.getAttachment(this.data.index, this.data.attachmentName)); + } + } + }; + + // spine-core/src/Skeleton.ts + var Skeleton = class _Skeleton { + static quadTriangles = [0, 1, 2, 2, 3, 0]; + static yDown = false; + static get yDir() { + return _Skeleton.yDown ? -1 : 1; + } + /** The skeleton's setup pose data. */ + data; + /** The skeleton's bones, sorted parent first. The root bone is always the first bone. */ + bones; + /** The skeleton's slots. To add a slot, also add it to {@link DrawOrder.pose}. */ + slots; + /** The skeleton's draw order. Use {@link DrawOrder.appliedPose} for rendering and {@link DrawOrder.pose} for changing the draw + * order. */ + drawOrder; + /** The skeleton's constraints. */ + // biome-ignore lint/suspicious/noExplicitAny: reference runtime does not restrict to specific types + constraints; + /** The skeleton's physics constraints. */ + physics; + /** The list of bones and constraints, sorted in the order they should be updated, as computed by {@link updateCache}. */ + // biome-ignore lint/suspicious/noExplicitAny: reference runtime does not restrict to specific types + _updateCache = []; + // biome-ignore lint/suspicious/noExplicitAny: reference runtime does not restrict to specific types + resetCache = []; + /** The skeleton's current skin. May be null. */ + skin = null; + /** The color to tint all the skeleton's attachments. */ + color; + /** Scales the entire skeleton on the X axis. + * + * Bones that do not inherit scale are still affected by this property. */ + scaleX = 1; + _scaleY = 1; + /** Scales the entire skeleton on the Y axis. + * + * Bones that do not inherit scale are still affected by this property. */ + get scaleY() { + return this._scaleY * _Skeleton.yDir; + } + set scaleY(scaleY) { + this._scaleY = scaleY; + } + /** Sets the skeleton X position, which is added to the root bone worldX position. + * + * Bones that do not inherit translation are still affected by this property. */ + x = 0; + /** Sets the skeleton Y position, which is added to the root bone worldY position. + * + * Bones that do not inherit translation are still affected by this property. */ + y = 0; + /** Returns the skeleton's time, is used for time-based manipulations, such as {@link PhysicsConstraint}. + * + * See {@link _update}. */ + time = 0; + /** The x component of a vector that defines the direction {@link PhysicsConstraintPose.wind} is applied. */ + windX = 1; + /** The y component of a vector that defines the direction {@link PhysicsConstraintPose.wind} is applied. */ + windY = 0; + /** The x component of a vector that defines the direction {@link PhysicsConstraintPose.gravity} is applied. */ + gravityX = 0; + /** The y component of a vector that defines the direction {@link PhysicsConstraintPose.gravity} is applied. */ + gravityY = 1; + _update = 0; + constructor(data) { + if (!data) throw new Error("data cannot be null."); + this.data = data; + this.bones = []; + for (let i = 0; i < data.bones.length; i++) { + const boneData = data.bones[i]; + let bone; + if (!boneData.parent) + bone = new Bone(boneData, null); + else { + const parent = this.bones[boneData.parent.index]; + bone = new Bone(boneData, parent); + parent.children.push(bone); + } + this.bones.push(bone); + } + this.slots = []; + for (const slotData of this.data.slots) + this.slots.push(new Slot(slotData, this)); + this.drawOrder = new DrawOrder(this.slots); + this.physics = []; + this.constraints = []; + for (const constraintData of this.data.constraints) { + const constraint = constraintData.create(this); + if (constraint instanceof PhysicsConstraint) this.physics.push(constraint); + this.constraints.push(constraint); + } + this.color = new Color(1, 1, 1, 1); + this.updateCache(); + } + /** Caches information about bones and constraints. Must be called if the {@link skin} is modified or if bones, constraints, + * or weighted path attachments are added or removed. */ + updateCache() { + this._updateCache.length = 0; + this.resetCache.length = 0; + this.drawOrder.unconstrained(); + const slots = this.slots; + for (let i = 0, n2 = slots.length; i < n2; i++) + slots[i].unconstrained(); + const bones = this.bones; + const boneCount = bones.length; + for (let i = 0, n2 = boneCount; i < n2; i++) { + const bone = bones[i]; + bone.sorted = bone.data.skinRequired; + bone.active = !bone.sorted; + bone.unconstrained(); + } + if (this.skin) { + const skinBones = this.skin.bones; + for (let i = 0, n2 = this.skin.bones.length; i < n2; i++) { + let bone = this.bones[skinBones[i].index]; + do { + bone.sorted = false; + bone.active = true; + bone = bone.parent; + } while (bone); + } + } + const constraints = this.constraints; + let n = this.constraints.length; + for (let i = 0; i < n; i++) + constraints[i].unconstrained(); + for (let i = 0; i < n; i++) { + const constraint = constraints[i]; + constraint.active = constraint.isSourceActive() && (!constraint.data.skinRequired || this.skin != null && this.skin.constraints.includes(constraint.data)); + if (constraint.active) constraint.sort(this); + } + for (let i = 0; i < boneCount; i++) + this.sortBone(bones[i]); + n = this._updateCache.length; + for (let i = 0; i < n; i++) { + const updateable = this._updateCache[i]; + if (updateable instanceof Bone) this._updateCache[i] = updateable.appliedPose; + } + } + // biome-ignore lint/suspicious/noExplicitAny: reference runtime does not restrict to specific types + constrained(object) { + if (object.pose === object.appliedPose) { + object.constrained(); + this.resetCache.push(object); + } + } + sortBone(bone) { + if (bone.sorted || !bone.active) return; + const parent = bone.parent; + if (parent) this.sortBone(parent); + bone.sorted = true; + this._updateCache.push(bone); + } + sortReset(bones) { + for (let i = 0, n = bones.length; i < n; i++) { + const bone = bones[i]; + if (bone.active) { + if (bone.sorted) this.sortReset(bone.children); + bone.sorted = false; + } + } + } + /** Updates the world transform for each bone and applies all constraints. + * + * See World transforms in the Spine + * Runtimes Guide. */ + updateWorldTransform(physics) { + this._update++; + if (this.drawOrder.appliedPose === this.drawOrder.constrainedPose) this.drawOrder.resetConstrained(); + const resetCache = this.resetCache; + for (let i = 0, n = this.resetCache.length; i < n; i++) + resetCache[i].resetConstrained(); + const updateCache = this._updateCache; + for (let i = 0, n = this._updateCache.length; i < n; i++) + updateCache[i].update(this, physics); + } + /** Sets the bones, constraints, and slots to their setup pose values. */ + setupPose() { + this.setupPoseBones(); + this.setupPoseSlots(); + } + /** Sets the bones and constraints to their setup pose values. */ + setupPoseBones() { + const bones = this.bones; + for (let i = 0, n = bones.length; i < n; i++) + bones[i].setupPose(); + const constraints = this.constraints; + for (let i = 0, n = constraints.length; i < n; i++) + constraints[i].setupPose(); + } + /** Sets the slots and draw order to their setup pose values. */ + setupPoseSlots() { + this.drawOrder.setupPose(); + const slots = this.slots; + for (let i = 0, n = slots.length; i < n; i++) + slots[i].setupPose(); + } + /** Returns the root bone, or null if the skeleton has no bones. */ + getRootBone() { + if (this.bones.length === 0) return null; + return this.bones[0]; + } + /** Finds a bone by comparing each bone's name. It is more efficient to cache the results of this method than to call it + * repeatedly. */ + findBone(boneName) { + if (!boneName) throw new Error("boneName cannot be null."); + const bones = this.bones; + for (let i = 0, n = bones.length; i < n; i++) + if (bones[i].data.name === boneName) return bones[i]; + return null; + } + /** Finds a slot by comparing each slot's name. It is more efficient to cache the results of this method than to call it + * repeatedly. */ + findSlot(slotName) { + if (!slotName) throw new Error("slotName cannot be null."); + const slots = this.slots; + for (let i = 0, n = slots.length; i < n; i++) + if (slots[i].data.name === slotName) return slots[i]; + return null; + } + setSkin(newSkin) { + if (typeof newSkin === "string") + this.setSkinByName(newSkin); + else + this.setSkinBySkin(newSkin); + } + setSkinByName(skinName) { + const skin = this.data.findSkin(skinName); + if (!skin) throw new Error(`Skin not found: ${skinName}`); + this.setSkin(skin); + } + setSkinBySkin(newSkin) { + if (newSkin === this.skin) return; + if (newSkin) { + if (this.skin) + newSkin.attachAll(this, this.skin); + else { + const slots = this.slots; + for (let i = 0, n = slots.length; i < n; i++) { + const slot = slots[i]; + const name = slot.data.attachmentName; + if (name) { + const attachment = newSkin.getAttachment(i, name); + if (attachment) slot.pose.setAttachment(attachment); + } + } + } + } + this.skin = newSkin; + this.updateCache(); + } + getAttachment(slotNameOrIndex, placeholder) { + if (typeof slotNameOrIndex === "string") + return this.getAttachmentByName(slotNameOrIndex, placeholder); + return this.getAttachmentByIndex(slotNameOrIndex, placeholder); + } + /** Finds an attachment by looking in the {@link skin} and {@link SkeletonData.defaultSkin} using the slot name and attachment + * name. + * + * See {@link getAttachment}. + * @returns May be null. */ + getAttachmentByName(slotName, placeholder) { + const slot = this.data.findSlot(slotName); + if (!slot) throw new Error(`Can't find slot with name ${slotName}`); + return this.getAttachment(slot.index, placeholder); + } + /** Finds an attachment by looking in the {@link skin} and {@link SkeletonData.defaultSkin} using the slot index and + * attachment name. First the skin is checked and if the attachment was not found, the default skin is checked. + * + * See [Runtime skins](http://esotericsoftware.com/spine-runtime-skins) in the Spine Runtimes Guide. + * @returns May be null. */ + getAttachmentByIndex(slotIndex, placeholder) { + if (!placeholder) throw new Error("placeholder cannot be null."); + if (this.skin) { + const attachment = this.skin.getAttachment(slotIndex, placeholder); + if (attachment) return attachment; + } + if (this.data.defaultSkin) return this.data.defaultSkin.getAttachment(slotIndex, placeholder); + return null; + } + /** A convenience method to set an attachment by finding the slot with {@link findSlot}, finding the attachment with + * {@link getAttachment}, then setting the slot's {@link Slot.attachment}. + * @param placeholder May be null to clear the slot's attachment. */ + setAttachment(slotName, placeholder) { + if (!slotName) throw new Error("slotName cannot be null."); + const slot = this.findSlot(slotName); + if (!slot) throw new Error(`Slot not found: ${slotName}`); + let attachment = null; + if (placeholder) { + attachment = this.getAttachment(slot.data.index, placeholder); + if (!attachment) + throw new Error(`Attachment not found: ${placeholder}, for slot: ${slotName}`); + } + slot.pose.setAttachment(attachment); + } + /** Finds a constraint of the specified type by comparing each constraints's name. It is more efficient to cache the results of + * this method than to call it multiple times. */ + // biome-ignore lint/suspicious/noExplicitAny: reference runtime does not restrict to specific types + findConstraint(constraintName, type) { + if (constraintName == null) throw new Error("constraintName cannot be null."); + if (type == null) throw new Error("type cannot be null."); + const constraints = this.constraints; + for (let i = 0, n = constraints.length; i < n; i++) { + const constraint = constraints[i]; + if (constraint instanceof type && constraint.data.name === constraintName) return constraint; + } + return null; + } + /** Returns the axis aligned bounding box (AABB) of the region and mesh attachments for the applied pose. + * @param offset An output value, the distance from the skeleton origin to the bottom left corner of the AABB. + * @param size An output value, the width and height of the AABB. + * @param temp Working memory to temporarily store attachments' computed world vertices. */ + getBoundsRect(clipper) { + const offset = new Vector2(); + const size = new Vector2(); + this.getBounds(offset, size, void 0, clipper); + return { x: offset.x, y: offset.y, width: size.x, height: size.y }; + } + /** Returns the axis aligned bounding box (AABB) of the region and mesh attachments for the applied pose. Optionally applies + * clipping. + * @param offset An output value, the distance from the skeleton origin to the bottom left corner of the AABB. + * @param size An output value, the width and height of the AABB. + * @param temp Working memory to temporarily store attachments' computed world vertices. + * @param clipper {@link SkeletonClipping} to use. If `null`, no clipping is applied. */ + getBounds(offset, size, temp = new Array(2), clipper = null) { + if (!offset) throw new Error("offset cannot be null."); + if (!size) throw new Error("size cannot be null."); + const drawOrder = this.drawOrder.appliedPose; + const slots = drawOrder; + let minX = Number.POSITIVE_INFINITY, minY = Number.POSITIVE_INFINITY, maxX = Number.NEGATIVE_INFINITY, maxY = Number.NEGATIVE_INFINITY; + for (let i = 0, n = drawOrder.length; i < n; i++) { + const slot = slots[i]; + if (!slot.bone.active) continue; + let verticesLength = 0; + let vertices = null; + let triangles = null; + const attachment = slot.appliedPose.attachment; + if (attachment) { + if (attachment instanceof RegionAttachment) { + verticesLength = 8; + vertices = Utils.setArraySize(temp, verticesLength, 0); + attachment.computeWorldVertices(slot, attachment.getOffsets(slot.appliedPose), vertices, 0, 2); + triangles = _Skeleton.quadTriangles; + } else if (attachment instanceof MeshAttachment) { + verticesLength = attachment.worldVerticesLength; + vertices = Utils.setArraySize(temp, verticesLength, 0); + attachment.computeWorldVertices(this, slot, 0, verticesLength, vertices, 0, 2); + triangles = attachment.triangles; + } else if (attachment instanceof ClippingAttachment && clipper) { + clipper.clipEnd(slot); + clipper.clipStart(this, slot, attachment); + continue; + } + if (vertices && triangles) { + if (clipper?.isClipping() && clipper.clipTriangles(vertices, triangles, triangles.length)) { + vertices = clipper.clippedVertices; + verticesLength = clipper.clippedVertices.length; + } + for (let ii = 0, nn = vertices.length; ii < nn; ii += 2) { + const x = vertices[ii], y = vertices[ii + 1]; + minX = Math.min(minX, x); + minY = Math.min(minY, y); + maxX = Math.max(maxX, x); + maxY = Math.max(maxY, y); + } + } + } + if (clipper) clipper.clipEnd(slot); + } + if (clipper) clipper.clipEnd(); + offset.set(minX, minY); + size.set(maxX - minX, maxY - minY); + } + /** Scales the entire skeleton on the X and Y axes. + * + * Bones that do not inherit scale are still affected by this property. */ + setScale(scaleX, scaleY) { + this.scaleX = scaleX; + this.scaleY = scaleY; + } + /** Sets the skeleton X and Y position, which is added to the root bone worldX and worldY position. + * + * Bones that do not inherit translation are still affected by this property. */ + setPosition(x, y) { + this.x = x; + this.y = y; + } + /** Increments the skeleton's {@link time}. */ + update(delta) { + this.time += delta; + } + /** Calls {@link PhysicsConstraint.translate} for each physics constraint. */ + physicsTranslate(x, y) { + const constraints = this.physics; + for (let i = 0, n = constraints.length; i < n; i++) + constraints[i].translate(x, y); + } + /** Calls {@link PhysicsConstraint.rotate} for each physics constraint. */ + physicsRotate(x, y, degrees) { + const constraints = this.physics; + for (let i = 0, n = constraints.length; i < n; i++) + constraints[i].rotate(x, y, degrees); + } + }; + + // spine-core/src/PhysicsConstraint.ts + var PhysicsConstraint = class _PhysicsConstraint extends Constraint { + bone; + _reset = true; + ux = 0; + uy = 0; + cx = 0; + cy = 0; + tx = 0; + ty = 0; + xOffset = 0; + xLag = 0; + xVelocity = 0; + yOffset = 0; + yLag = 0; + yVelocity = 0; + rotateOffset = 0; + rotateLag = 0; + rotateVelocity = 0; + scaleOffset = 0; + scaleLag = 0; + scaleVelocity = 0; + remaining = 0; + lastTime = 0; + constructor(data, skeleton) { + super(data, new PhysicsConstraintPose(), new PhysicsConstraintPose()); + if (skeleton == null) throw new Error("skeleton cannot be null."); + this.bone = skeleton.bones[data.bone.index].constrainedPose; + } + copy(skeleton) { + var copy = new _PhysicsConstraint(this.data, skeleton); + copy.pose.set(this.pose); + return copy; + } + /** Resets all physics state that was the result of previous movement. Use this after moving a bone to prevent physics from + * reacting to the movement. */ + reset(skeleton) { + this.remaining = 0; + this.lastTime = skeleton.time; + this._reset = true; + this.xOffset = 0; + this.xLag = 0; + this.xVelocity = 0; + this.yOffset = 0; + this.yLag = 0; + this.yVelocity = 0; + this.rotateOffset = 0; + this.rotateLag = 0; + this.rotateVelocity = 0; + this.scaleOffset = 0; + this.scaleLag = 0; + this.scaleVelocity = 0; + } + /** Translates the physics constraint so the next {@link update} forces are applied as if the bone moved an + * additional amount in world space. */ + translate(x, y) { + this.ux -= x; + this.uy -= y; + this.cx -= x; + this.cy -= y; + } + /** Rotates the physics constraint so the next {@link update} forces are applied as if the bone rotated + * around the specified point in world space. */ + rotate(x, y, degrees) { + const r = degrees * MathUtils.degRad, cos = Math.cos(r), sin = Math.sin(r); + const dx = this.cx - x, dy = this.cy - y; + this.translate(dx * cos - dy * sin - dx, dx * sin + dy * cos - dy); + } + /** Applies the constraint to the constrained bones. */ + update(skeleton, physics) { + const p = this.appliedPose; + const mix = p.mix; + if (mix === 0) return; + const x = this.data.x > 0, y = this.data.y > 0, rotateOrShearX = this.data.rotate > 0 || this.data.shearX > 0, scaleX = this.data.scaleX > 0; + const bone = this.bone; + let l = bone.bone.data.length, t = this.data.step, z = 0; + if (physics === 0 /* none */) return; + bone.modifyWorld(skeleton); + switch (physics) { + // biome-ignore lint/suspicious/noFallthroughSwitchClause: fall through expected + case 1 /* reset */: + this.reset(skeleton); + // Fall through. + case 2 /* update */: { + const delta = Math.max(skeleton.time - this.lastTime, 0), aa = this.remaining; + this.remaining += delta; + this.lastTime = skeleton.time; + const bx = bone.worldX, by = bone.worldY; + if (this._reset) { + this._reset = false; + this.ux = bx; + this.uy = by; + } else { + let a = this.remaining, i = p.inertia, f = skeleton.data.referenceScale, d = -1, m = 0, e = 0, qx = this.data.limit * delta, qy = qx * Math.abs(skeleton.scaleY); + qx *= Math.abs(skeleton.scaleX); + if (x || y) { + if (x) { + const u = (this.ux - bx) * i; + this.xOffset += u > qx ? qx : u < -qx ? -qx : u; + this.ux = bx; + } + if (y) { + const u = (this.uy - by) * i; + this.yOffset += u > qy ? qy : u < -qy ? -qy : u; + this.uy = by; + } + if (a >= t) { + const xs = this.xOffset, ys = this.yOffset; + d = p.damping ** (60 * t); + m = t * p.massInverse; + e = p.strength; + const w = f * p.wind, g = f * p.gravity; + const ax = (w * skeleton.windX + g * skeleton.gravityX) * skeleton.scaleX; + const ay = (w * skeleton.windY + g * skeleton.gravityY) * skeleton.scaleY; + do { + if (x) { + this.xVelocity += (ax - this.xOffset * e) * m; + this.xOffset += this.xVelocity * t; + this.xVelocity *= d; + } + if (y) { + this.yVelocity -= (ay + this.yOffset * e) * m; + this.yOffset += this.yVelocity * t; + this.yVelocity *= d; + } + a -= t; + } while (a >= t); + this.xLag = this.xOffset - xs; + this.yLag = this.yOffset - ys; + } + z = Math.max(0, 1 - a / t); + if (x) bone.worldX += (this.xOffset - this.xLag * z) * mix * this.data.x; + if (y) bone.worldY += (this.yOffset - this.yLag * z) * mix * this.data.y; + } + if (rotateOrShearX || scaleX) { + let ca = Math.atan2(bone.c, bone.a), c = 0, s = 0, mr = 0, dx = this.cx - bone.worldX, dy = this.cy - bone.worldY; + if (dx > qx) + dx = qx; + else if (dx < -qx) + dx = -qx; + if (dy > qy) + dy = qy; + else if (dy < -qy) + dy = -qy; + a = this.remaining; + if (rotateOrShearX) { + mr = (this.data.rotate + this.data.shearX) * mix; + z = this.rotateLag * Math.max(0, 1 - aa / t); + let r = Math.atan2(dy + this.ty, dx + this.tx) - ca - (this.rotateOffset - z) * mr; + this.rotateOffset += (r - Math.ceil(r * MathUtils.invPI2 - 0.5) * MathUtils.PI2) * i; + r = (this.rotateOffset - z) * mr + ca; + c = Math.cos(r); + s = Math.sin(r); + if (scaleX) { + r = l * bone.getWorldScaleX(); + if (r > 0) this.scaleOffset += (dx * c + dy * s) * i / r; + } + } else { + c = Math.cos(ca); + s = Math.sin(ca); + const r = l * bone.getWorldScaleX() - this.scaleLag * Math.max(0, 1 - aa / t); + if (r > 0) this.scaleOffset += (dx * c + dy * s) * i / r; + } + if (a >= t) { + if (d === -1) { + d = p.damping ** (60 * t); + m = t * p.massInverse; + e = p.strength; + } + const ax = p.wind * skeleton.windX + p.gravity * skeleton.gravityX; + const ay = (p.wind * skeleton.windY + p.gravity * skeleton.gravityY) * Skeleton.yDir; + const rs = this.rotateOffset, ss = this.scaleOffset, h = l / f; + while (true) { + a -= t; + if (scaleX) { + this.scaleVelocity += (ax * c - ay * s - this.scaleOffset * e) * m; + this.scaleOffset += this.scaleVelocity * t; + this.scaleVelocity *= d; + } + if (rotateOrShearX) { + this.rotateVelocity -= ((ax * s + ay * c) * h + this.rotateOffset * e) * m; + this.rotateOffset += this.rotateVelocity * t; + this.rotateVelocity *= d; + if (a < t) break; + const r = this.rotateOffset * mr + ca; + c = Math.cos(r); + s = Math.sin(r); + } else if (a < t) + break; + } + this.rotateLag = this.rotateOffset - rs; + this.scaleLag = this.scaleOffset - ss; + } + z = Math.max(0, 1 - a / t); + } + this.remaining = a; + } + this.cx = bone.worldX; + this.cy = bone.worldY; + break; + } + case 3 /* pose */: + z = Math.max(0, 1 - this.remaining / t); + if (x) bone.worldX += (this.xOffset - this.xLag * z) * mix * this.data.x; + if (y) bone.worldY += (this.yOffset - this.yLag * z) * mix * this.data.y; + } + if (rotateOrShearX) { + let o = (this.rotateOffset - this.rotateLag * z) * mix, s = 0, c = 0, a = 0; + if (this.data.shearX > 0) { + let r = 0; + if (this.data.rotate > 0) { + r = o * this.data.rotate; + s = Math.sin(r); + c = Math.cos(r); + a = bone.b; + bone.b = c * a - s * bone.d; + bone.d = s * a + c * bone.d; + } + r += o * this.data.shearX; + s = Math.sin(r); + c = Math.cos(r); + a = bone.a; + bone.a = c * a - s * bone.c; + bone.c = s * a + c * bone.c; + } else { + o *= this.data.rotate; + s = Math.sin(o); + c = Math.cos(o); + a = bone.a; + bone.a = c * a - s * bone.c; + bone.c = s * a + c * bone.c; + a = bone.b; + bone.b = c * a - s * bone.d; + bone.d = s * a + c * bone.d; + } + } + if (scaleX) { + let s = 1 + (this.scaleOffset - this.scaleLag * z) * mix * this.data.scaleX; + bone.a *= s; + bone.c *= s; + switch (this.data.scaleYMode) { + case 1 /* Uniform */: + bone.b *= s; + bone.d *= s; + break; + case 2 /* Volume */: + s = Math.abs(s); + s = s >= 0.7 ? 1 / s : 4 - 3.67347 * s; + bone.b *= s; + bone.d *= s; + } + } + if (physics !== 3 /* pose */) { + this.tx = l * bone.a; + this.ty = l * bone.c; + } + } + sort(skeleton) { + const bone = this.bone.bone; + skeleton.sortBone(bone); + skeleton._updateCache.push(this); + skeleton.sortReset(bone.children); + skeleton.constrained(bone); + } + isSourceActive() { + return this.bone.bone.active; + } + }; + + // spine-core/src/PhysicsConstraintData.ts + var PhysicsConstraintData = class extends ConstraintData { + /** The bone constrained by this physics constraint. */ + set bone(boneData) { + this._bone = boneData; + } + get bone() { + if (!this._bone) throw new Error("BoneData not set."); + else return this._bone; + } + _bone = null; + /** Physics influence on x translation, 0-1. */ + x = 0; + /** Physics influence on y translation, 0-1. */ + y = 0; + /** Physics influence on rotation, 0-1. */ + rotate = 0; + /** Physics influence on scaleX, 0-1. */ + scaleX = 0; + /** Physics influence on shearX, 0-1. */ + shearX = 0; + /** Movement greater than the limit will not have a greater affect on physics. */ + limit = 0; + /** The time in milliseconds required to advanced the physics simulation one step. */ + step = 0; + /** True when this constraint's inertia is controlled by global slider timelines. */ + inertiaGlobal = false; + /** True when this constraint's strength is controlled by global slider timelines. */ + strengthGlobal = false; + /** True when this constraint's damping is controlled by global slider timelines. */ + dampingGlobal = false; + /** True when this constraint's mass is controlled by global slider timelines. */ + massGlobal = false; + /** True when this constraint's wind is controlled by global slider timelines. */ + windGlobal = false; + /** True when this constraint's gravity is controlled by global slider timelines. */ + gravityGlobal = false; + /** True when this constraint's mix is controlled by global slider timelines. */ + mixGlobal = false; + /** Determines how the {@link BonePose.scaleY} changes when {@link BonePose.scaleX} sets + * {@link BonePose.scaleX}. */ + _scaleYMode = 0 /* None */; + get scaleYMode() { + return this._scaleYMode; + } + set scaleYMode(scaleYMode) { + if (scaleYMode == null) throw new Error("scaleYMode cannot be null."); + this._scaleYMode = scaleYMode; + } + constructor(name) { + super(name, new PhysicsConstraintPose()); + } + create(skeleton) { + return new PhysicsConstraint(this, skeleton); + } + }; + + // spine-core/src/polyfills.ts + (() => { + if (typeof Math.fround === "undefined") { + Math.fround = /* @__PURE__ */ ((array) => (x) => { + array[0] = x; + return array[0]; + })(new Float32Array(1)); + } + })(); + + // spine-core/src/SliderPose.ts + var SliderPose = class { + /** The time in the {@link SliderData.animation} to apply the animation. */ + time = 0; + /** A percentage (unbounded) that controls the mix between the constrained and unconstrained poses. */ + mix = 0; + set(pose) { + this.time = pose.time; + this.mix = pose.mix; + } + }; + + // spine-core/src/Slider.ts + var Slider = class _Slider extends Constraint { + static offsets = [0, 0, 0, 0, 0, 0]; + /** When set, the bone's transform property is used to set the slider's {@link SliderPose.time}. */ + bone = null; + constructor(data, skeleton) { + super(data, new SliderPose(), new SliderPose()); + if (!skeleton) throw new Error("skeleton cannot be null."); + if (data.bone != null) this.bone = skeleton.bones[data.bone.index]; + } + copy(skeleton) { + var copy = new _Slider(this.data, skeleton); + copy.pose.set(this.pose); + return copy; + } + update(skeleton, physics) { + const p = this.appliedPose; + if (p.mix === 0) return; + const data = this.data, animation = data.animation, bone = this.bone; + if (bone !== null) { + if (!bone.active) return; + if (data.local) bone.appliedPose.validateLocalTransform(skeleton); + p.time = data.offset + (data.property.value(skeleton, bone.appliedPose, data.local, _Slider.offsets) - data.property.offset) * data.scale; + if (data.loop) + p.time = animation.duration + p.time % animation.duration; + else + p.time = Math.max(0, p.time); + } + const bones = skeleton.bones; + const indices = animation.bones; + for (let i = 0, n = animation.bones.length; i < n; i++) + bones[indices[i]].appliedPose.modifyLocal(skeleton); + animation.apply(skeleton, p.time, p.time, data.loop, null, p.mix, 0 /* current */, data.additive, false, true); + } + sort(skeleton) { + const bone = this.bone; + const data = this.data; + if (bone && !data.local) skeleton.sortBone(bone); + skeleton._updateCache.push(this); + const bones = skeleton.bones; + const indices = data.animation.bones; + for (let i = 0, n = data.animation.bones.length; i < n; i++) { + const bone2 = bones[indices[i]]; + bone2.sorted = false; + skeleton.sortReset(bone2.children); + skeleton.constrained(bone2); + } + const timelines = data.animation.timelines; + const slots = skeleton.slots; + const constraints = skeleton.constraints; + const physics = skeleton.physics; + const physicsCount = skeleton.physics.length; + for (let i = 0, n = data.animation.timelines.length; i < n; i++) { + const t = timelines[i]; + if (isSlotTimeline(t)) + skeleton.constrained(slots[t.slotIndex]); + else if (t instanceof DrawOrderTimeline || t instanceof DrawOrderFolderTimeline) + skeleton.drawOrder.constrained(); + else if (t instanceof PhysicsConstraintTimeline) { + if (t.constraintIndex === -1) { + for (let ii = 0; ii < physicsCount; ii++) + skeleton.constrained(physics[ii]); + } else + skeleton.constrained(constraints[t.constraintIndex]); + } else if (isConstraintTimeline(t)) { + const constraintIndex = t.constraintIndex; + if (constraintIndex !== -1) skeleton.constrained(constraints[constraintIndex]); + } + } + } + }; + + // spine-core/src/SliderData.ts + var SliderData = class extends ConstraintData { + /** The animation the slider will apply. */ + animation; + /** When true, the animation is applied by adding it to the current pose rather than overwriting it. */ + additive = false; + /** When true, the animation repeats after its duration, otherwise the last frame is used. */ + loop = false; + /** When set, the bone's transform property is used to set the slider's {@link SliderPose.time}. */ + bone = null; + /** When a bone is set, the specified transform property is used to set the slider's {@link SliderPose.time}. */ + property; + /** When a bone is set, this is the scale of the {@link property} value in relation to the slider time. */ + scale = 0; + /** When a bone is set, the offset is added to the property. */ + offset = 0; + /** When true and a bone is set, the bone's local transform property is read instead of its world transform. */ + local = false; + // Nonessential. + /** When a bone is set, the maximum slider time for the bone property range, or 0 if nonessential data was not exported. */ + max = 0; + constructor(name) { + super(name, new SliderPose()); + } + create(skeleton) { + return new Slider(this, skeleton); + } + }; + + // spine-core/src/SkeletonData.ts + var SkeletonData = class { + /** The skeleton's name, which by default is the name of the skeleton data file, if possible. May be null. */ + name = null; + /** The skeleton's bones, sorted parent first. The root bone is always the first bone. */ + bones = []; + // Ordered parents first. + /** The skeleton's slots in the setup pose draw order. */ + slots = []; + // Setup pose draw order. + skins = []; + /** The skeleton's default skin. By default this skin contains all attachments that were not in a skin in Spine. + * + * See {@link Skeleton.getAttachmentByName}. + * May be null. */ + defaultSkin = null; + /** The skeleton's events. */ + events = []; + /** The skeleton's animations. */ + animations = []; + /** The skeleton's IK constraints. */ + // biome-ignore lint/suspicious/noExplicitAny: reference runtime does not restrict to specific types + constraints = []; + /** The X coordinate of the skeleton's axis aligned bounding box in the setup pose. */ + x = 0; + /** The Y coordinate of the skeleton's axis aligned bounding box in the setup pose. */ + y = 0; + /** The width of the skeleton's axis aligned bounding box in the setup pose. */ + width = 0; + /** The height of the skeleton's axis aligned bounding box in the setup pose. */ + height = 0; + /** Baseline scale factor for applying distance-dependent effects on non-scalable properties, such as angle or scale. Default + * is 100. */ + referenceScale = 100; + /** The Spine version used to export the skeleton data, or null. */ + version = null; + /** The skeleton data hash. This value will change if any of the skeleton data has changed. May be null. */ + hash = null; + // Nonessential + /** The dopesheet FPS in Spine. Available only when nonessential data was exported. */ + fps = 30; + /** The path to the images folder as defined in Spine. Available only when nonessential data was exported. May be null. */ + imagesPath = null; + /** The path to the audio folder as defined in Spine. Available only when nonessential data was exported. May be null. */ + audioPath = null; + /** Finds a bone by comparing each bone's name. It is more efficient to cache the results of this method than to call it + * multiple times. + * @returns May be null. */ + findBone(boneName) { + if (!boneName) throw new Error("boneName cannot be null."); + const bones = this.bones; + for (let i = 0, n = bones.length; i < n; i++) + if (bones[i].name === boneName) return bones[i]; + return null; + } + /** Finds a slot by comparing each slot's name. It is more efficient to cache the results of this method than to call it + * multiple times. + * @returns May be null. */ + findSlot(slotName) { + if (!slotName) throw new Error("slotName cannot be null."); + const slots = this.slots; + for (let i = 0, n = slots.length; i < n; i++) + if (slots[i].name === slotName) return slots[i]; + return null; + } + /** Finds a skin by comparing each skin's name. It is more efficient to cache the results of this method than to call it + * multiple times. + * @returns May be null. */ + findSkin(skinName) { + if (!skinName) throw new Error("skinName cannot be null."); + const skins = this.skins; + for (let i = 0, n = skins.length; i < n; i++) + if (skins[i].name === skinName) return skins[i]; + return null; + } + /** Finds an event by comparing each events's name. It is more efficient to cache the results of this method than to call it + * multiple times. + * @returns May be null. */ + findEvent(eventDataName) { + if (!eventDataName) throw new Error("eventDataName cannot be null."); + const events = this.events; + for (let i = 0, n = events.length; i < n; i++) + if (events[i].name === eventDataName) return events[i]; + return null; + } + /** Collects animations used by {@link SliderData slider constraints}. + * + * Slider animations are designed to be applied by slider constraints rather than on their own. Applications that have a user + * choose an animation may want to exclude them. */ + findSliderAnimations(animations) { + const constraints = this.constraints; + for (let i = 0, n = this.constraints.length; i < n; i++) { + const data = constraints[i]; + if (data instanceof SliderData && data.animation != null) animations.push(data.animation); + } + return animations; + } + /** Finds an animation by comparing each animation's name. It is more efficient to cache the results of this method than to + * call it multiple times. + * @returns May be null. */ + findAnimation(animationName) { + if (!animationName) throw new Error("animationName cannot be null."); + const animations = this.animations; + for (let i = 0, n = animations.length; i < n; i++) + if (animations[i].name === animationName) return animations[i]; + return null; + } + // --- Constraints. + /** Finds a constraint of the specified type by comparing each constraints's name. It is more efficient to cache the results of + * this method than to call it multiple times. */ + // biome-ignore lint/suspicious/noExplicitAny: reference runtime does not restrict to specific types + findConstraint(constraintName, type) { + if (!constraintName) throw new Error("constraintName cannot be null."); + if (type == null) throw new Error("type cannot be null."); + const constraints = this.constraints; + for (let i = 0, n = this.constraints.length; i < n; i++) { + const constraint = constraints[i]; + if (constraint instanceof type && constraint.name === constraintName) return constraint; + } + return null; + } + }; + + // spine-core/src/Skin.ts + var SkinEntry = class { + /** The {@link Skeleton.slots} index. */ + slotIndex = 0; + placeholder; + /** The attachment for this skin entry. */ + attachment; + constructor(slotIndex = 0, placeholder, attachment) { + this.slotIndex = slotIndex; + this.placeholder = placeholder; + this.attachment = attachment; + } + }; + var Skin = class { + /** The skin's name, unique across all skins in the skeleton. + * + * See {@link SkeletonData.findSkin}. */ + name; + attachments = []; + bones = []; + // biome-ignore lint/suspicious/noExplicitAny: reference runtime does not restrict to specific types + constraints = []; + /** The color of the skin as it was in Spine, or a default color if nonessential data was not exported. */ + color = new Color(0.99607843, 0.61960787, 0.30980393, 1); + // fe9e4fff + constructor(name) { + if (!name) throw new Error("name cannot be null."); + this.name = name; + } + /** Adds an attachment to the skin for the specified slot index and name. */ + setAttachment(slotIndex, placeholder, attachment) { + if (!attachment) throw new Error("attachment cannot be null."); + const attachments = this.attachments; + if (slotIndex >= attachments.length) attachments.length = slotIndex + 1; + if (!attachments[slotIndex]) attachments[slotIndex] = {}; + attachments[slotIndex][placeholder] = attachment; + } + /** Adds all attachments, bones, and constraints from the specified skin to this skin. */ + addSkin(skin) { + for (let i = 0; i < skin.bones.length; i++) { + const bone = skin.bones[i]; + let contained = false; + for (let ii = 0; ii < this.bones.length; ii++) { + if (this.bones[ii] === bone) { + contained = true; + break; + } + } + if (!contained) this.bones.push(bone); + } + for (let i = 0; i < skin.constraints.length; i++) { + const constraint = skin.constraints[i]; + let contained = false; + for (let ii = 0; ii < this.constraints.length; ii++) { + if (this.constraints[ii] === constraint) { + contained = true; + break; + } + } + if (!contained) this.constraints.push(constraint); + } + const attachments = skin.getAttachments(); + for (let i = 0; i < attachments.length; i++) { + const attachment = attachments[i]; + this.setAttachment(attachment.slotIndex, attachment.placeholder, attachment.attachment); + } + } + /** Adds all bones and constraints and copies of all attachments from the specified skin to this skin. Mesh attachments are not + * copied, instead a new linked mesh is created. The attachment copies can be modified without affecting the originals. */ + copySkin(skin) { + for (let i = 0; i < skin.bones.length; i++) { + const bone = skin.bones[i]; + let contained = false; + for (let ii = 0; ii < this.bones.length; ii++) { + if (this.bones[ii] === bone) { + contained = true; + break; + } + } + if (!contained) this.bones.push(bone); + } + for (let i = 0; i < skin.constraints.length; i++) { + const constraint = skin.constraints[i]; + let contained = false; + for (let ii = 0; ii < this.constraints.length; ii++) { + if (this.constraints[ii] === constraint) { + contained = true; + break; + } + } + if (!contained) this.constraints.push(constraint); + } + const attachments = skin.getAttachments(); + for (let i = 0; i < attachments.length; i++) { + const attachment = attachments[i]; + if (!attachment.attachment) continue; + if (attachment.attachment instanceof MeshAttachment) { + attachment.attachment = attachment.attachment.newLinkedMesh(); + this.setAttachment(attachment.slotIndex, attachment.placeholder, attachment.attachment); + } else { + attachment.attachment = attachment.attachment.copy(); + this.setAttachment(attachment.slotIndex, attachment.placeholder, attachment.attachment); + } + } + } + /** Returns the attachment for the specified slot index and placeholder, or null. */ + getAttachment(slotIndex, placeholder) { + const dictionary = this.attachments[slotIndex]; + return dictionary ? dictionary[placeholder] : null; + } + /** Removes the attachment in the skin for the specified slot index and placeholder, if any. */ + removeAttachment(slotIndex, placeholder) { + const dictionary = this.attachments[slotIndex]; + if (dictionary) delete dictionary[placeholder]; + } + /** Returns all attachments in this skin. */ + getAttachments() { + const entries = []; + for (let i = 0; i < this.attachments.length; i++) { + const slotAttachments = this.attachments[i]; + if (slotAttachments) { + for (const name in slotAttachments) { + const attachment = slotAttachments[name]; + if (attachment) entries.push(new SkinEntry(i, name, attachment)); + } + } + } + return entries; + } + /** Returns all attachments in this skin for the specified slot index. */ + getAttachmentsForSlot(slotIndex, attachments) { + const slotAttachments = this.attachments[slotIndex]; + if (slotAttachments) { + for (const name in slotAttachments) { + const attachment = slotAttachments[name]; + if (attachment) attachments.push(new SkinEntry(slotIndex, name, attachment)); + } + } + } + /** Clears all attachments, bones, and constraints. */ + clear() { + this.attachments.length = 0; + this.bones.length = 0; + this.constraints.length = 0; + } + /** Attach each attachment in this skin if the corresponding attachment in the old skin is currently attached. */ + attachAll(skeleton, oldSkin) { + let slotIndex = 0; + for (let i = 0; i < skeleton.slots.length; i++) { + const slot = skeleton.slots[i]; + const slotAttachment = slot.pose.getAttachment(); + if (slotAttachment && slotIndex < oldSkin.attachments.length) { + const dictionary = oldSkin.attachments[slotIndex]; + for (const placeholder in dictionary) { + const skinAttachment = dictionary[placeholder]; + if (slotAttachment === skinAttachment) { + const attachment = this.getAttachment(slotIndex, placeholder); + if (attachment) slot.pose.setAttachment(attachment); + break; + } + } + } + slotIndex++; + } + } + }; + + // spine-core/src/SlotData.ts + var SlotData = class extends PosedData { + /** The index of the slot in {@link Skeleton.slots}. */ + index = 0; + /** The bone this slot belongs to. */ + boneData; + /** The name of the attachment that is visible for this slot in the setup pose, or null if no attachment is visible. */ + attachmentName = null; + /** The blend mode for drawing the slot's attachment. */ + blendMode = 0 /* Normal */; + // Nonessential. + /** False if the slot was hidden in Spine and nonessential data was exported. Does not affect runtime rendering. */ + visible = true; + constructor(index, name, boneData) { + super(name, new SlotPose()); + if (index < 0) throw new Error("index must be >= 0."); + if (!boneData) throw new Error("boneData cannot be null."); + this.index = index; + this.boneData = boneData; + } + }; + var BlendMode = /* @__PURE__ */ ((BlendMode2) => { + BlendMode2[BlendMode2["Normal"] = 0] = "Normal"; + BlendMode2[BlendMode2["Additive"] = 1] = "Additive"; + BlendMode2[BlendMode2["Multiply"] = 2] = "Multiply"; + BlendMode2[BlendMode2["Screen"] = 3] = "Screen"; + return BlendMode2; + })(BlendMode || {}); + + // spine-core/src/TransformConstraintPose.ts + var TransformConstraintPose = class { + /** A percentage (unbounded) that controls the mix between the constrained and unconstrained rotation. */ + mixRotate = 0; + /** A percentage (unbounded) that controls the mix between the constrained and unconstrained translation X. */ + mixX = 0; + /** A percentage (unbounded) that controls the mix between the constrained and unconstrained translation Y. */ + mixY = 0; + /** A percentage (unbounded) that controls the mix between the constrained and unconstrained scale X. */ + mixScaleX = 0; + /** A percentage (unbounded) that controls the mix between the constrained and unconstrained scale Y. */ + mixScaleY = 0; + /** A percentage (unbounded) that controls the mix between the constrained and unconstrained shear Y. */ + mixShearY = 0; + set(pose) { + this.mixRotate = pose.mixRotate; + this.mixX = pose.mixX; + this.mixY = pose.mixY; + this.mixScaleX = pose.mixScaleX; + this.mixScaleY = pose.mixScaleY; + this.mixShearY = pose.mixShearY; + } + }; + + // spine-core/src/TransformConstraint.ts + var TransformConstraint = class _TransformConstraint extends Constraint { + /** The bones that will be modified by this transform constraint. */ + bones; + /** The bone whose world transform will be copied to the constrained bones. */ + source; + constructor(data, skeleton) { + super(data, new TransformConstraintPose(), new TransformConstraintPose()); + if (!skeleton) throw new Error("skeleton cannot be null."); + this.bones = []; + for (const boneData of data.bones) + this.bones.push(skeleton.bones[boneData.index].constrainedPose); + const source = skeleton.bones[data.source.index]; + if (source == null) throw new Error("source cannot be null."); + this.source = source; + } + copy(skeleton) { + var copy = new _TransformConstraint(this.data, skeleton); + copy.pose.set(this.pose); + return copy; + } + update(skeleton, physics) { + const p = this.appliedPose; + if (p.mixRotate === 0 && p.mixX === 0 && p.mixY === 0 && p.mixScaleX === 0 && p.mixScaleY === 0 && p.mixShearY === 0) return; + const data = this.data; + const localSource = data.localSource, localTarget = data.localTarget, additive = data.additive, clamp = data.clamp; + const offsets = data.offsets; + const source = this.source.appliedPose; + if (localSource) source.validateLocalTransform(skeleton); + const fromItems = data.properties; + const fn = data.properties.length; + const bones = this.bones; + for (let i = 0, n = this.bones.length; i < n; i++) { + const bone = bones[i]; + if (localTarget) + bone.modifyLocal(skeleton); + else + bone.modifyWorld(skeleton); + for (let f = 0; f < fn; f++) { + const from = fromItems[f]; + const value = from.value(skeleton, source, localSource, offsets) - from.offset; + const toItems = from.to; + for (let t = 0, tn = from.to.length; t < tn; t++) { + const to = toItems[t]; + if (to.mix(p) !== 0) { + let clamped = to.offset + value * to.scale; + if (clamp) { + if (to.offset < to.max) + clamped = MathUtils.clamp(clamped, to.offset, to.max); + else + clamped = MathUtils.clamp(clamped, to.max, to.offset); + } + to.apply(skeleton, p, bone, clamped, localTarget, additive); + } + } + } + } + } + sort(skeleton) { + if (!this.data.localSource) skeleton.sortBone(this.source); + const bones = this.bones; + const boneCount = this.bones.length; + const worldTarget = !this.data.localTarget; + if (worldTarget) { + for (let i = 0; i < boneCount; i++) + skeleton.sortBone(bones[i].bone); + } + skeleton._updateCache.push(this); + for (let i = 0; i < boneCount; i++) { + const bone = bones[i].bone; + skeleton.sortReset(bone.children); + skeleton.constrained(bone); + } + for (let i = 0; i < boneCount; i++) + bones[i].bone.sorted = worldTarget; + } + isSourceActive() { + return this.source.active; + } + }; + + // spine-core/src/TransformConstraintData.ts + var TransformConstraintData = class _TransformConstraintData extends ConstraintData { + static ROTATION = 0; + static X = 1; + static Y = 2; + static SCALEX = 3; + static SCALEY = 4; + static SHEARY = 5; + /** The bones that will be modified by this transform constraint. */ + bones = []; + /** The bone whose world transform will be copied to the constrained bones. */ + set source(source) { + this._source = source; + } + get source() { + if (!this._source) throw new Error("BoneData not set."); + else return this._source; + } + _source = null; + offsets = [0, 0, 0, 0, 0, 0]; + /** An offset added to the constrained bone X translation. */ + offsetX = 0; + /** An offset added to the constrained bone Y translation. */ + offsetY = 0; + /** Reads the source bone's local transform instead of its world transform. */ + localSource = false; + /** Sets the constrained bones' local transforms instead of their world transforms. */ + localTarget = false; + /** Adds the source bone transform to the constrained bones instead of setting it absolutely. */ + additive = false; + /** Prevents constrained bones from exceeding the ranged defined by {@link ToProperty.offset} and {@link ToProperty.max}. */ + clamp = false; + /** The mapping of transform properties to other transform properties. */ + properties = []; + constructor(name) { + super(name, new TransformConstraintPose()); + } + create(skeleton) { + return new TransformConstraint(this, skeleton); + } + /** An offset added to the constrained bone rotation. */ + getOffsetRotation() { + return this.offsets[_TransformConstraintData.ROTATION]; + } + setOffsetRotation(offsetRotation) { + this.offsets[_TransformConstraintData.ROTATION] = offsetRotation; + } + /** An offset added to the constrained bone X translation. */ + getOffsetX() { + return this.offsets[_TransformConstraintData.X]; + } + setOffsetX(offsetX) { + this.offsets[_TransformConstraintData.X] = offsetX; + } + /** An offset added to the constrained bone Y translation. */ + getOffsetY() { + return this.offsets[_TransformConstraintData.Y]; + } + setOffsetY(offsetY) { + this.offsets[_TransformConstraintData.Y] = offsetY; + } + /** An offset added to the constrained bone scaleX. */ + getOffsetScaleX() { + return this.offsets[_TransformConstraintData.SCALEX]; + } + setOffsetScaleX(offsetScaleX) { + this.offsets[_TransformConstraintData.SCALEX] = offsetScaleX; + } + /** An offset added to the constrained bone scaleY. */ + getOffsetScaleY() { + return this.offsets[_TransformConstraintData.SCALEY]; + } + setOffsetScaleY(offsetScaleY) { + this.offsets[_TransformConstraintData.SCALEY] = offsetScaleY; + } + /** An offset added to the constrained bone shearY. */ + getOffsetShearY() { + return this.offsets[_TransformConstraintData.SHEARY]; + } + setOffsetShearY(offsetShearY) { + this.offsets[_TransformConstraintData.SHEARY] = offsetShearY; + } + }; + var FromProperty = class { + /** The value of this property that corresponds to {@link ToProperty.offset}. */ + offset = 0; + /** Constrained properties. */ + to = []; + }; + var ToProperty = class { + /** The value of this property that corresponds to {@link FromProperty.offset}. */ + offset = 0; + /** The maximum value of this property when {@link TransformConstraintData.clamp clamped}. */ + max = 0; + /** The scale of the {@link FromProperty} value in relation to this property. */ + scale = 0; + }; + var FromRotate = class extends FromProperty { + value(skeleton, source, local, offsets) { + if (local) return source.rotation + offsets[TransformConstraintData.ROTATION]; + const sx = skeleton.scaleX, sy = skeleton.scaleY; + let value = Math.atan2(source.c / sy, source.a / sx) * MathUtils.radDeg + ((source.a * source.d - source.b * source.c) * sx * sy > 0 ? offsets[TransformConstraintData.ROTATION] : -offsets[TransformConstraintData.ROTATION]); + if (value < 0) value += 360; + return value; + } + }; + var ToRotate = class extends ToProperty { + mix(pose) { + return pose.mixRotate; + } + apply(skeleton, pose, bone, value, local, additive) { + if (local) + bone.rotation += (additive ? value : value - bone.rotation) * pose.mixRotate; + else { + const sx = skeleton.scaleX, sy = skeleton.scaleY, ix = 1 / sx, iy = 1 / sy; + const a = bone.a * ix, b = bone.b * ix, c = bone.c * iy, d = bone.d * iy; + value *= MathUtils.degRad; + if (!additive) value -= Math.atan2(c, a); + if (value > MathUtils.PI) + value -= MathUtils.PI2; + else if (value < -MathUtils.PI) + value += MathUtils.PI2; + value *= pose.mixRotate; + const cos = Math.cos(value), sin = Math.sin(value); + bone.a = (cos * a - sin * c) * sx; + bone.b = (cos * b - sin * d) * sx; + bone.c = (sin * a + cos * c) * sy; + bone.d = (sin * b + cos * d) * sy; + } + } + }; + var FromX = class extends FromProperty { + value(skeleton, source, local, offsets) { + return local ? source.x + offsets[TransformConstraintData.X] : (offsets[TransformConstraintData.X] * source.a + offsets[TransformConstraintData.Y] * source.b + source.worldX) / skeleton.scaleX; + } + }; + var ToX = class extends ToProperty { + mix(pose) { + return pose.mixX; + } + apply(skeleton, pose, bone, value, local, additive) { + if (local) + bone.x += (additive ? value : value - bone.x) * pose.mixX; + else { + if (!additive) value -= bone.worldX / skeleton.scaleX; + bone.worldX += value * pose.mixX * skeleton.scaleX; + } + } + }; + var FromY = class extends FromProperty { + value(skeleton, source, local, offsets) { + return local ? source.y + offsets[TransformConstraintData.Y] : (offsets[TransformConstraintData.X] * source.c + offsets[TransformConstraintData.Y] * source.d + source.worldY) / skeleton.scaleY; + } + }; + var ToY = class extends ToProperty { + mix(pose) { + return pose.mixY; + } + apply(skeleton, pose, bone, value, local, additive) { + if (local) + bone.y += (additive ? value : value - bone.y) * pose.mixY; + else { + if (!additive) value -= bone.worldY / skeleton.scaleY; + bone.worldY += value * pose.mixY * skeleton.scaleY; + } + } + }; + var FromScaleX = class extends FromProperty { + value(skeleton, source, local, offsets) { + if (local) return source.scaleX + offsets[TransformConstraintData.SCALEX]; + const a = source.a / skeleton.scaleX, c = source.c / skeleton.scaleY; + return Math.sqrt(a * a + c * c) + offsets[TransformConstraintData.SCALEX]; + } + }; + var ToScaleX = class extends ToProperty { + mix(pose) { + return pose.mixScaleX; + } + apply(skeleton, pose, bone, value, local, additive) { + if (local) { + if (additive) + bone.scaleX *= 1 + (value - 1) * pose.mixScaleX; + else if (bone.scaleX !== 0) + bone.scaleX += (value - bone.scaleX) * pose.mixScaleX; + } else if (additive) { + const s = 1 + (value - 1) * pose.mixScaleX; + bone.a *= s; + bone.c *= s; + } else { + let a = bone.a / skeleton.scaleX, c = bone.c / skeleton.scaleY, s = Math.sqrt(a * a + c * c); + if (s !== 0) { + s = 1 + (value - s) * pose.mixScaleX / s; + bone.a *= s; + bone.c *= s; + } + } + } + }; + var FromScaleY = class extends FromProperty { + value(skeleton, source, local, offsets) { + if (local) return source.scaleY + offsets[TransformConstraintData.SCALEY]; + const b = source.b / skeleton.scaleX, d = source.d / skeleton.scaleY; + return Math.sqrt(b * b + d * d) + offsets[TransformConstraintData.SCALEY]; + } + }; + var ToScaleY = class extends ToProperty { + mix(pose) { + return pose.mixScaleY; + } + apply(skeleton, pose, bone, value, local, additive) { + if (local) { + if (additive) + bone.scaleY *= 1 + (value - 1) * pose.mixScaleY; + else if (bone.scaleY !== 0) + bone.scaleY += (value - bone.scaleY) * pose.mixScaleY; + } else if (additive) { + const s = 1 + (value - 1) * pose.mixScaleY; + bone.b *= s; + bone.d *= s; + } else { + let b = bone.b / skeleton.scaleX, d = bone.d / skeleton.scaleY, s = Math.sqrt(b * b + d * d); + if (s !== 0) { + s = 1 + (value - s) * pose.mixScaleY / s; + bone.b *= s; + bone.d *= s; + } + } + } + }; + var FromShearY = class extends FromProperty { + value(skeleton, source, local, offsets) { + if (local) return source.shearY + offsets[TransformConstraintData.SHEARY]; + const ix = 1 / skeleton.scaleX, iy = 1 / skeleton.scaleY; + return (Math.atan2(source.d * iy, source.b * ix) - Math.atan2(source.c * iy, source.a * ix)) * MathUtils.radDeg - 90 + offsets[TransformConstraintData.SHEARY]; + } + }; + var ToShearY = class extends ToProperty { + mix(pose) { + return pose.mixShearY; + } + apply(skeleton, pose, bone, value, local, additive) { + if (local) { + if (!additive) value -= bone.shearY; + bone.shearY += value * pose.mixShearY; + } else { + const sx = skeleton.scaleX, sy = skeleton.scaleY, b = bone.b / sx, d = bone.d / sy, by = Math.atan2(d, b); + value = (value + 90) * MathUtils.degRad; + if (additive) + value -= MathUtils.PI / 2; + else { + value -= by - Math.atan2(bone.c / sy, bone.a / sx); + if (value > MathUtils.PI) + value -= MathUtils.PI2; + else if (value < -MathUtils.PI) + value += MathUtils.PI2; + } + value = by + value * pose.mixShearY; + const s = Math.sqrt(b * b + d * d); + bone.b = Math.cos(value) * s * sx; + bone.d = Math.sin(value) * s * sy; + } + } + }; + + // spine-core/src/SkeletonBinary.ts + var SkeletonBinary = class { + /** Scales bone positions, image sizes, and translations as they are loaded. This allows different size images to be used at + * runtime than were used in Spine. + * + * See [Scaling](http://esotericsoftware.com/spine-loading-skeleton-data#Scaling) in the Spine Runtimes Guide. */ + scale = 1; + attachmentLoader; + linkedMeshes = []; + constructor(attachmentLoader) { + this.attachmentLoader = attachmentLoader; + } + readSkeletonData(binary) { + const scale = this.scale; + const skeletonData = new SkeletonData(); + skeletonData.name = ""; + const input = new BinaryInput(binary); + const lowHash = input.readInt32(); + const highHash = input.readInt32(); + skeletonData.hash = highHash === 0 && lowHash === 0 ? null : highHash.toString(16) + lowHash.toString(16); + skeletonData.version = input.readString(); + skeletonData.x = input.readFloat(); + skeletonData.y = input.readFloat(); + skeletonData.width = input.readFloat(); + skeletonData.height = input.readFloat(); + skeletonData.referenceScale = input.readFloat() * scale; + const nonessential = input.readBoolean(); + if (nonessential) { + skeletonData.fps = input.readFloat(); + skeletonData.imagesPath = input.readString(); + skeletonData.audioPath = input.readString(); + } + let n = 0; + n = input.readInt(true); + for (let i = 0; i < n; i++) { + const str = input.readString(); + if (!str) throw new Error("String in string table must not be null."); + input.strings.push(str); + } + const bones = skeletonData.bones; + n = input.readInt(true); + for (let i = 0; i < n; i++) { + const name = input.readString(); + if (!name) throw new Error("Bone name must not be null."); + const parent = i === 0 ? null : bones[input.readInt(true)]; + const data = new BoneData(i, name, parent); + const setup = data.setupPose; + setup.rotation = input.readFloat(); + setup.x = input.readFloat() * scale; + setup.y = input.readFloat() * scale; + setup.scaleX = input.readFloat(); + setup.scaleY = input.readFloat(); + setup.shearX = input.readFloat(); + setup.shearY = input.readFloat(); + setup.inherit = input.readByte(); + data.length = input.readFloat() * scale; + data.skinRequired = input.readBoolean(); + if (nonessential) { + Color.rgba8888ToColor(data.color, input.readInt32()); + data.icon = input.readString() ?? void 0; + data.iconSize = input.readFloat(); + data.iconRotation = input.readFloat(); + data.visible = input.readBoolean(); + } + bones.push(data); + } + n = input.readInt(true); + for (let i = 0; i < n; i++) { + const slotName = input.readString(); + if (!slotName) throw new Error("Slot name must not be null."); + const boneData = bones[input.readInt(true)]; + const data = new SlotData(i, slotName, boneData); + Color.rgba8888ToColor(data.setupPose.color, input.readInt32()); + const darkColor = input.readInt32(); + if (darkColor !== -1) Color.rgb888ToColor(data.setupPose.darkColor = new Color(), darkColor); + data.attachmentName = input.readStringRef(); + data.blendMode = input.readInt(true); + if (nonessential) data.visible = input.readBoolean(); + skeletonData.slots.push(data); + } + const constraints = skeletonData.constraints; + const constraintCount = input.readInt(true); + for (let i = 0; i < constraintCount; i++) { + const name = input.readString(); + if (!name) throw new Error("Constraint data name must not be null."); + let nn; + switch (input.readByte()) { + case CONSTRAINT_IK: { + const data = new IkConstraintData(name); + nn = input.readInt(true); + for (let ii = 0; ii < nn; ii++) + data.bones.push(bones[input.readInt(true)]); + data.target = bones[input.readInt(true)]; + const flags = input.readByte(); + data.skinRequired = (flags & 1) !== 0; + if ((flags & 2) !== 0) data.scaleYMode = input.readUnsignedByte(); + const setup = data.setupPose; + setup.bendDirection = (flags & 4) !== 0 ? -1 : 1; + setup.compress = (flags & 8) !== 0; + setup.stretch = (flags & 16) !== 0; + if ((flags & 32) !== 0) setup.mix = (flags & 64) !== 0 ? input.readFloat() : 1; + if ((flags & 128) !== 0) setup.softness = input.readFloat() * scale; + constraints.push(data); + break; + } + case CONSTRAINT_TRANSFORM: { + const data = new TransformConstraintData(name); + nn = input.readInt(true); + for (let ii = 0; ii < nn; ii++) + data.bones.push(bones[input.readInt(true)]); + data.source = bones[input.readInt(true)]; + let flags = input.readUnsignedByte(); + data.skinRequired = (flags & 1) !== 0; + data.localSource = (flags & 2) !== 0; + data.localTarget = (flags & 4) !== 0; + data.additive = (flags & 8) !== 0; + data.clamp = (flags & 16) !== 0; + nn = flags >> 5; + for (let ii = 0, tn; ii < nn; ii++) { + let fromScale = 1; + let from; + switch (input.readByte()) { + case 0: + from = new FromRotate(); + break; + case 1: { + fromScale = scale; + from = new FromX(); + break; + } + case 2: { + fromScale = scale; + from = new FromY(); + break; + } + case 3: + from = new FromScaleX(); + break; + case 4: + from = new FromScaleY(); + break; + case 5: + from = new FromShearY(); + break; + default: + from = null; + } + if (!from) continue; + from.offset = input.readFloat() * fromScale; + tn = input.readByte(); + for (let t = 0; t < tn; t++) { + let toScale = 1; + let to; + switch (input.readByte()) { + case 0: + to = new ToRotate(); + break; + case 1: { + toScale = scale; + to = new ToX(); + break; + } + case 2: { + toScale = scale; + to = new ToY(); + break; + } + case 3: + to = new ToScaleX(); + break; + case 4: + to = new ToScaleY(); + break; + case 5: + to = new ToShearY(); + break; + default: + to = null; + } + if (!to) continue; + to.offset = input.readFloat() * toScale; + to.max = input.readFloat() * toScale; + to.scale = input.readFloat() * toScale / fromScale; + from.to[t] = to; + } + data.properties[ii] = from; + } + flags = input.readByte(); + if ((flags & 1) !== 0) data.offsets[TransformConstraintData.ROTATION] = input.readFloat(); + if ((flags & 2) !== 0) data.offsets[TransformConstraintData.X] = input.readFloat() * scale; + if ((flags & 4) !== 0) data.offsets[TransformConstraintData.Y] = input.readFloat() * scale; + if ((flags & 8) !== 0) data.offsets[TransformConstraintData.SCALEX] = input.readFloat(); + if ((flags & 16) !== 0) data.offsets[TransformConstraintData.SCALEY] = input.readFloat(); + if ((flags & 32) !== 0) data.offsets[TransformConstraintData.SHEARY] = input.readFloat(); + flags = input.readByte(); + const setup = data.setupPose; + if ((flags & 1) !== 0) setup.mixRotate = input.readFloat(); + if ((flags & 2) !== 0) setup.mixX = input.readFloat(); + if ((flags & 4) !== 0) setup.mixY = input.readFloat(); + if ((flags & 8) !== 0) setup.mixScaleX = input.readFloat(); + if ((flags & 16) !== 0) setup.mixScaleY = input.readFloat(); + if ((flags & 32) !== 0) setup.mixShearY = input.readFloat(); + constraints.push(data); + break; + } + case CONSTRAINT_PATH: { + const data = new PathConstraintData(name); + nn = input.readInt(true); + for (let ii = 0; ii < nn; ii++) + data.bones.push(bones[input.readInt(true)]); + data.slot = skeletonData.slots[input.readInt(true)]; + const flags = input.readByte(); + data.skinRequired = (flags & 1) !== 0; + data.positionMode = flags >> 1 & 1; + data.spacingMode = flags >> 2 & 3; + data.rotateMode = flags >> 4 & 3; + if ((flags & 128) !== 0) data.offsetRotation = input.readFloat(); + const setup = data.setupPose; + setup.position = input.readFloat(); + if (data.positionMode === 0 /* Fixed */) setup.position *= scale; + setup.spacing = input.readFloat(); + if (data.spacingMode === 0 /* Length */ || data.spacingMode === 1 /* Fixed */) setup.spacing *= scale; + setup.mixRotate = input.readFloat(); + setup.mixX = input.readFloat(); + setup.mixY = input.readFloat(); + constraints.push(data); + break; + } + case CONSTRAINT_PHYSICS: { + const data = new PhysicsConstraintData(name); + data.bone = bones[input.readInt(true)]; + let flags = input.readByte(); + data.skinRequired = (flags & 1) !== 0; + if ((flags & 2) !== 0) data.x = input.readFloat(); + if ((flags & 4) !== 0) data.y = input.readFloat(); + if ((flags & 8) !== 0) data.rotate = input.readFloat(); + if ((flags & 16) !== 0) { + let scaleX = input.readFloat(); + if (scaleX < -2) { + data.scaleYMode = 2 /* Volume */; + scaleX = -2 - scaleX; + } else if (scaleX < 0) { + data.scaleYMode = 1 /* Uniform */; + scaleX = -1 - scaleX; + } + data.scaleX = scaleX; + } + if ((flags & 32) !== 0) data.shearX = input.readFloat(); + data.limit = ((flags & 64) !== 0 ? input.readFloat() : 5e3) * scale; + data.step = 1 / input.readUnsignedByte(); + const setup = data.setupPose; + setup.inertia = input.readFloat(); + setup.strength = input.readFloat(); + setup.damping = input.readFloat(); + setup.massInverse = (flags & 128) !== 0 ? input.readFloat() : 1; + setup.wind = input.readFloat(); + setup.gravity = input.readFloat(); + flags = input.readByte(); + if ((flags & 1) !== 0) data.inertiaGlobal = true; + if ((flags & 2) !== 0) data.strengthGlobal = true; + if ((flags & 4) !== 0) data.dampingGlobal = true; + if ((flags & 8) !== 0) data.massGlobal = true; + if ((flags & 16) !== 0) data.windGlobal = true; + if ((flags & 32) !== 0) data.gravityGlobal = true; + if ((flags & 64) !== 0) data.mixGlobal = true; + setup.mix = (flags & 128) !== 0 ? input.readFloat() : 1; + constraints.push(data); + break; + } + case CONSTRAINT_SLIDER: { + const data = new SliderData(name); + const flags = input.readByte(); + data.skinRequired = (flags & 1) !== 0; + data.loop = (flags & 2) !== 0; + data.additive = (flags & 4) !== 0; + if ((flags & 8) !== 0) { + const value = input.readFloat(); + if (nonessential && (flags & 64) !== 0) + data.max = value; + else + data.setupPose.time = value; + } + if ((flags & 16) !== 0) data.setupPose.mix = (flags & 32) !== 0 ? input.readFloat() : 1; + if ((flags & 64) !== 0) { + data.local = (flags & 128) !== 0; + data.bone = bones[input.readInt(true)]; + const offset = input.readFloat(); + let propertyScale = 1; + switch (input.readByte()) { + case 0: + data.property = new FromRotate(); + break; + case 1: { + propertyScale = scale; + data.property = new FromX(); + break; + } + case 2: { + propertyScale = scale; + data.property = new FromY(); + break; + } + case 3: + data.property = new FromScaleX(); + break; + case 4: + data.property = new FromScaleY(); + break; + case 5: + data.property = new FromShearY(); + break; + default: + continue; + } + ; + data.property.offset = offset * propertyScale; + data.offset = input.readFloat(); + data.scale = input.readFloat() / propertyScale; + } + constraints.push(data); + break; + } + } + } + const defaultSkin = this.readSkin(input, skeletonData, true, nonessential); + if (defaultSkin) { + skeletonData.defaultSkin = defaultSkin; + skeletonData.skins.push(defaultSkin); + } + { + let i = skeletonData.skins.length; + Utils.setArraySize(skeletonData.skins, n = i + input.readInt(true)); + for (; i < n; i++) { + const skin = this.readSkin(input, skeletonData, false, nonessential); + if (!skin) throw new Error("readSkin() should not have returned null."); + skeletonData.skins[i] = skin; + } + } + n = this.linkedMeshes.length; + for (let i = 0; i < n; i++) { + const linkedMesh = this.linkedMeshes[i]; + const skin = skeletonData.skins[linkedMesh.skinIndex]; + if (!linkedMesh.source) throw new Error("Linked mesh parent must not be null"); + const source = skin.getAttachment(linkedMesh.sourceIndex, linkedMesh.source); + if (!source) throw new Error(`Source mesh not found: ${linkedMesh.source}`); + linkedMesh.mesh.timelineAttachment = linkedMesh.inheritTimelines ? source : linkedMesh.mesh; + linkedMesh.mesh.setSourceMesh(source); + linkedMesh.mesh.updateSequence(); + } + this.linkedMeshes.length = 0; + n = input.readInt(true); + for (let i = 0; i < n; i++) { + const eventName = input.readString(); + if (!eventName) throw new Error("Event data name must not be null"); + const data = new EventData(eventName); + const setup = data.setupPose; + setup.intValue = input.readInt(false); + setup.floatValue = input.readFloat(); + setup.stringValue = input.readString(); + data._audioPath = input.readString(); + if (data.audioPath) { + setup.volume = input.readFloat(); + setup.balance = input.readFloat(); + } + skeletonData.events.push(data); + } + const animations = skeletonData.animations; + n = input.readInt(true); + for (let i = 0; i < n; i++) { + const animationName = input.readString(); + if (!animationName) throw new Error("Animation name must not be null."); + animations.push(this.readAnimation(input, animationName, skeletonData, nonessential)); + } + for (let i = 0; i < constraintCount; i++) { + const constraint = constraints[i]; + if (constraint instanceof SliderData) constraint.animation = animations[input.readInt(true)]; + } + return skeletonData; + } + readSkin(input, skeletonData, defaultSkin, nonessential) { + let skin = null; + let slotCount = 0; + if (defaultSkin) { + slotCount = input.readInt(true); + if (slotCount === 0) return null; + skin = new Skin("default"); + } else { + const skinName = input.readString(); + if (!skinName) throw new Error("Skin name must not be null."); + skin = new Skin(skinName); + if (nonessential) Color.rgba8888ToColor(skin.color, input.readInt32()); + let n = input.readInt(true); + let from = skeletonData.bones, to = skin.bones; + for (let i = 0; i < n; i++) + to[i] = from[input.readInt(true)]; + n = input.readInt(true); + from = skeletonData.constraints; + to = skin.constraints; + for (let i = 0; i < n; i++) + to[i] = from[input.readInt(true)]; + slotCount = input.readInt(true); + } + for (let i = 0; i < slotCount; i++) { + const slotIndex = input.readInt(true); + for (let ii = 0, nn = input.readInt(true); ii < nn; ii++) { + const placeholder = input.readStringRef(); + if (!placeholder) + throw new Error("Attachment name must not be null"); + const attachment = this.readAttachment(input, skeletonData, skin, slotIndex, placeholder, nonessential); + if (attachment) skin.setAttachment(slotIndex, placeholder, attachment); + } + } + return skin; + } + readAttachment(input, skeletonData, skin, slotIndex, placeholder, nonessential) { + const scale = this.scale; + const flags = input.readByte(); + const name = (flags & 8) !== 0 ? input.readStringRef() : placeholder; + if (!name) throw new Error("Attachment name must not be null"); + switch (flags & 7) { + // BUG? + case 0 /* Region */: { + let path = (flags & 16) !== 0 ? input.readStringRef() : null; + const color = (flags & 32) !== 0 ? input.readInt32() : 4294967295; + const sequence = this.readSequence(input, (flags & 64) !== 0); + const rotation = (flags & 128) !== 0 ? input.readFloat() : 0; + const x = input.readFloat(); + const y = input.readFloat(); + const scaleX = input.readFloat(); + const scaleY = input.readFloat(); + const width = input.readFloat(); + const height = input.readFloat(); + if (!path) path = name; + const region = this.attachmentLoader.newRegionAttachment(skin, placeholder, name, path, sequence); + if (!region) return null; + region.path = path; + region.x = x * scale; + region.y = y * scale; + region.scaleX = scaleX; + region.scaleY = scaleY; + region.rotation = rotation; + region.width = width * scale; + region.height = height * scale; + Color.rgba8888ToColor(region.color, color); + region.updateSequence(); + return region; + } + case 1 /* BoundingBox */: { + const vertices = this.readVertices(input, (flags & 16) !== 0); + const color = nonessential ? input.readInt32() : 0; + const box = this.attachmentLoader.newBoundingBoxAttachment(skin, placeholder, name); + if (!box) return null; + box.worldVerticesLength = vertices.length; + box.vertices = vertices.vertices; + box.bones = vertices.bones; + if (nonessential) Color.rgba8888ToColor(box.color, color); + return box; + } + case 2 /* Mesh */: { + let path = (flags & 16) !== 0 ? input.readStringRef() : name; + const color = (flags & 32) !== 0 ? input.readInt32() : 4294967295; + const sequence = this.readSequence(input, (flags & 64) !== 0); + const hullLength = input.readInt(true); + const vertices = this.readVertices(input, (flags & 128) !== 0); + const uvs = this.readFloatArray(input, vertices.length, 1); + const triangles = this.readShortArray(input, (vertices.length - hullLength - 2) * 3); + const slotCount = input.readInt(true); + let timelineSlots = null; + if (slotCount > 0) { + timelineSlots = []; + for (let i = 0; i < slotCount; i++) + timelineSlots[i] = input.readInt(true); + } + let edges = []; + let width = 0, height = 0; + if (nonessential) { + edges = this.readShortArray(input, input.readInt(true)); + width = input.readFloat(); + height = input.readFloat(); + } + if (!path) path = name; + const mesh = this.attachmentLoader.newMeshAttachment(skin, placeholder, name, path, sequence); + if (!mesh) return null; + mesh.path = path; + Color.rgba8888ToColor(mesh.color, color); + mesh.hullLength = hullLength << 1; + mesh.bones = vertices.bones; + mesh.vertices = vertices.vertices; + mesh.worldVerticesLength = vertices.length; + mesh.regionUVs = uvs; + mesh.triangles = triangles; + if (timelineSlots) mesh.timelineSlots = timelineSlots; + if (nonessential) { + mesh.edges = edges; + mesh.width = width * scale; + mesh.height = height * scale; + } + mesh.updateSequence(); + return mesh; + } + case 3 /* LinkedMesh */: { + const path = (flags & 16) !== 0 ? input.readStringRef() : name; + if (path == null) throw new Error("Path of linked mesh must not be null"); + const color = (flags & 32) !== 0 ? input.readInt32() : 4294967295; + const sequence = this.readSequence(input, (flags & 64) !== 0); + const inheritTimelines = (flags & 128) !== 0; + const sourceIndex = input.readInt(true); + const skinIndex = input.readInt(true); + const source = input.readStringRef(); + let width = 0, height = 0; + if (nonessential) { + width = input.readFloat(); + height = input.readFloat(); + } + const mesh = this.attachmentLoader.newMeshAttachment(skin, placeholder, name, path, sequence); + if (!mesh) return null; + mesh.path = path; + Color.rgba8888ToColor(mesh.color, color); + if (nonessential) { + mesh.width = width * scale; + mesh.height = height * scale; + } + this.linkedMeshes.push(new LinkedMesh(mesh, skinIndex, slotIndex, sourceIndex, source, inheritTimelines)); + return mesh; + } + case 4 /* Path */: { + const closed = (flags & 16) !== 0; + const constantSpeed = (flags & 32) !== 0; + const vertices = this.readVertices(input, (flags & 64) !== 0); + const lengths = this.readFloatArray(input, vertices.length / 6, scale); + const color = nonessential ? input.readInt32() : 0; + const path = this.attachmentLoader.newPathAttachment(skin, placeholder, name); + if (!path) return null; + path.closed = closed; + path.constantSpeed = constantSpeed; + path.worldVerticesLength = vertices.length; + path.vertices = vertices.vertices; + path.bones = vertices.bones; + path.lengths = lengths; + if (nonessential) Color.rgba8888ToColor(path.color, color); + return path; + } + case 5 /* Point */: { + const rotation = input.readFloat(); + const x = input.readFloat(); + const y = input.readFloat(); + const color = nonessential ? input.readInt32() : 0; + const point = this.attachmentLoader.newPointAttachment(skin, placeholder, name); + if (!point) return null; + point.x = x * scale; + point.y = y * scale; + point.rotation = rotation; + if (nonessential) Color.rgba8888ToColor(point.color, color); + return point; + } + case 6 /* Clipping */: { + const endSlotIndex = input.readInt(true); + const vertices = this.readVertices(input, (flags & 16) !== 0); + const color = nonessential ? input.readInt32() : 0; + const clip = this.attachmentLoader.newClippingAttachment(skin, placeholder, name); + if (!clip) return null; + clip.endSlot = skeletonData.slots[endSlotIndex]; + clip.convex = (flags & 32) !== 0; + clip.inverse = (flags & 64) !== 0; + clip.worldVerticesLength = vertices.length; + clip.vertices = vertices.vertices; + clip.bones = vertices.bones; + if (nonessential) Color.rgba8888ToColor(clip.color, color); + return clip; + } + } + } + readSequence(input, hasPathSuffix) { + if (!hasPathSuffix) return new Sequence(1, false); + const sequence = new Sequence(input.readInt(true), true); + sequence.start = input.readInt(true); + sequence.digits = input.readInt(true); + sequence.setupIndex = input.readInt(true); + return sequence; + } + readVertices(input, weighted) { + const scale = this.scale; + const vertexCount = input.readInt(true); + const length = vertexCount << 1; + if (!weighted) + return new Vertices(null, this.readFloatArray(input, length, scale), length); + const n = input.readInt(true); + const bones = []; + const weights = []; + for (let b = 0, w = 0; b < n; ) { + const boneCount = input.readInt(true); + bones[b++] = boneCount; + for (let ii = 0; ii < boneCount; ii++, w += 3) { + bones[b++] = input.readInt(true); + weights[w] = input.readFloat() * scale; + weights[w + 1] = input.readFloat() * scale; + weights[w + 2] = input.readFloat(); + } + } + return new Vertices(bones, Utils.toFloatArray(weights), length); + } + readFloatArray(input, n, scale) { + const array = []; + if (scale === 1) { + for (let i = 0; i < n; i++) + array[i] = input.readFloat(); + } else { + for (let i = 0; i < n; i++) + array[i] = input.readFloat() * scale; + } + return array; + } + readShortArray(input, n) { + const array = []; + for (let i = 0; i < n; i++) + array[i] = input.readInt(true); + return array; + } + readAnimation(input, name, skeletonData, nonessential) { + input.readInt(true); + const timelines = []; + const scale = this.scale; + for (let i = 0, n = input.readInt(true); i < n; i++) { + const slotIndex = input.readInt(true); + for (let ii = 0, nn = input.readInt(true); ii < nn; ii++) { + const timelineType = input.readByte(); + const frameCount = input.readInt(true); + const frameLast = frameCount - 1; + switch (timelineType) { + case SLOT_ATTACHMENT: { + const timeline = new AttachmentTimeline(frameCount, slotIndex); + for (let frame = 0; frame < frameCount; frame++) + timeline.setFrame(frame, input.readFloat(), input.readStringRef()); + timelines.push(timeline); + break; + } + case SLOT_RGBA: { + const bezierCount = input.readInt(true); + const timeline = new RGBATimeline(frameCount, bezierCount, slotIndex); + let time = input.readFloat(); + let r = input.readUnsignedByte() / 255; + let g = input.readUnsignedByte() / 255; + let b = input.readUnsignedByte() / 255; + let a = input.readUnsignedByte() / 255; + for (let frame = 0, bezier = 0; ; frame++) { + timeline.setFrame(frame, time, r, g, b, a); + if (frame === frameLast) break; + const time2 = input.readFloat(); + const r2 = input.readUnsignedByte() / 255; + const g2 = input.readUnsignedByte() / 255; + const b2 = input.readUnsignedByte() / 255; + const a2 = input.readUnsignedByte() / 255; + switch (input.readByte()) { + case CURVE_STEPPED: + timeline.setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, r, r2, 1); + setBezier(input, timeline, bezier++, frame, 1, time, time2, g, g2, 1); + setBezier(input, timeline, bezier++, frame, 2, time, time2, b, b2, 1); + setBezier(input, timeline, bezier++, frame, 3, time, time2, a, a2, 1); + } + time = time2; + r = r2; + g = g2; + b = b2; + a = a2; + } + timelines.push(timeline); + break; + } + case SLOT_RGB: { + const bezierCount = input.readInt(true); + const timeline = new RGBTimeline(frameCount, bezierCount, slotIndex); + let time = input.readFloat(); + let r = input.readUnsignedByte() / 255; + let g = input.readUnsignedByte() / 255; + let b = input.readUnsignedByte() / 255; + for (let frame = 0, bezier = 0; ; frame++) { + timeline.setFrame(frame, time, r, g, b); + if (frame === frameLast) break; + const time2 = input.readFloat(); + const r2 = input.readUnsignedByte() / 255; + const g2 = input.readUnsignedByte() / 255; + const b2 = input.readUnsignedByte() / 255; + switch (input.readByte()) { + case CURVE_STEPPED: + timeline.setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, r, r2, 1); + setBezier(input, timeline, bezier++, frame, 1, time, time2, g, g2, 1); + setBezier(input, timeline, bezier++, frame, 2, time, time2, b, b2, 1); + } + time = time2; + r = r2; + g = g2; + b = b2; + } + timelines.push(timeline); + break; + } + case SLOT_RGBA2: { + const bezierCount = input.readInt(true); + const timeline = new RGBA2Timeline(frameCount, bezierCount, slotIndex); + let time = input.readFloat(); + let r = input.readUnsignedByte() / 255; + let g = input.readUnsignedByte() / 255; + let b = input.readUnsignedByte() / 255; + let a = input.readUnsignedByte() / 255; + let r2 = input.readUnsignedByte() / 255; + let g2 = input.readUnsignedByte() / 255; + let b2 = input.readUnsignedByte() / 255; + for (let frame = 0, bezier = 0; ; frame++) { + timeline.setFrame(frame, time, r, g, b, a, r2, g2, b2); + if (frame === frameLast) break; + const time2 = input.readFloat(); + const nr = input.readUnsignedByte() / 255; + const ng = input.readUnsignedByte() / 255; + const nb = input.readUnsignedByte() / 255; + const na = input.readUnsignedByte() / 255; + const nr2 = input.readUnsignedByte() / 255; + const ng2 = input.readUnsignedByte() / 255; + const nb2 = input.readUnsignedByte() / 255; + switch (input.readByte()) { + case CURVE_STEPPED: + timeline.setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, r, nr, 1); + setBezier(input, timeline, bezier++, frame, 1, time, time2, g, ng, 1); + setBezier(input, timeline, bezier++, frame, 2, time, time2, b, nb, 1); + setBezier(input, timeline, bezier++, frame, 3, time, time2, a, na, 1); + setBezier(input, timeline, bezier++, frame, 4, time, time2, r2, nr2, 1); + setBezier(input, timeline, bezier++, frame, 5, time, time2, g2, ng2, 1); + setBezier(input, timeline, bezier++, frame, 6, time, time2, b2, nb2, 1); + } + time = time2; + r = nr; + g = ng; + b = nb; + a = na; + r2 = nr2; + g2 = ng2; + b2 = nb2; + } + timelines.push(timeline); + break; + } + case SLOT_RGB2: { + const bezierCount = input.readInt(true); + const timeline = new RGB2Timeline(frameCount, bezierCount, slotIndex); + let time = input.readFloat(); + let r = input.readUnsignedByte() / 255; + let g = input.readUnsignedByte() / 255; + let b = input.readUnsignedByte() / 255; + let r2 = input.readUnsignedByte() / 255; + let g2 = input.readUnsignedByte() / 255; + let b2 = input.readUnsignedByte() / 255; + for (let frame = 0, bezier = 0; ; frame++) { + timeline.setFrame(frame, time, r, g, b, r2, g2, b2); + if (frame === frameLast) break; + const time2 = input.readFloat(); + const nr = input.readUnsignedByte() / 255; + const ng = input.readUnsignedByte() / 255; + const nb = input.readUnsignedByte() / 255; + const nr2 = input.readUnsignedByte() / 255; + const ng2 = input.readUnsignedByte() / 255; + const nb2 = input.readUnsignedByte() / 255; + switch (input.readByte()) { + case CURVE_STEPPED: + timeline.setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, r, nr, 1); + setBezier(input, timeline, bezier++, frame, 1, time, time2, g, ng, 1); + setBezier(input, timeline, bezier++, frame, 2, time, time2, b, nb, 1); + setBezier(input, timeline, bezier++, frame, 3, time, time2, r2, nr2, 1); + setBezier(input, timeline, bezier++, frame, 4, time, time2, g2, ng2, 1); + setBezier(input, timeline, bezier++, frame, 5, time, time2, b2, nb2, 1); + } + time = time2; + r = nr; + g = ng; + b = nb; + r2 = nr2; + g2 = ng2; + b2 = nb2; + } + timelines.push(timeline); + break; + } + case SLOT_ALPHA: { + const timeline = new AlphaTimeline(frameCount, input.readInt(true), slotIndex); + let time = input.readFloat(), a = input.readUnsignedByte() / 255; + for (let frame = 0, bezier = 0; ; frame++) { + timeline.setFrame(frame, time, a); + if (frame === frameLast) break; + const time2 = input.readFloat(); + const a2 = input.readUnsignedByte() / 255; + switch (input.readByte()) { + case CURVE_STEPPED: + timeline.setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, a, a2, 1); + } + time = time2; + a = a2; + } + timelines.push(timeline); + } + } + } + } + for (let i = 0, n = input.readInt(true); i < n; i++) { + const boneIndex = input.readInt(true); + for (let ii = 0, nn = input.readInt(true); ii < nn; ii++) { + const type = input.readByte(), frameCount = input.readInt(true); + if (type === BONE_INHERIT) { + const timeline = new InheritTimeline(frameCount, boneIndex); + for (let frame = 0; frame < frameCount; frame++) { + timeline.setFrame(frame, input.readFloat(), input.readByte()); + } + timelines.push(timeline); + continue; + } + const bezierCount = input.readInt(true); + switch (type) { + case BONE_ROTATE: + readTimeline(input, timelines, new RotateTimeline(frameCount, bezierCount, boneIndex), 1); + break; + case BONE_TRANSLATE: + readTimeline(input, timelines, new TranslateTimeline(frameCount, bezierCount, boneIndex), scale); + break; + case BONE_TRANSLATEX: + readTimeline(input, timelines, new TranslateXTimeline(frameCount, bezierCount, boneIndex), scale); + break; + case BONE_TRANSLATEY: + readTimeline(input, timelines, new TranslateYTimeline(frameCount, bezierCount, boneIndex), scale); + break; + case BONE_SCALE: + readTimeline(input, timelines, new ScaleTimeline(frameCount, bezierCount, boneIndex), 1); + break; + case BONE_SCALEX: + readTimeline(input, timelines, new ScaleXTimeline(frameCount, bezierCount, boneIndex), 1); + break; + case BONE_SCALEY: + readTimeline(input, timelines, new ScaleYTimeline(frameCount, bezierCount, boneIndex), 1); + break; + case BONE_SHEAR: + readTimeline(input, timelines, new ShearTimeline(frameCount, bezierCount, boneIndex), 1); + break; + case BONE_SHEARX: + readTimeline(input, timelines, new ShearXTimeline(frameCount, bezierCount, boneIndex), 1); + break; + case BONE_SHEARY: + readTimeline(input, timelines, new ShearYTimeline(frameCount, bezierCount, boneIndex), 1); + break; + } + } + } + for (let i = 0, n = input.readInt(true); i < n; i++) { + const index = input.readInt(true), frameCount = input.readInt(true), frameLast = frameCount - 1; + const timeline = new IkConstraintTimeline(frameCount, input.readInt(true), index); + let flags = input.readByte(); + let time = input.readFloat(), mix = (flags & 1) !== 0 ? (flags & 2) !== 0 ? input.readFloat() : 1 : 0; + let softness = (flags & 4) !== 0 ? input.readFloat() * scale : 0; + for (let frame = 0, bezier = 0; ; frame++) { + timeline.setFrame(frame, time, mix, softness, (flags & 8) !== 0 ? 1 : -1, (flags & 16) !== 0, (flags & 32) !== 0); + if (frame === frameLast) break; + flags = input.readByte(); + const time2 = input.readFloat(), mix2 = (flags & 1) !== 0 ? (flags & 2) !== 0 ? input.readFloat() : 1 : 0; + const softness2 = (flags & 4) !== 0 ? input.readFloat() * scale : 0; + if ((flags & 64) !== 0) { + timeline.setStepped(frame); + } else if ((flags & 128) !== 0) { + setBezier(input, timeline, bezier++, frame, 0, time, time2, mix, mix2, 1); + setBezier(input, timeline, bezier++, frame, 1, time, time2, softness, softness2, scale); + } + time = time2; + mix = mix2; + softness = softness2; + } + timelines.push(timeline); + } + for (let i = 0, n = input.readInt(true); i < n; i++) { + const index = input.readInt(true), frameCount = input.readInt(true), frameLast = frameCount - 1; + const timeline = new TransformConstraintTimeline(frameCount, input.readInt(true), index); + let time = input.readFloat(), mixRotate = input.readFloat(), mixX = input.readFloat(), mixY = input.readFloat(), mixScaleX = input.readFloat(), mixScaleY = input.readFloat(), mixShearY = input.readFloat(); + for (let frame = 0, bezier = 0; ; frame++) { + timeline.setFrame(frame, time, mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY); + if (frame === frameLast) break; + const time2 = input.readFloat(), mixRotate2 = input.readFloat(), mixX2 = input.readFloat(), mixY2 = input.readFloat(), mixScaleX2 = input.readFloat(), mixScaleY2 = input.readFloat(), mixShearY2 = input.readFloat(); + switch (input.readByte()) { + case CURVE_STEPPED: + timeline.setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, mixRotate, mixRotate2, 1); + setBezier(input, timeline, bezier++, frame, 1, time, time2, mixX, mixX2, 1); + setBezier(input, timeline, bezier++, frame, 2, time, time2, mixY, mixY2, 1); + setBezier(input, timeline, bezier++, frame, 3, time, time2, mixScaleX, mixScaleX2, 1); + setBezier(input, timeline, bezier++, frame, 4, time, time2, mixScaleY, mixScaleY2, 1); + setBezier(input, timeline, bezier++, frame, 5, time, time2, mixShearY, mixShearY2, 1); + } + time = time2; + mixRotate = mixRotate2; + mixX = mixX2; + mixY = mixY2; + mixScaleX = mixScaleX2; + mixScaleY = mixScaleY2; + mixShearY = mixShearY2; + } + timelines.push(timeline); + } + for (let i = 0, n = input.readInt(true); i < n; i++) { + const index = input.readInt(true); + const data = skeletonData.constraints[index]; + for (let ii = 0, nn = input.readInt(true); ii < nn; ii++) { + const type = input.readByte(), frameCount = input.readInt(true), bezierCount = input.readInt(true); + switch (type) { + case PATH_POSITION: + readTimeline( + input, + timelines, + new PathConstraintPositionTimeline(frameCount, bezierCount, index), + data.positionMode === 0 /* Fixed */ ? scale : 1 + ); + break; + case PATH_SPACING: + readTimeline( + input, + timelines, + new PathConstraintSpacingTimeline(frameCount, bezierCount, index), + data.spacingMode === 0 /* Length */ || data.spacingMode === 1 /* Fixed */ ? scale : 1 + ); + break; + case PATH_MIX: { + const timeline = new PathConstraintMixTimeline(frameCount, bezierCount, index); + let time = input.readFloat(), mixRotate = input.readFloat(), mixX = input.readFloat(), mixY = input.readFloat(); + for (let frame = 0, bezier = 0, frameLast = timeline.getFrameCount() - 1; ; frame++) { + timeline.setFrame(frame, time, mixRotate, mixX, mixY); + if (frame === frameLast) break; + const time2 = input.readFloat(), mixRotate2 = input.readFloat(), mixX2 = input.readFloat(), mixY2 = input.readFloat(); + switch (input.readByte()) { + case CURVE_STEPPED: + timeline.setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, mixRotate, mixRotate2, 1); + setBezier(input, timeline, bezier++, frame, 1, time, time2, mixX, mixX2, 1); + setBezier(input, timeline, bezier++, frame, 2, time, time2, mixY, mixY2, 1); + } + time = time2; + mixRotate = mixRotate2; + mixX = mixX2; + mixY = mixY2; + } + timelines.push(timeline); + } + } + } + } + for (let i = 0, n = input.readInt(true); i < n; i++) { + const index = input.readInt(true) - 1; + for (let ii = 0, nn = input.readInt(true); ii < nn; ii++) { + const type = input.readByte(), frameCount = input.readInt(true); + if (type === PHYSICS_RESET) { + const timeline = new PhysicsConstraintResetTimeline(frameCount, index); + for (let frame = 0; frame < frameCount; frame++) + timeline.setFrame(frame, input.readFloat()); + timelines.push(timeline); + continue; + } + const bezierCount = input.readInt(true); + switch (type) { + case PHYSICS_INERTIA: + readTimeline(input, timelines, new PhysicsConstraintInertiaTimeline(frameCount, bezierCount, index), 1); + break; + case PHYSICS_STRENGTH: + readTimeline(input, timelines, new PhysicsConstraintStrengthTimeline(frameCount, bezierCount, index), 1); + break; + case PHYSICS_DAMPING: + readTimeline(input, timelines, new PhysicsConstraintDampingTimeline(frameCount, bezierCount, index), 1); + break; + case PHYSICS_MASS: + readTimeline(input, timelines, new PhysicsConstraintMassTimeline(frameCount, bezierCount, index), 1); + break; + case PHYSICS_WIND: + readTimeline(input, timelines, new PhysicsConstraintWindTimeline(frameCount, bezierCount, index), 1); + break; + case PHYSICS_GRAVITY: + readTimeline(input, timelines, new PhysicsConstraintGravityTimeline(frameCount, bezierCount, index), 1); + break; + case PHYSICS_MIX: + readTimeline(input, timelines, new PhysicsConstraintMixTimeline(frameCount, bezierCount, index), 1); + break; + default: + throw new Error("Unknown physics timeline type."); + } + } + } + for (let i = 0, n = input.readInt(true); i < n; i++) { + const index = input.readInt(true); + for (let ii = 0, nn = input.readInt(true); ii < nn; ii++) { + const type = input.readByte(), frameCount = input.readInt(true), bezierCount = input.readInt(true); + switch (type) { + case SLIDER_TIME: + readTimeline(input, timelines, new SliderTimeline(frameCount, bezierCount, index), 1); + break; + case SLIDER_MIX: + readTimeline(input, timelines, new SliderMixTimeline(frameCount, bezierCount, index), 1); + break; + default: + throw new Error(`Uknown slider type: ${type}`); + } + } + } + for (let i = 0, n = input.readInt(true); i < n; i++) { + const skin = skeletonData.skins[input.readInt(true)]; + for (let ii = 0, nn = input.readInt(true); ii < nn; ii++) { + const slotIndex = input.readInt(true); + for (let iii = 0, nnn = input.readInt(true); iii < nnn; iii++) { + const attachmentName = input.readStringRef(); + if (!attachmentName) throw new Error("attachmentName must not be null."); + const attachment = skin.getAttachment(slotIndex, attachmentName); + const timelineType = input.readByte(); + const frameCount = input.readInt(true); + const frameLast = frameCount - 1; + switch (timelineType) { + case ATTACHMENT_DEFORM: { + const vertexAttachment = attachment; + const weighted = vertexAttachment.bones; + const vertices = vertexAttachment.vertices; + const deformLength = weighted ? vertices.length / 3 * 2 : vertices.length; + const bezierCount = input.readInt(true); + const timeline = new DeformTimeline(frameCount, bezierCount, slotIndex, vertexAttachment); + let time = input.readFloat(); + for (let frame = 0, bezier = 0; ; frame++) { + let deform; + let end = input.readInt(true); + if (end === 0) + deform = weighted ? Utils.newFloatArray(deformLength) : vertices; + else { + deform = Utils.newFloatArray(deformLength); + const start = input.readInt(true); + end += start; + if (scale === 1) { + for (let v = start; v < end; v++) + deform[v] = input.readFloat(); + } else { + for (let v = start; v < end; v++) + deform[v] = input.readFloat() * scale; + } + if (!weighted) { + for (let v = 0, vn = deform.length; v < vn; v++) + deform[v] += vertices[v]; + } + } + timeline.setFrame(frame, time, deform); + if (frame === frameLast) break; + const time2 = input.readFloat(); + switch (input.readByte()) { + case CURVE_STEPPED: + timeline.setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, 0, 1, 1); + } + time = time2; + } + timelines.push(timeline); + break; + } + case ATTACHMENT_SEQUENCE: { + const timeline = new SequenceTimeline(frameCount, slotIndex, attachment); + for (let frame = 0; frame < frameCount; frame++) { + const time = input.readFloat(); + const modeAndIndex = input.readInt32(); + timeline.setFrame( + frame, + time, + SequenceModeValues[modeAndIndex & 15], + modeAndIndex >> 4, + input.readFloat() + ); + } + timelines.push(timeline); + break; + } + } + } + } + } + const slotCount = skeletonData.slots.length; + const drawOrderCount = input.readInt(true); + if (drawOrderCount > 0) { + const timeline = new DrawOrderTimeline(drawOrderCount); + for (let i = 0; i < drawOrderCount; i++) + timeline.setFrame(i, input.readFloat(), readDrawOrder(input, slotCount)); + timelines.push(timeline); + } + const folderCount = input.readInt(true); + for (let i = 0; i < folderCount; i++) { + const folderSlotCount = input.readInt(true); + const folderSlots = new Array(folderSlotCount); + for (let ii = 0; ii < folderSlotCount; ii++) + folderSlots[ii] = input.readInt(true); + const keyCount = input.readInt(true); + const timeline = new DrawOrderFolderTimeline(keyCount, folderSlots, slotCount); + for (let ii = 0; ii < keyCount; ii++) + timeline.setFrame(ii, input.readFloat(), readDrawOrder(input, folderSlotCount)); + timelines.push(timeline); + } + const eventCount = input.readInt(true); + if (eventCount > 0) { + const timeline = new EventTimeline(eventCount); + for (let i = 0; i < eventCount; i++) { + const time = input.readFloat(); + const eventData = skeletonData.events[input.readInt(true)]; + const event = new Event(time, eventData); + event.intValue = input.readInt(false); + event.floatValue = input.readFloat(); + event.stringValue = input.readString(); + if (event.stringValue == null) event.stringValue = eventData.setupPose.stringValue; + if (event.data.audioPath) { + event.volume = input.readFloat(); + event.balance = input.readFloat(); + } + timeline.setFrame(i, event); + } + timelines.push(timeline); + } + let duration = 0; + for (let i = 0, n = timelines.length; i < n; i++) + duration = Math.max(duration, timelines[i].getDuration()); + const animation = new Animation(name, timelines, duration); + if (nonessential) Color.rgba8888ToColor(animation.color, input.readInt32()); + return animation; + } + }; + var BinaryInput = class { + constructor(data, strings = [], index = 0, buffer = new DataView(data instanceof ArrayBuffer ? data : data.buffer)) { + this.strings = strings; + this.index = index; + this.buffer = buffer; + } + readByte() { + return this.buffer.getInt8(this.index++); + } + readUnsignedByte() { + return this.buffer.getUint8(this.index++); + } + readShort() { + const value = this.buffer.getInt16(this.index); + this.index += 2; + return value; + } + readInt32() { + const value = this.buffer.getInt32(this.index); + this.index += 4; + return value; + } + readInt(optimizePositive) { + let b = this.readByte(); + let result = b & 127; + if ((b & 128) !== 0) { + b = this.readByte(); + result |= (b & 127) << 7; + if ((b & 128) !== 0) { + b = this.readByte(); + result |= (b & 127) << 14; + if ((b & 128) !== 0) { + b = this.readByte(); + result |= (b & 127) << 21; + if ((b & 128) !== 0) { + b = this.readByte(); + result |= (b & 127) << 28; + } + } + } + } + return optimizePositive ? result : result >>> 1 ^ -(result & 1); + } + readStringRef() { + const index = this.readInt(true); + return index === 0 ? null : this.strings[index - 1]; + } + readString() { + let byteCount = this.readInt(true); + switch (byteCount) { + case 0: + return null; + case 1: + return ""; + } + byteCount--; + let chars = ""; + for (let i = 0; i < byteCount; ) { + const b = this.readUnsignedByte(); + switch (b >> 4) { + case 12: + case 13: + chars += String.fromCharCode((b & 31) << 6 | this.readByte() & 63); + i += 2; + break; + case 14: + chars += String.fromCharCode((b & 15) << 12 | (this.readByte() & 63) << 6 | this.readByte() & 63); + i += 3; + break; + default: + chars += String.fromCharCode(b); + i++; + } + } + return chars; + } + readFloat() { + const value = this.buffer.getFloat32(this.index); + this.index += 4; + return value; + } + readBoolean() { + return this.readByte() !== 0; + } + }; + var LinkedMesh = class { + source; + skinIndex; + slotIndex; + sourceIndex; + mesh; + inheritTimelines; + constructor(mesh, skinIndex, slotIndex, sourceIndex, source, inheritTimelines) { + this.mesh = mesh; + this.skinIndex = skinIndex; + this.slotIndex = slotIndex; + this.sourceIndex = sourceIndex; + this.source = source; + this.inheritTimelines = inheritTimelines; + } + }; + var Vertices = class { + constructor(bones = null, vertices, length = 0) { + this.bones = bones; + this.vertices = vertices; + this.length = length; + } + }; + function readTimeline(input, timelines, timeline, scale) { + if (timeline instanceof CurveTimeline1) + readTimeline1(input, timelines, timeline, scale); + else + readTimeline2(input, timelines, timeline, scale); + } + function readTimeline1(input, timelines, timeline, scale) { + let time = input.readFloat(), value = input.readFloat() * scale; + for (let frame = 0, bezier = 0, frameLast = timeline.getFrameCount() - 1; ; frame++) { + timeline.setFrame(frame, time, value); + if (frame === frameLast) break; + const time2 = input.readFloat(), value2 = input.readFloat() * scale; + switch (input.readByte()) { + case CURVE_STEPPED: + timeline.setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, value, value2, scale); + } + time = time2; + value = value2; + } + timelines.push(timeline); + } + function readTimeline2(input, timelines, timeline, scale) { + let time = input.readFloat(), value1 = input.readFloat() * scale, value2 = input.readFloat() * scale; + for (let frame = 0, bezier = 0, frameLast = timeline.getFrameCount() - 1; ; frame++) { + timeline.setFrame(frame, time, value1, value2); + if (frame === frameLast) break; + const time2 = input.readFloat(), nvalue1 = input.readFloat() * scale, nvalue2 = input.readFloat() * scale; + switch (input.readByte()) { + case CURVE_STEPPED: + timeline.setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, value1, nvalue1, scale); + setBezier(input, timeline, bezier++, frame, 1, time, time2, value2, nvalue2, scale); + } + time = time2; + value1 = nvalue1; + value2 = nvalue2; + } + timelines.push(timeline); + } + function readDrawOrder(input, slotCount) { + const changeCount = input.readInt(true); + if (changeCount === 0) return null; + const drawOrder = new Array(slotCount).fill(-1); + const unchanged = new Array(slotCount - changeCount); + let originalIndex = 0, unchangedIndex = 0; + for (let i = 0; i < changeCount; i++) { + const slotIndex = input.readInt(true); + while (originalIndex !== slotIndex) + unchanged[unchangedIndex++] = originalIndex++; + drawOrder[originalIndex + input.readInt(true)] = originalIndex++; + } + while (originalIndex < slotCount) + unchanged[unchangedIndex++] = originalIndex++; + for (let i = slotCount - 1; i >= 0; i--) + if (drawOrder[i] === -1) drawOrder[i] = unchanged[--unchangedIndex]; + return drawOrder; + } + function setBezier(input, timeline, bezier, frame, value, time1, time2, value1, value2, scale) { + timeline.setBezier(bezier, frame, value, time1, value1, input.readFloat(), input.readFloat() * scale, input.readFloat(), input.readFloat() * scale, time2, value2); + } + var BONE_ROTATE = 0; + var BONE_TRANSLATE = 1; + var BONE_TRANSLATEX = 2; + var BONE_TRANSLATEY = 3; + var BONE_SCALE = 4; + var BONE_SCALEX = 5; + var BONE_SCALEY = 6; + var BONE_SHEAR = 7; + var BONE_SHEARX = 8; + var BONE_SHEARY = 9; + var BONE_INHERIT = 10; + var SLOT_ATTACHMENT = 0; + var SLOT_RGBA = 1; + var SLOT_RGB = 2; + var SLOT_RGBA2 = 3; + var SLOT_RGB2 = 4; + var SLOT_ALPHA = 5; + var CONSTRAINT_IK = 0; + var CONSTRAINT_PATH = 1; + var CONSTRAINT_TRANSFORM = 2; + var CONSTRAINT_PHYSICS = 3; + var CONSTRAINT_SLIDER = 4; + var ATTACHMENT_DEFORM = 0; + var ATTACHMENT_SEQUENCE = 1; + var PATH_POSITION = 0; + var PATH_SPACING = 1; + var PATH_MIX = 2; + var PHYSICS_INERTIA = 0; + var PHYSICS_STRENGTH = 1; + var PHYSICS_DAMPING = 2; + var PHYSICS_MASS = 4; + var PHYSICS_WIND = 5; + var PHYSICS_GRAVITY = 6; + var PHYSICS_MIX = 7; + var PHYSICS_RESET = 8; + var SLIDER_TIME = 0; + var SLIDER_MIX = 1; + var CURVE_STEPPED = 1; + var CURVE_BEZIER = 2; + + // spine-core/src/SkeletonBounds.ts + var SkeletonBounds = class { + /** The left edge of the axis aligned bounding box. */ + minX = 0; + /** The bottom edge of the axis aligned bounding box. */ + minY = 0; + /** The right edge of the axis aligned bounding box. */ + maxX = 0; + /** The top edge of the axis aligned bounding box. */ + maxY = 0; + /** The visible bounding boxes. */ + boundingBoxes = []; + /** The world vertices for the bounding box polygons. */ + polygons = []; + polygonPool = new Pool(() => { + return Utils.newFloatArray(16); + }); + /** Clears any previous polygons, finds all visible bounding box attachments, and computes the world vertices for each bounding + * box's polygon. + * @param updateAabb If true, the axis aligned bounding box containing all the polygons is computed. If false, the + * SkeletonBounds AABB methods will always return true. */ + update(skeleton, updateAabb) { + if (!skeleton) throw new Error("skeleton cannot be null."); + const boundingBoxes = this.boundingBoxes; + const polygons = this.polygons; + const polygonPool = this.polygonPool; + const slots = skeleton.slots; + const slotCount = slots.length; + boundingBoxes.length = 0; + polygonPool.freeAll(polygons); + polygons.length = 0; + for (let i = 0; i < slotCount; i++) { + const slot = slots[i]; + if (!slot.bone.active) continue; + const attachment = slot.appliedPose.attachment; + if (attachment instanceof BoundingBoxAttachment) { + boundingBoxes.push(attachment); + let polygon = polygonPool.obtain(); + if (polygon.length !== attachment.worldVerticesLength) { + polygon = Utils.newFloatArray(attachment.worldVerticesLength); + } + polygons.push(polygon); + attachment.computeWorldVertices(skeleton, slot, 0, attachment.worldVerticesLength, polygon, 0, 2); + } + } + if (updateAabb) { + this.aabbCompute(); + } else { + this.minX = Number.POSITIVE_INFINITY; + this.minY = Number.POSITIVE_INFINITY; + this.maxX = Number.NEGATIVE_INFINITY; + this.maxY = Number.NEGATIVE_INFINITY; + } + } + aabbCompute() { + let minX = Number.POSITIVE_INFINITY, minY = Number.POSITIVE_INFINITY, maxX = Number.NEGATIVE_INFINITY, maxY = Number.NEGATIVE_INFINITY; + const polygons = this.polygons; + for (let i = 0, n = polygons.length; i < n; i++) { + const polygon = polygons[i]; + const vertices = polygon; + for (let ii = 0, nn = polygon.length; ii < nn; ii += 2) { + const x = vertices[ii]; + const y = vertices[ii + 1]; + minX = Math.min(minX, x); + minY = Math.min(minY, y); + maxX = Math.max(maxX, x); + maxY = Math.max(maxY, y); + } + } + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + /** Returns true if the axis aligned bounding box contains the point. */ + aabbContainsPoint(x, y) { + return x >= this.minX && x <= this.maxX && y >= this.minY && y <= this.maxY; + } + /** Returns true if the axis aligned bounding box intersects the line segment. */ + aabbIntersectsSegment(x1, y1, x2, y2) { + const minX = this.minX; + const minY = this.minY; + const maxX = this.maxX; + const maxY = this.maxY; + if (x1 <= minX && x2 <= minX || y1 <= minY && y2 <= minY || x1 >= maxX && x2 >= maxX || y1 >= maxY && y2 >= maxY) + return false; + const m = (y2 - y1) / (x2 - x1); + let y = m * (minX - x1) + y1; + if (y > minY && y < maxY) return true; + y = m * (maxX - x1) + y1; + if (y > minY && y < maxY) return true; + let x = (minY - y1) / m + x1; + if (x > minX && x < maxX) return true; + x = (maxY - y1) / m + x1; + if (x > minX && x < maxX) return true; + return false; + } + /** Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds. */ + aabbIntersectsSkeleton(bounds) { + return this.minX < bounds.maxX && this.maxX > bounds.minX && this.minY < bounds.maxY && this.maxY > bounds.minY; + } + /** Returns the first bounding box attachment that contains the point, or null. When doing many checks, it is usually more + * efficient to only call this method if {@link aabbContainsPoint} returns true. */ + containsPoint(x, y) { + const polygons = this.polygons; + for (let i = 0, n = polygons.length; i < n; i++) + if (this.containsPointPolygon(polygons[i], x, y)) return this.boundingBoxes[i]; + return null; + } + /** Returns true if the polygon contains the point. */ + containsPointPolygon(polygon, x, y) { + const vertices = polygon; + const nn = polygon.length; + let prevIndex = nn - 2; + let inside = false; + for (let ii = 0; ii < nn; ii += 2) { + const vertexY = vertices[ii + 1]; + const prevY = vertices[prevIndex + 1]; + if (vertexY < y && prevY >= y || prevY < y && vertexY >= y) { + const vertexX = vertices[ii]; + if (vertexX + (y - vertexY) / (prevY - vertexY) * (vertices[prevIndex] - vertexX) < x) inside = !inside; + } + prevIndex = ii; + } + return inside; + } + /** Returns the first bounding box attachment that contains any part of the line segment, or null. When doing many checks, it + * is usually more efficient to only call this method if {@link aabbIntersectsSegment} returns + * true. */ + intersectsSegment(x1, y1, x2, y2) { + const polygons = this.polygons; + for (let i = 0, n = polygons.length; i < n; i++) + if (this.intersectsSegmentPolygon(polygons[i], x1, y1, x2, y2)) return this.boundingBoxes[i]; + return null; + } + /** Returns true if the polygon contains any part of the line segment. */ + intersectsSegmentPolygon(polygon, x1, y1, x2, y2) { + const vertices = polygon; + const nn = polygon.length; + const width12 = x1 - x2, height12 = y1 - y2; + const det1 = x1 * y2 - y1 * x2; + let x3 = vertices[nn - 2], y3 = vertices[nn - 1]; + for (let ii = 0; ii < nn; ii += 2) { + const x4 = vertices[ii], y4 = vertices[ii + 1]; + const det2 = x3 * y4 - y3 * x4; + const width34 = x3 - x4, height34 = y3 - y4; + const det3 = width12 * height34 - height12 * width34; + const x = (det1 * width34 - width12 * det2) / det3; + if ((x >= x3 && x <= x4 || x >= x4 && x <= x3) && (x >= x1 && x <= x2 || x >= x2 && x <= x1)) { + const y = (det1 * height34 - height12 * det2) / det3; + if ((y >= y3 && y <= y4 || y >= y4 && y <= y3) && (y >= y1 && y <= y2 || y >= y2 && y <= y1)) return true; + } + x3 = x4; + y3 = y4; + } + return false; + } + /** Returns the polygon for the specified bounding box, or null. */ + getPolygon(boundingBox) { + if (!boundingBox) throw new Error("boundingBox cannot be null."); + const index = this.boundingBoxes.indexOf(boundingBox); + return index === -1 ? null : this.polygons[index]; + } + /** The width of the axis aligned bounding box. */ + getWidth() { + return this.maxX - this.minX; + } + /** The height of the axis aligned bounding box. */ + getHeight() { + return this.maxY - this.minY; + } + }; + + // spine-core/src/Triangulator.ts + var Triangulator = class _Triangulator { + convexPolygons = []; + convexPolygonsIndices = []; + indicesArray = []; + isConcaveArray = []; + triangles = []; + polygonPool = new Pool(() => { + return []; + }); + polygonIndicesPool = new Pool(() => { + return []; + }); + triangulate(verticesArray) { + const vertices = verticesArray; + let vertexCount = verticesArray.length >> 1; + const indices = this.indicesArray; + indices.length = 0; + for (let i = 0; i < vertexCount; i++) + indices[i] = i; + const isConcave = this.isConcaveArray; + isConcave.length = 0; + for (let i = 0; i < vertexCount; i++) + isConcave[i] = _Triangulator.isConcave(i, vertexCount, vertices, indices); + const triangles = this.triangles; + triangles.length = 0; + while (vertexCount > 3) { + let previous = vertexCount - 1, i = 0, next = 1; + while (true) { + outer: + if (!isConcave[i]) { + const p1 = indices[previous] << 1, p2 = indices[i] << 1, p3 = indices[next] << 1; + const p1x = vertices[p1], p1y = vertices[p1 + 1]; + const p2x = vertices[p2], p2y = vertices[p2 + 1]; + const p3x = vertices[p3], p3y = vertices[p3 + 1]; + for (let ii = next + 1 < vertexCount ? next + 1 : 0; ii !== previous; ) { + if (isConcave[ii]) { + const v = indices[ii] << 1; + const vx = vertices[v], vy = vertices[v + 1]; + if (_Triangulator.positiveArea(p3x, p3y, p1x, p1y, vx, vy) && _Triangulator.positiveArea(p1x, p1y, p2x, p2y, vx, vy) && _Triangulator.positiveArea(p2x, p2y, p3x, p3y, vx, vy)) break outer; + } + if (++ii === vertexCount) ii = 0; + } + break; + } + if (next === 0) { + do { + if (!isConcave[i]) break; + i--; + } while (i > 0); + previous = i > 0 ? i - 1 : vertexCount - 1; + next = i + 1 < vertexCount ? i + 1 : 0; + break; + } + previous = i; + i = next; + if (++next === vertexCount) next = 0; + } + triangles.push(indices[previous], indices[i], indices[next]); + indices.splice(i, 1); + isConcave.splice(i, 1); + vertexCount--; + const previousIndex = i > 0 ? i - 1 : vertexCount - 1; + const nextIndex = i < vertexCount ? i : 0; + isConcave[previousIndex] = _Triangulator.isConcave(previousIndex, vertexCount, vertices, indices); + isConcave[nextIndex] = _Triangulator.isConcave(nextIndex, vertexCount, vertices, indices); + } + if (vertexCount === 3) triangles.push(indices[2], indices[0], indices[1]); + return triangles; + } + decompose(verticesArray, triangles) { + const vertices = verticesArray; + const convexPolygons = this.convexPolygons; + this.polygonPool.freeAll(convexPolygons); + convexPolygons.length = 0; + const convexPolygonsIndices = this.convexPolygonsIndices; + this.polygonIndicesPool.freeAll(convexPolygonsIndices); + convexPolygonsIndices.length = 0; + let polygonIndices = this.polygonIndicesPool.obtain(); + polygonIndices.length = 0; + let polygon = this.polygonPool.obtain(); + polygon.length = 0; + let fanBaseIndex = -1, lastWinding = 0; + for (let i = 0, n = triangles.length; i < n; i += 3) { + const t1 = triangles[i] << 1, t2 = triangles[i + 1] << 1, t3 = triangles[i + 2] << 1; + const x1 = vertices[t1], y1 = vertices[t1 + 1]; + const x2 = vertices[t2], y2 = vertices[t2 + 1]; + const x3 = vertices[t3], y3 = vertices[t3 + 1]; + if (fanBaseIndex === t1) { + const o = polygon.length - 4; + if (_Triangulator.winding(polygon[o], polygon[o + 1], polygon[o + 2], polygon[o + 3], x3, y3) === lastWinding && _Triangulator.winding(x3, y3, polygon[0], polygon[1], polygon[2], polygon[3]) === lastWinding) { + polygon.push(x3, y3); + polygonIndices.push(t3); + continue; + } + } + if (polygon.length > 0) { + convexPolygons.push(polygon); + convexPolygonsIndices.push(polygonIndices); + polygon = this.polygonPool.obtain(); + polygonIndices = this.polygonIndicesPool.obtain(); + } + polygon.length = 0; + polygon.push(x1, y1, x2, y2); + polygon.push(x3, y3); + polygonIndices.length = 0; + polygonIndices.push(t1, t2, t3); + lastWinding = _Triangulator.winding(x1, y1, x2, y2, x3, y3); + fanBaseIndex = t1; + } + if (polygon.length > 0) { + convexPolygons.push(polygon); + convexPolygonsIndices.push(polygonIndices); + } + for (let i = 0, n = convexPolygons.length; i < n; i++) { + polygonIndices = convexPolygonsIndices[i]; + if (polygonIndices.length === 0) continue; + const firstIndex = polygonIndices[0]; + let lastIndex = polygonIndices[polygonIndices.length - 1]; + polygon = convexPolygons[i]; + const o = polygon.length - 4; + let prevPrevX = polygon[o], prevPrevY = polygon[o + 1]; + let prevX = polygon[o + 2], prevY = polygon[o + 3]; + const firstX = polygon[0], firstY = polygon[1]; + const secondX = polygon[2], secondY = polygon[3]; + const winding = _Triangulator.winding(prevPrevX, prevPrevY, prevX, prevY, firstX, firstY); + for (let ii = 0; ii < n; ii++) { + if (ii === i) continue; + const otherIndices = convexPolygonsIndices[ii]; + if (otherIndices.length !== 3) continue; + const otherFirstIndex = otherIndices[0]; + const otherSecondIndex = otherIndices[1]; + const otherLastIndex = otherIndices[2]; + const otherPoly = convexPolygons[ii]; + const x3 = otherPoly[otherPoly.length - 2], y3 = otherPoly[otherPoly.length - 1]; + if (otherFirstIndex !== firstIndex || otherSecondIndex !== lastIndex) continue; + if (_Triangulator.winding(prevPrevX, prevPrevY, prevX, prevY, x3, y3) === winding && _Triangulator.winding(x3, y3, firstX, firstY, secondX, secondY) === winding) { + otherPoly.length = 0; + otherIndices.length = 0; + polygon.push(x3, y3); + polygonIndices.push(otherLastIndex); + lastIndex = otherLastIndex; + prevPrevX = prevX; + prevPrevY = prevY; + prevX = x3; + prevY = y3; + ii = -1; + } + } + } + for (let i = convexPolygons.length - 1; i >= 0; i--) { + polygon = convexPolygons[i]; + if (polygon.length === 0) { + convexPolygons.splice(i, 1); + this.polygonPool.free(polygon); + polygonIndices = convexPolygonsIndices[i]; + convexPolygonsIndices.splice(i, 1); + this.polygonIndicesPool.free(polygonIndices); + } else + polygon.push(polygon[0], polygon[1]); + } + return convexPolygons; + } + static isConcave(index, vertexCount, vertices, indices) { + const previous = indices[index > 0 ? index - 1 : vertexCount - 1] << 1; + const current = indices[index] << 1; + const next = indices[index + 1 < vertexCount ? index + 1 : 0] << 1; + return !_Triangulator.positiveArea( + vertices[previous], + vertices[previous + 1], + vertices[current], + vertices[current + 1], + vertices[next], + vertices[next + 1] + ); + } + static positiveArea(p1x, p1y, p2x, p2y, p3x, p3y) { + return p1x * (p3y - p2y) + p2x * (p1y - p3y) + p3x * (p2y - p1y) >= 0; + } + static winding(p1x, p1y, p2x, p2y, p3x, p3y) { + return p1x * (p3y - p2y) + p2x * (p1y - p3y) + p3x * (p2y - p1y) >= 0 ? 1 : -1; + } + }; + + // spine-core/src/SkeletonClipping.ts + var SkeletonClipping = class { + triangulator = null; + clippingPolygon = []; + clippingPolygons = []; + clipOutput = []; + clippedVertices = []; + /** An empty array unless {@link clipTrianglesUnpacked} was used. **/ + clippedUVs = []; + clippedTriangles = []; + inverseVertices = []; + _clippedVerticesTyped = new Float32Array(1024); + _clippedUVsTyped = new Float32Array(1024); + _clippedTrianglesTyped = new Uint16Array(1024); + clippedVerticesTyped = new Float32Array(0); + clippedUVsTyped = new Float32Array(0); + clippedTrianglesTyped = new Uint16Array(0); + clippedVerticesLength = 0; + clippedUVsLength = 0; + clippedTrianglesLength = 0; + scratch = []; + inverse = false; + clipAttachment = null; + clipStart(skeleton, slot, clip) { + if (this.clipAttachment) return; + const n = clip.worldVerticesLength; + this.clipAttachment = clip; + this.inverse = clip.inverse; + const vertices = Utils.setArraySize(this.clippingPolygon, n); + clip.computeWorldVertices(skeleton, slot, 0, n, vertices, 0, 2); + const clippingPolygon = this.clippingPolygon; + const convex = this.makeClockwise(clippingPolygon); + if (convex || this.inverse || clip.convex) { + if (!convex) this.makeConvex(clippingPolygon); + this.clippingPolygon.push(clippingPolygon[0], clippingPolygon[1]); + this.clippingPolygons.push(clippingPolygon); + } else { + if (this.triangulator === null) this.triangulator = new Triangulator(); + this.clippingPolygons.push(...this.triangulator.decompose(clippingPolygon, this.triangulator.triangulate(clippingPolygon))); + } + } + clipEnd(slot) { + if (!this.clipAttachment) return; + if (slot && this.clipAttachment.endSlot !== slot.data) return; + this.clipAttachment = null; + this.clippingPolygons.length = 0; + } + isClipping() { + return this.clipAttachment != null; + } + clipTriangles(vertices, triangles, trianglesLength, uvs, light, dark, twoColor, stride) { + return uvs && light && dark && typeof twoColor === "boolean" && typeof stride === "number" ? this.clipTrianglesRender(vertices, triangles, trianglesLength, uvs, light, dark, twoColor, stride) : this.clipTrianglesNoRender(vertices, triangles, trianglesLength); + } + clipTrianglesNoRender(vertices, triangles, trianglesLength) { + const clippedVertices = this.clippedVertices; + clippedVertices.length = 0; + const clippedTriangles = this.clippedTriangles; + clippedTriangles.length = 0; + let index = 0; + if (this.inverse) { + const polygon = this.clippingPolygons[0]; + for (let i = 0; i < trianglesLength; i += 3) { + let t = triangles[i] << 1; + const x1 = vertices[t], y1 = vertices[t + 1]; + t = triangles[i + 1] << 1; + const x2 = vertices[t], y2 = vertices[t + 1]; + t = triangles[i + 2] << 1; + const x3 = vertices[t], y3 = vertices[t + 1]; + this.clipInverse(x1, y1, x2, y2, x3, y3, polygon); + const iv = this.inverseVertices; + for (let offset = 0, nn = this.inverseVertices.length; offset < nn; ) { + const polygonSize = iv[offset++]; + let vertexCount = polygonSize >> 1, s = clippedVertices.length; + const cv = Utils.setArraySize(clippedVertices, s + polygonSize); + Utils.arrayCopy(iv, offset, cv, s, polygonSize); + s = clippedTriangles.length; + const ct = Utils.setArraySize(clippedTriangles, s + 3 * (vertexCount - 2)); + for (let ii = 1; ii < vertexCount - 1; ii++, s += 3) { + ct[s] = index; + ct[s + 1] = index + ii; + ct[s + 2] = index + ii + 1; + } + index += vertexCount; + offset += polygonSize; + } + } + return true; + } + const clipOutput = this.clipOutput; + const polygons = this.clippingPolygons; + const polygonsCount = polygons.length; + let clipOutputItems = null; + for (let i = 0; i < trianglesLength; i += 3) { + let t = triangles[i] << 1; + const x1 = vertices[t], y1 = vertices[t + 1]; + t = triangles[i + 1] << 1; + const x2 = vertices[t], y2 = vertices[t + 1]; + t = triangles[i + 2] << 1; + const x3 = vertices[t], y3 = vertices[t + 1]; + for (let p = 0; p < polygonsCount; p++) { + let s = clippedVertices.length; + if (this.clip(x1, y1, x2, y2, x3, y3, polygons[p])) { + clipOutputItems = this.clipOutput; + const clipOutputLength = clipOutput.length; + if (clipOutputLength === 0) continue; + let clipOutputCount = clipOutputLength >> 1; + const cv = Utils.setArraySize(clippedVertices, s + clipOutputLength); + Utils.arrayCopy(clipOutputItems, 0, cv, s, clipOutputLength); + s = clippedTriangles.length; + const ct = Utils.setArraySize(clippedTriangles, s + 3 * (clipOutputCount - 2)); + clipOutputCount--; + for (let ii = 1; ii < clipOutputCount; ii++, s += 3) { + ct[s] = index; + ct[s + 1] = index + ii; + ct[s + 2] = index + ii + 1; + } + index += clipOutputCount; + } else { + const cv = Utils.setArraySize(clippedVertices, s + 3 * 2); + cv[s] = x1; + cv[s + 1] = y1; + cv[s + 2] = x2; + cv[s + 3] = y2; + cv[s + 4] = x3; + cv[s + 5] = y3; + s = clippedTriangles.length; + const ct = Utils.setArraySize(clippedTriangles, s + 3); + ct[s] = index; + ct[s + 1] = index + 1; + ct[s + 2] = index + 2; + index += 3; + break; + } + } + } + return clipOutputItems != null; + } + clipTrianglesRender(vertices, triangles, trianglesLength, uvs, light, dark, twoColor, stride) { + const clippedVertices = this.clippedVertices; + clippedVertices.length = 0; + const clippedTriangles = this.clippedTriangles; + clippedTriangles.length = 0; + let index = 0; + if (this.inverse) { + const polygon = this.clippingPolygons[0]; + for (let i = 0; i < trianglesLength; i += 3) { + let t0 = triangles[i], t1 = triangles[i + 1], t2 = triangles[i + 2]; + const x1 = vertices[t0 * stride], y1 = vertices[t0 * stride + 1]; + const x2 = vertices[t1 * stride], y2 = vertices[t1 * stride + 1]; + const x3 = vertices[t2 * stride], y3 = vertices[t2 * stride + 1]; + this.clipInverse(x1, y1, x2, y2, x3, y3, polygon); + const nn = this.inverseVertices.length; + if (nn === 0) continue; + const u1 = uvs[t0 <<= 1], v1 = uvs[t0 + 1]; + const u2 = uvs[t1 <<= 1], v2 = uvs[t1 + 1]; + const u3 = uvs[t2 <<= 1], v3 = uvs[t2 + 1]; + const d0 = y2 - y3, d1 = x3 - x2, d2 = x1 - x3, d4 = y3 - y1, d = 1 / (d0 * d2 + d1 * (y1 - y3)); + const iv = this.inverseVertices; + for (let offset = 0; offset < nn; ) { + const polygonSize = iv[offset++]; + const vertexCount = polygonSize >> 1; + let s = clippedVertices.length; + const cv = Utils.setArraySize(clippedVertices, s + vertexCount * stride); + for (let ii = 0; ii < polygonSize; ii += 2, s += stride) { + const x = iv[offset + ii], y = iv[offset + ii + 1]; + cv[s] = x; + cv[s + 1] = y; + cv[s + 2] = light.r; + cv[s + 3] = light.g; + cv[s + 4] = light.b; + cv[s + 5] = light.a; + const c0 = x - x3, c1 = y - y3, a = (d0 * c0 + d1 * c1) * d, b = (d4 * c0 + d2 * c1) * d, c = 1 - a - b; + cv[s + 6] = u1 * a + u2 * b + u3 * c; + cv[s + 7] = v1 * a + v2 * b + v3 * c; + if (twoColor) { + cv[s + 8] = dark.r; + cv[s + 9] = dark.g; + cv[s + 10] = dark.b; + cv[s + 11] = dark.a; + } + } + s = clippedTriangles.length; + const ct = Utils.setArraySize(clippedTriangles, s + 3 * (vertexCount - 2)); + for (let ii = 1; ii < vertexCount - 1; ii++, s += 3) { + ct[s] = index; + ct[s + 1] = index + ii; + ct[s + 2] = index + ii + 1; + } + index += vertexCount; + offset += polygonSize; + } + } + return true; + } + const clipOutput = this.clipOutput; + const polygons = this.clippingPolygons; + const polygonsCount = this.clippingPolygons.length; + let clipOutputItems = null; + for (let i = 0; i < trianglesLength; i += 3) { + let t = triangles[i]; + const x1 = vertices[t * stride], y1 = vertices[t * stride + 1]; + const u1 = uvs[t << 1], v1 = uvs[(t << 1) + 1]; + t = triangles[i + 1]; + const x2 = vertices[t * stride], y2 = vertices[t * stride + 1]; + const u2 = uvs[t << 1], v2 = uvs[(t << 1) + 1]; + t = triangles[i + 2]; + const x3 = vertices[t * stride], y3 = vertices[t * stride + 1]; + const u3 = uvs[t << 1], v3 = uvs[(t << 1) + 1]; + let d0 = 0, d1 = 0, d2 = 0, d4 = 0, d = 0; + for (let p = 0; p < polygonsCount; p++) { + let s = clippedVertices.length; + if (this.clip(x1, y1, x2, y2, x3, y3, polygons[p])) { + clipOutputItems = this.clipOutput; + const clipOutputLength = clipOutput.length; + if (clipOutputLength === 0) continue; + let clipOutputCount = clipOutputLength >> 1; + if (d === 0) { + d0 = y2 - y3; + d1 = x3 - x2; + d2 = x1 - x3; + d4 = y3 - y1; + d = 1 / (d0 * d2 - d1 * d4); + } + const cv = Utils.setArraySize(clippedVertices, s + clipOutputCount * stride); + for (let ii = 0; ii < clipOutputLength; ii += 2, s += stride) { + const x = clipOutputItems[ii], y = clipOutputItems[ii + 1]; + cv[s] = x; + cv[s + 1] = y; + cv[s + 2] = light.r; + cv[s + 3] = light.g; + cv[s + 4] = light.b; + cv[s + 5] = light.a; + const c0 = x - x3, c1 = y - y3, a = (d0 * c0 + d1 * c1) * d, b = (d4 * c0 + d2 * c1) * d, c = 1 - a - b; + cv[s + 6] = u1 * a + u2 * b + u3 * c; + cv[s + 7] = v1 * a + v2 * b + v3 * c; + if (twoColor) { + cv[s + 8] = dark.r; + cv[s + 9] = dark.g; + cv[s + 10] = dark.b; + cv[s + 11] = dark.a; + } + } + s = clippedTriangles.length; + const ct = Utils.setArraySize(clippedTriangles, s + 3 * (clipOutputCount - 2)); + clipOutputCount--; + for (let ii = 1; ii < clipOutputCount; ii++, s += 3) { + ct[s] = index; + ct[s + 1] = index + ii; + ct[s + 2] = index + ii + 1; + } + index += clipOutputCount + 1; + } else { + const cv = Utils.setArraySize(clippedVertices, s + 3 * stride); + cv[s] = x1; + cv[s + 1] = y1; + cv[s + 2] = light.r; + cv[s + 3] = light.g; + cv[s + 4] = light.b; + cv[s + 5] = light.a; + if (!twoColor) { + cv[s + 6] = u1; + cv[s + 7] = v1; + cv[s + 8] = x2; + cv[s + 9] = y2; + cv[s + 10] = light.r; + cv[s + 11] = light.g; + cv[s + 12] = light.b; + cv[s + 13] = light.a; + cv[s + 14] = u2; + cv[s + 15] = v2; + cv[s + 16] = x3; + cv[s + 17] = y3; + cv[s + 18] = light.r; + cv[s + 19] = light.g; + cv[s + 20] = light.b; + cv[s + 21] = light.a; + cv[s + 22] = u3; + cv[s + 23] = v3; + } else { + cv[s + 6] = u1; + cv[s + 7] = v1; + cv[s + 8] = dark.r; + cv[s + 9] = dark.g; + cv[s + 10] = dark.b; + cv[s + 11] = dark.a; + cv[s + 12] = x2; + cv[s + 13] = y2; + cv[s + 14] = light.r; + cv[s + 15] = light.g; + cv[s + 16] = light.b; + cv[s + 17] = light.a; + cv[s + 18] = u2; + cv[s + 19] = v2; + cv[s + 20] = dark.r; + cv[s + 21] = dark.g; + cv[s + 22] = dark.b; + cv[s + 23] = dark.a; + cv[s + 24] = x3; + cv[s + 25] = y3; + cv[s + 26] = light.r; + cv[s + 27] = light.g; + cv[s + 28] = light.b; + cv[s + 29] = light.a; + cv[s + 30] = u3; + cv[s + 31] = v3; + cv[s + 32] = dark.r; + cv[s + 33] = dark.g; + cv[s + 34] = dark.b; + cv[s + 35] = dark.a; + } + s = clippedTriangles.length; + const ct = Utils.setArraySize(clippedTriangles, s + 3); + ct[s] = index; + ct[s + 1] = index + 1; + ct[s + 2] = index + 2; + index += 3; + break; + } + } + } + return clipOutputItems != null; + } + clipTrianglesUnpacked(vertices, vertexStart, triangles, trianglesLength, uvs, stride = 2) { + let clippedVertices = this._clippedVerticesTyped; + let clippedUVs = this._clippedUVsTyped; + let clippedTriangles = this._clippedTrianglesTyped; + let index = 0; + this.clippedVerticesLength = 0; + this.clippedUVsLength = 0; + this.clippedTrianglesLength = 0; + if (this.inverse) { + const polygon = this.clippingPolygons[0]; + for (let i = 0; i < trianglesLength; i += 3) { + let v = triangles[i] * stride; + const x1 = vertices[vertexStart + v], y1 = vertices[vertexStart + v + 1]; + let uv = triangles[i] << 1; + const u1 = uvs[uv], v1 = uvs[uv + 1]; + v = triangles[i + 1] * stride; + const x2 = vertices[vertexStart + v], y2 = vertices[vertexStart + v + 1]; + uv = triangles[i + 1] << 1; + const u2 = uvs[uv], v2 = uvs[uv + 1]; + v = triangles[i + 2] * stride; + const x3 = vertices[vertexStart + v], y3 = vertices[vertexStart + v + 1]; + uv = triangles[i + 2] << 1; + const u3 = uvs[uv], v3 = uvs[uv + 1]; + this.clipInverse(x1, y1, x2, y2, x3, y3, polygon); + const nn = this.inverseVertices.length; + if (nn === 0) continue; + const d0 = y2 - y3, d1 = x3 - x2, d2 = x1 - x3, d4 = y3 - y1, d = 1 / (d0 * d2 + d1 * (y1 - y3)); + const iv = this.inverseVertices; + for (let offset = 0; offset < nn; ) { + const polygonSize = iv[offset++]; + const vertexCount = polygonSize >> 1; + let s = this.clippedVerticesLength; + const newLength = s + vertexCount * stride; + const newUVLength = this.clippedUVsLength + vertexCount * 2; + if (clippedVertices.length < newLength) { + this._clippedVerticesTyped = new Float32Array(newLength * 2); + this._clippedVerticesTyped.set(clippedVertices.subarray(0, s)); + clippedVertices = this._clippedVerticesTyped; + } + if (clippedUVs.length < newUVLength) { + this._clippedUVsTyped = new Float32Array(newUVLength * 2); + this._clippedUVsTyped.set(clippedUVs.subarray(0, this.clippedUVsLength)); + clippedUVs = this._clippedUVsTyped; + } + this.clippedVerticesLength = newLength; + this.clippedUVsLength = newUVLength; + const cv = this._clippedVerticesTyped; + const cu = this._clippedUVsTyped; + let uvIndex = newUVLength - vertexCount * 2; + for (let ii = 0; ii < polygonSize; ii += 2, s += stride, uvIndex += 2) { + const x = iv[offset + ii], y = iv[offset + ii + 1]; + cv[s] = x; + cv[s + 1] = y; + const c0 = x - x3, c1 = y - y3, a = (d0 * c0 + d1 * c1) * d, b = (d4 * c0 + d2 * c1) * d, c = 1 - a - b; + cu[uvIndex] = u1 * a + u2 * b + u3 * c; + cu[uvIndex + 1] = v1 * a + v2 * b + v3 * c; + } + s = this.clippedTrianglesLength; + const newLengthTriangles = s + 3 * (vertexCount - 2); + if (clippedTriangles.length < newLengthTriangles) { + this._clippedTrianglesTyped = new Uint16Array(newLengthTriangles * 2); + this._clippedTrianglesTyped.set(clippedTriangles.subarray(0, s)); + clippedTriangles = this._clippedTrianglesTyped; + } + this.clippedTrianglesLength = newLengthTriangles; + const ct = clippedTriangles; + for (let ii = 1; ii < vertexCount - 1; ii++, s += 3) { + ct[s] = index; + ct[s + 1] = index + ii; + ct[s + 2] = index + ii + 1; + } + index += vertexCount; + offset += polygonSize; + } + } + this.clippedVerticesTyped = this._clippedVerticesTyped.subarray(0, this.clippedVerticesLength); + this.clippedUVsTyped = this._clippedUVsTyped.subarray(0, this.clippedUVsLength); + this.clippedTrianglesTyped = this._clippedTrianglesTyped.subarray(0, this.clippedTrianglesLength); + return true; + } + const clipOutput = this.clipOutput; + const polygons = this.clippingPolygons; + const polygonsCount = this.clippingPolygons.length; + let clipOutputItems = null; + for (let i = 0; i < trianglesLength; i += 3) { + let t = triangles[i]; + let v = t * stride; + const x1 = vertices[vertexStart + v], y1 = vertices[vertexStart + v + 1]; + let uv = t << 1; + const u1 = uvs[uv], v1 = uvs[uv + 1]; + t = triangles[i + 1]; + v = t * stride; + const x2 = vertices[vertexStart + v], y2 = vertices[vertexStart + v + 1]; + uv = t << 1; + const u2 = uvs[uv], v2 = uvs[uv + 1]; + t = triangles[i + 2]; + v = t * stride; + const x3 = vertices[vertexStart + v], y3 = vertices[vertexStart + v + 1]; + uv = t << 1; + const u3 = uvs[uv], v3 = uvs[uv + 1]; + let d0 = 0, d1 = 0, d2 = 0, d4 = 0, d = 0; + for (let p = 0; p < polygonsCount; p++) { + let s = this.clippedVerticesLength; + if (this.clip(x1, y1, x2, y2, x3, y3, polygons[p])) { + clipOutputItems = clipOutput; + const clipOutputLength = clipOutput.length; + if (clipOutputLength === 0) continue; + let clipOutputCount = clipOutputLength >> 1; + if (d === 0) { + d0 = y2 - y3; + d1 = x3 - x2; + d2 = x1 - x3; + d4 = y3 - y1; + d = 1 / (d0 * d2 - d1 * d4); + } + const newLength = s + clipOutputCount * stride; + if (clippedVertices.length < newLength) { + this._clippedVerticesTyped = new Float32Array(newLength * 2); + this._clippedVerticesTyped.set(clippedVertices.subarray(0, s)); + this._clippedUVsTyped = new Float32Array((this.clippedUVsLength + clipOutputCount * 2) * 2); + this._clippedUVsTyped.set(clippedUVs.subarray(0, this.clippedUVsLength)); + clippedVertices = this._clippedVerticesTyped; + clippedUVs = this._clippedUVsTyped; + } + const cv = clippedVertices; + const cu = clippedUVs; + this.clippedVerticesLength = newLength; + let uvIndex = this.clippedUVsLength; + this.clippedUVsLength = uvIndex + clipOutputCount * 2; + for (let ii = 0; ii < clipOutputLength; ii += 2, s += stride, uvIndex += 2) { + const x = clipOutputItems[ii], y = clipOutputItems[ii + 1]; + cv[s] = x; + cv[s + 1] = y; + const c0 = x - x3, c1 = y - y3, a = (d0 * c0 + d1 * c1) * d, b = (d4 * c0 + d2 * c1) * d, c = 1 - a - b; + cu[uvIndex] = u1 * a + u2 * b + u3 * c; + cu[uvIndex + 1] = v1 * a + v2 * b + v3 * c; + } + s = this.clippedTrianglesLength; + const newLengthTriangles = s + 3 * (clipOutputCount - 2); + if (clippedTriangles.length < newLengthTriangles) { + this._clippedTrianglesTyped = new Uint16Array(newLengthTriangles * 2); + this._clippedTrianglesTyped.set(clippedTriangles.subarray(0, s)); + clippedTriangles = this._clippedTrianglesTyped; + } + this.clippedTrianglesLength = newLengthTriangles; + const ct = clippedTriangles; + clipOutputCount--; + for (let ii = 1; ii < clipOutputCount; ii++, s += 3) { + ct[s] = index; + ct[s + 1] = index + ii; + ct[s + 2] = index + ii + 1; + } + index += clipOutputCount + 1; + } else { + let newLength = s + 3 * stride; + if (clippedVertices.length < newLength) { + this._clippedVerticesTyped = new Float32Array(newLength * 2); + this._clippedVerticesTyped.set(clippedVertices.subarray(0, s)); + clippedVertices = this._clippedVerticesTyped; + } + clippedVertices[s] = x1; + clippedVertices[s + 1] = y1; + clippedVertices[s + stride] = x2; + clippedVertices[s + stride + 1] = y2; + clippedVertices[s + stride * 2] = x3; + clippedVertices[s + stride * 2 + 1] = y3; + const uvLength = this.clippedUVsLength + 3 * 2; + if (clippedUVs.length < uvLength) { + this._clippedUVsTyped = new Float32Array(uvLength * 2); + this._clippedUVsTyped.set(clippedUVs.subarray(0, this.clippedUVsLength)); + clippedUVs = this._clippedUVsTyped; + } + const uvIndex = this.clippedUVsLength; + clippedUVs[uvIndex] = u1; + clippedUVs[uvIndex + 1] = v1; + clippedUVs[uvIndex + 2] = u2; + clippedUVs[uvIndex + 3] = v2; + clippedUVs[uvIndex + 4] = u3; + clippedUVs[uvIndex + 5] = v3; + this.clippedVerticesLength = newLength; + this.clippedUVsLength = uvLength; + s = this.clippedTrianglesLength; + newLength = s + 3; + if (clippedTriangles.length < newLength) { + this._clippedTrianglesTyped = new Uint16Array(newLength * 2); + this._clippedTrianglesTyped.set(clippedTriangles.subarray(0, s)); + clippedTriangles = this._clippedTrianglesTyped; + } + const ct = clippedTriangles; + ct[s] = index; + ct[s + 1] = index + 1; + ct[s + 2] = index + 2; + index += 3; + this.clippedTrianglesLength = newLength; + break; + } + } + } + this.clippedVerticesTyped = this._clippedVerticesTyped.subarray(0, this.clippedVerticesLength); + this.clippedUVsTyped = this._clippedUVsTyped.subarray(0, this.clippedUVsLength); + this.clippedTrianglesTyped = this._clippedTrianglesTyped.subarray(0, this.clippedTrianglesLength); + return clipOutputItems !== null; + } + clip(x1, y1, x2, y2, x3, y3, polygon) { + const originalOutput = this.clipOutput; + let clipped = false; + let input, output; + if (polygon.length % 4 >= 2) { + input = this.clipOutput; + output = this.scratch; + } else { + input = this.scratch; + output = this.clipOutput; + } + const v = polygon; + input.length = 8; + const iv = input; + iv[0] = x1; + iv[1] = y1; + iv[2] = x2; + iv[3] = y2; + iv[4] = x3; + iv[5] = y3; + iv[6] = x1; + iv[7] = y1; + output.length = 0; + const last = polygon.length - 4; + for (let i = 0; ; i += 2) { + const edgeX = v[i], edgeY = v[i + 1], ex = edgeX - v[i + 2], ey = edgeY - v[i + 3]; + const outputStart = output.length; + const iv2 = input; + x1 = iv2[0]; + y1 = iv2[1]; + let s1 = ey * (edgeX - x1) - ex * (edgeY - y1); + for (let ii = 2, nn = input.length - 2; ii <= nn; ii += 2) { + x2 = iv2[ii]; + y2 = iv2[ii + 1]; + const s2 = ey * (edgeX - x2) - ex * (edgeY - y2); + if (s1 > 0) { + if (s2 > 0) + output.push(x2, y2); + else { + const ix = x2 - x1, iy = y2 - y1, t = s1 / (ix * ey - iy * ex); + if (t >= 0 && t <= 1) { + output.push(x1 + ix * t, y1 + iy * t); + clipped = true; + } else + output.push(x2, y2); + } + } else if (s2 > 0) { + const ix = x2 - x1, iy = y2 - y1, t = s1 / (ix * ey - iy * ex); + if (t >= 0 && t <= 1) { + output.push(x1 + ix * t, y1 + iy * t, x2, y2); + clipped = true; + } else + output.push(x2, y2); + } else + clipped = true; + x1 = x2; + y1 = y2; + s1 = s2; + } + if (outputStart === output.length) { + originalOutput.length = 0; + return true; + } + output.push(output[0], output[1]); + if (i === last) break; + const temp = output; + output = input; + output.length = 0; + input = temp; + } + if (originalOutput !== output) { + originalOutput.length = 0; + for (let i = 0, n = output.length - 2; i < n; i++) + originalOutput[i] = output[i]; + } else + originalOutput.length = originalOutput.length - 2; + return clipped; + } + clipInverse(x1, y1, x2, y2, x3, y3, polygon) { + this.inverseVertices.length = 0; + const vLast = polygon.length - 4; + let input, output; + if (polygon.length % 4 >= 2) { + input = this.clipOutput; + output = this.scratch; + } else { + input = this.scratch; + output = this.clipOutput; + } + input.length = 8; + let v = polygon, iv = input; + iv[0] = x1; + iv[1] = y1; + iv[2] = x2; + iv[3] = y2; + iv[4] = x3; + iv[5] = y3; + iv[6] = x1; + iv[7] = y1; + output.length = 0; + for (let i = 0; ; i += 2) { + const edgeX = v[i], edgeY = v[i + 1], ex = edgeX - v[i + 2], ey = edgeY - v[i + 3]; + const outputStart = output.length, fragmentStart = this.inverseVertices.length; + this.inverseVertices.push(0); + iv = input; + x1 = iv[0]; + y1 = iv[1]; + let s1 = ey * (edgeX - x1) - ex * (edgeY - y1); + for (let ii = 2, nn = input.length - 2; ii <= nn; ii += 2) { + x2 = iv[ii]; + y2 = iv[ii + 1]; + const s2 = ey * (edgeX - x2) - ex * (edgeY - y2); + if (s1 > 0) { + if (s2 > 0) + output.push(x2, y2); + else { + const ix = x2 - x1, iy = y2 - y1, t = s1 / (ix * ey - iy * ex); + if (t >= 0 && t <= 1) { + const cx = x1 + ix * t, cy = y1 + iy * t; + output.push(cx, cy); + this.inverseVertices.push(cx, cy, x2, y2); + } else + output.push(x2, y2); + } + } else if (s2 > 0) { + const ix = x2 - x1, iy = y2 - y1, t = s1 / (ix * ey - iy * ex); + if (t >= 0 && t <= 1) { + const cx = x1 + ix * t, cy = y1 + iy * t; + this.inverseVertices.push(cx, cy); + output.push(cx, cy, x2, y2); + } else + output.push(x2, y2); + } else + this.inverseVertices.push(x2, y2); + x1 = x2; + y1 = y2; + s1 = s2; + } + const fragmentSize = this.inverseVertices.length - fragmentStart - 1; + if (fragmentSize >= 6) + this.inverseVertices[fragmentStart] = fragmentSize; + else + this.inverseVertices.length = fragmentStart; + if (outputStart === output.length) break; + output.push(output[0], output[1]); + if (i === vLast) break; + const temp = output; + output = input; + output.length = 0; + input = temp; + } + } + makeClockwise(polygon) { + const v = polygon; + const n = polygon.length; + let noCW = true, noCCW = true; + let area = 0, prevX = v[n - 2], prevY = v[n - 1], currX = v[0], currY = v[1]; + for (let i = 2; i < n; i += 2) { + const nextX = v[i], nextY = v[i + 1]; + area += currX * nextY - nextX * currY; + const cross2 = (currX - prevX) * (nextY - currY) - (currY - prevY) * (nextX - currX); + noCCW = noCCW && cross2 <= 0; + noCW = noCW && cross2 >= 0; + prevX = currX; + prevY = currY; + currX = nextX; + currY = nextY; + } + area += currX * v[1] - v[0] * currY; + const cross = (currX - prevX) * (v[1] - currY) - (currY - prevY) * (v[0] - currX); + noCCW = noCCW && cross <= 0; + noCW = noCW && cross >= 0; + if (area >= 0) { + for (let i = 0, lastX = n - 2, half = n >> 1; i < half; i += 2) { + const x = v[i], y = v[i + 1]; + const other = lastX - i; + v[i] = v[other]; + v[i + 1] = v[other + 1]; + v[other] = x; + v[other + 1] = y; + } + return noCW; + } + return noCCW; + } + makeConvex(polygon) { + const n = polygon.length; + const v = polygon; + this.clipOutput.length = n; + const sorted = this.clipOutput; + sorted[0] = v[0]; + sorted[1] = v[1]; + for (let i = 2; i < n; i += 2) { + const x = v[i], y = v[i + 1]; + let p = i - 2; + for (; p >= 0 && (sorted[p] > x || sorted[p] === x && sorted[p + 1] > y); p -= 2) { + sorted[p + 2] = sorted[p]; + sorted[p + 3] = sorted[p + 1]; + } + sorted[p + 2] = x; + sorted[p + 3] = y; + } + v[0] = sorted[0]; + v[1] = sorted[1]; + v[2] = sorted[2]; + v[3] = sorted[3]; + let s = 4; + for (let i = 4; i < n; i += 2, s += 2) { + const x = sorted[i], y = sorted[i + 1]; + while ((v[s - 2] - v[s - 4]) * (y - v[s - 3]) - (v[s - 1] - v[s - 3]) * (x - v[s - 4]) >= 0) { + s -= 2; + if (s === 2) break; + } + v[s] = x; + v[s + 1] = y; + } + v[s] = sorted[n - 4]; + v[s + 1] = sorted[n - 3]; + const t = s; + s += 2; + for (let i = n - 6; i >= 0; i -= 2, s += 2) { + const x = sorted[i], y = sorted[i + 1]; + while ((v[s - 2] - v[s - 4]) * (y - v[s - 3]) - (v[s - 1] - v[s - 3]) * (x - v[s - 4]) >= 0) { + s -= 2; + if (s === t) break; + } + v[s] = x; + v[s + 1] = y; + } + polygon.length = s - 2; + } + }; + + // spine-core/src/SkeletonJson.ts + var SkeletonJson = class { + attachmentLoader; + /** Scales bone positions, image sizes, and translations as they are loaded. This allows different size images to be used at + * runtime than were used in Spine. + * + * See [Scaling](http://esotericsoftware.com/spine-loading-skeleton-data#Scaling) in the Spine Runtimes Guide. */ + scale = 1; + linkedMeshes = []; + constructor(attachmentLoader) { + this.attachmentLoader = attachmentLoader; + } + // biome-ignore lint/suspicious/noExplicitAny: it is any until we define a schema + readSkeletonData(json) { + const scale = this.scale; + const skeletonData = new SkeletonData(); + const root = typeof json === "string" ? JSON.parse(json) : json; + const skeletonMap = root.skeleton; + if (skeletonMap) { + skeletonData.hash = skeletonMap.hash; + skeletonData.version = skeletonMap.spine; + skeletonData.x = skeletonMap.x; + skeletonData.y = skeletonMap.y; + skeletonData.width = skeletonMap.width; + skeletonData.height = skeletonMap.height; + skeletonData.referenceScale = getValue(skeletonMap, "referenceScale", 100) * scale; + skeletonData.fps = skeletonMap.fps; + skeletonData.imagesPath = skeletonMap.images ?? null; + skeletonData.audioPath = skeletonMap.audio ?? null; + } + if (root.bones) { + for (let i = 0; i < root.bones.length; i++) { + const boneMap = root.bones[i]; + let parent = null; + const parentName = getValue(boneMap, "parent", null); + if (parentName) parent = skeletonData.findBone(parentName); + const data = new BoneData(skeletonData.bones.length, boneMap.name, parent); + data.length = getValue(boneMap, "length", 0) * scale; + const setup = data.setupPose; + setup.x = getValue(boneMap, "x", 0) * scale; + setup.y = getValue(boneMap, "y", 0) * scale; + setup.rotation = getValue(boneMap, "rotation", 0); + setup.scaleX = getValue(boneMap, "scaleX", 1); + setup.scaleY = getValue(boneMap, "scaleY", 1); + setup.shearX = getValue(boneMap, "shearX", 0); + setup.shearY = getValue(boneMap, "shearY", 0); + setup.inherit = Utils.enumValue(Inherit, getValue(boneMap, "inherit", "Normal")); + data.skinRequired = getValue(boneMap, "skin", false); + const color = getValue(boneMap, "color", null); + if (color) data.color.setFromString(color); + data.icon = getValue(boneMap, "icon", void 0); + data.iconSize = getValue(boneMap, "iconSize", 1); + data.iconRotation = getValue(boneMap, "iconRotation", 0); + skeletonData.bones.push(data); + } + } + if (root.slots) { + for (let i = 0; i < root.slots.length; i++) { + const slotMap = root.slots[i]; + const slotName = slotMap.name; + const boneData = skeletonData.findBone(slotMap.bone); + if (!boneData) throw new Error(`Couldn't find bone ${slotMap.bone} for slot ${slotName}`); + const data = new SlotData(skeletonData.slots.length, slotName, boneData); + const color = getValue(slotMap, "color", null); + if (color) data.setupPose.color.setFromString(color); + const dark = getValue(slotMap, "dark", null); + if (dark) data.setupPose.darkColor = Color.fromString(dark); + data.attachmentName = getValue(slotMap, "attachment", null); + data.blendMode = Utils.enumValue(BlendMode, getValue(slotMap, "blend", "normal")); + data.visible = getValue(slotMap, "visible", true); + skeletonData.slots.push(data); + } + } + if (root.constraints) { + for (const constraintMap of root.constraints) { + const name = constraintMap.name; + const skinRequired = getValue(constraintMap, "skin", false); + switch (getValue(constraintMap, "type", false)) { + case "ik": { + const data = new IkConstraintData(name); + data.skinRequired = skinRequired; + for (let ii = 0; ii < constraintMap.bones.length; ii++) { + const bone = skeletonData.findBone(constraintMap.bones[ii]); + if (!bone) throw new Error(`Couldn't find bone ${constraintMap.bones[ii]} for IK constraint ${name}.`); + data.bones.push(bone); + } + const targetName = constraintMap.target; + const target = skeletonData.findBone(targetName); + if (!target) throw new Error(`Couldn't find target bone ${targetName} for IK constraint ${name}.`); + data.target = target; + const scaleY = getValue(constraintMap, "scaleY", null); + if (scaleY != null) data.scaleYMode = Utils.enumValue(ScaleYMode, scaleY); + const setup = data.setupPose; + setup.mix = getValue(constraintMap, "mix", 1); + setup.softness = getValue(constraintMap, "softness", 0) * scale; + setup.bendDirection = getValue(constraintMap, "bendPositive", true) ? 1 : -1; + setup.compress = getValue(constraintMap, "compress", false); + setup.stretch = getValue(constraintMap, "stretch", false); + skeletonData.constraints.push(data); + break; + } + case "transform": { + const data = new TransformConstraintData(name); + data.skinRequired = skinRequired; + for (let ii = 0; ii < constraintMap.bones.length; ii++) { + const boneName = constraintMap.bones[ii]; + const bone = skeletonData.findBone(boneName); + if (!bone) throw new Error(`Couldn't find bone ${boneName} for transform constraint ${constraintMap.name}.`); + data.bones.push(bone); + } + const sourceName = constraintMap.source; + const source = skeletonData.findBone(sourceName); + if (!source) throw new Error(`Couldn't find source bone ${sourceName} for transform constraint ${constraintMap.name}.`); + data.source = source; + data.localSource = getValue(constraintMap, "localSource", false); + data.localTarget = getValue(constraintMap, "localTarget", false); + data.additive = getValue(constraintMap, "additive", false); + data.clamp = getValue(constraintMap, "clamp", false); + let rotate = false, x = false, y = false, scaleX = false, scaleY = false, shearY = false; + const fromEntries = Object.entries(getValue(constraintMap, "properties", {})); + for (const [name2, fromEntry] of fromEntries) { + const from = this.fromProperty(name2); + const fromScale = this.propertyScale(name2, scale); + from.offset = getValue(fromEntry, "offset", 0) * fromScale; + const toEntries = Object.entries(getValue(fromEntry, "to", {})); + for (const [name3, toEntry] of toEntries) { + let toScale = 1; + let to; + switch (name3) { + case "rotate": { + rotate = true; + to = new ToRotate(); + break; + } + case "x": { + x = true; + to = new ToX(); + toScale = scale; + break; + } + case "y": { + y = true; + to = new ToY(); + toScale = scale; + break; + } + case "scaleX": { + scaleX = true; + to = new ToScaleX(); + break; + } + case "scaleY": { + scaleY = true; + to = new ToScaleY(); + break; + } + case "shearY": { + shearY = true; + to = new ToShearY(); + break; + } + default: + throw new Error(`Invalid transform constraint to property: ${name3}`); + } + to.offset = getValue(toEntry, "offset", 0) * toScale; + to.max = getValue(toEntry, "max", 1) * toScale; + to.scale = getValue(toEntry, "scale", 1) * toScale / fromScale; + from.to.push(to); + } + if (from.to.length > 0) data.properties.push(from); + } + data.offsets[TransformConstraintData.ROTATION] = getValue(constraintMap, "rotation", 0); + data.offsets[TransformConstraintData.X] = getValue(constraintMap, "x", 0) * scale; + data.offsets[TransformConstraintData.Y] = getValue(constraintMap, "y", 0) * scale; + data.offsets[TransformConstraintData.SCALEX] = getValue(constraintMap, "scaleX", 0); + data.offsets[TransformConstraintData.SCALEY] = getValue(constraintMap, "scaleY", 0); + data.offsets[TransformConstraintData.SHEARY] = getValue(constraintMap, "shearY", 0); + const setup = data.setupPose; + if (rotate) setup.mixRotate = getValue(constraintMap, "mixRotate", 1); + if (x) setup.mixX = getValue(constraintMap, "mixX", 1); + if (y) setup.mixY = getValue(constraintMap, "mixY", setup.mixX); + if (scaleX) setup.mixScaleX = getValue(constraintMap, "mixScaleX", 1); + if (scaleY) setup.mixScaleY = getValue(constraintMap, "mixScaleY", setup.mixScaleX); + if (shearY) setup.mixShearY = getValue(constraintMap, "mixShearY", 1); + skeletonData.constraints.push(data); + break; + } + case "path": { + const data = new PathConstraintData(name); + data.skinRequired = skinRequired; + for (let ii = 0; ii < constraintMap.bones.length; ii++) { + const boneName = constraintMap.bones[ii]; + const bone = skeletonData.findBone(boneName); + if (!bone) throw new Error(`Couldn't find bone ${boneName} for path constraint ${constraintMap.name}.`); + data.bones.push(bone); + } + const slotName = constraintMap.slot; + const slot = skeletonData.findSlot(slotName); + if (!slot) throw new Error(`Couldn't find slot ${slotName} for path constraint ${constraintMap.name}.`); + data.slot = slot; + data.positionMode = Utils.enumValue(PositionMode, getValue(constraintMap, "positionMode", "Percent")); + data.spacingMode = Utils.enumValue(SpacingMode, getValue(constraintMap, "spacingMode", "Length")); + data.rotateMode = Utils.enumValue(RotateMode, getValue(constraintMap, "rotateMode", "Tangent")); + data.offsetRotation = getValue(constraintMap, "rotation", 0); + const setup = data.setupPose; + setup.position = getValue(constraintMap, "position", 0); + if (data.positionMode === 0 /* Fixed */) setup.position *= scale; + setup.spacing = getValue(constraintMap, "spacing", 0); + if (data.spacingMode === 0 /* Length */ || data.spacingMode === 1 /* Fixed */) setup.spacing *= scale; + setup.mixRotate = getValue(constraintMap, "mixRotate", 1); + setup.mixX = getValue(constraintMap, "mixX", 1); + setup.mixY = getValue(constraintMap, "mixY", setup.mixX); + skeletonData.constraints.push(data); + break; + } + case "physics": { + const data = new PhysicsConstraintData(name); + data.skinRequired = skinRequired; + const boneName = constraintMap.bone; + const bone = skeletonData.findBone(boneName); + if (bone == null) throw new Error(`Physics bone not found: ${boneName}`); + data.bone = bone; + data.x = getValue(constraintMap, "x", 0); + data.y = getValue(constraintMap, "y", 0); + data.rotate = getValue(constraintMap, "rotate", 0); + data.scaleX = getValue(constraintMap, "scaleX", 0); + const scaleY = getValue(constraintMap, "scaleY", null); + if (scaleY != null) data.scaleYMode = Utils.enumValue(ScaleYMode, scaleY); + data.shearX = getValue(constraintMap, "shearX", 0); + data.limit = getValue(constraintMap, "limit", 5e3) * scale; + data.step = 1 / getValue(constraintMap, "fps", 60); + const setup = data.setupPose; + setup.inertia = getValue(constraintMap, "inertia", 0.5); + setup.strength = getValue(constraintMap, "strength", 100); + setup.damping = getValue(constraintMap, "damping", 0.85); + setup.massInverse = 1 / getValue(constraintMap, "mass", 1); + setup.wind = getValue(constraintMap, "wind", 0); + setup.gravity = getValue(constraintMap, "gravity", 0); + setup.mix = getValue(constraintMap, "mix", 1); + data.inertiaGlobal = getValue(constraintMap, "inertiaGlobal", false); + data.strengthGlobal = getValue(constraintMap, "strengthGlobal", false); + data.dampingGlobal = getValue(constraintMap, "dampingGlobal", false); + data.massGlobal = getValue(constraintMap, "massGlobal", false); + data.windGlobal = getValue(constraintMap, "windGlobal", false); + data.gravityGlobal = getValue(constraintMap, "gravityGlobal", false); + data.mixGlobal = getValue(constraintMap, "mixGlobal", false); + skeletonData.constraints.push(data); + break; + } + case "slider": { + const data = new SliderData(name); + data.skinRequired = skinRequired; + data.additive = getValue(constraintMap, "additive", false); + data.loop = getValue(constraintMap, "loop", false); + data.setupPose.mix = getValue(constraintMap, "mix", 1); + const boneName = constraintMap.bone; + if (boneName) { + data.bone = skeletonData.findBone(boneName); + if (!data.bone) throw new Error(`Slider bone not found: ${boneName}`); + const property = constraintMap.property; + data.property = this.fromProperty(property); + const propertyScale = this.propertyScale(property, scale); + data.property.offset = getValue(constraintMap, "from", 0) * propertyScale; + data.offset = getValue(constraintMap, "to", 0); + data.scale = getValue(constraintMap, "scale", 1) / propertyScale; + data.max = getValue(constraintMap, "max", 0); + data.local = getValue(constraintMap, "local", false); + } else + data.setupPose.time = getValue(constraintMap, "time", 0); + skeletonData.constraints.push(data); + break; + } + } + } + } + if (root.skins) { + for (let i = 0; i < root.skins.length; i++) { + const skinMap = root.skins[i]; + const skin = new Skin(skinMap.name); + if (skinMap.bones) { + for (let ii = 0; ii < skinMap.bones.length; ii++) { + const boneName = skinMap.bones[ii]; + const bone = skeletonData.findBone(boneName); + if (!bone) throw new Error(`Couldn't find bone ${boneName} for skin ${skinMap.name}.`); + skin.bones.push(bone); + } + } + if (skinMap.ik) { + for (let ii = 0; ii < skinMap.ik.length; ii++) { + const constraintName = skinMap.ik[ii]; + const constraint = skeletonData.findConstraint(constraintName, IkConstraintData); + if (!constraint) throw new Error(`Couldn't find IK constraint ${constraintName} for skin ${skinMap.name}.`); + skin.constraints.push(constraint); + } + } + if (skinMap.transform) { + for (let ii = 0; ii < skinMap.transform.length; ii++) { + const constraintName = skinMap.transform[ii]; + const constraint = skeletonData.findConstraint(constraintName, TransformConstraintData); + if (!constraint) throw new Error(`Couldn't find transform constraint ${constraintName} for skin ${skinMap.name}.`); + skin.constraints.push(constraint); + } + } + if (skinMap.path) { + for (let ii = 0; ii < skinMap.path.length; ii++) { + const constraintName = skinMap.path[ii]; + const constraint = skeletonData.findConstraint(constraintName, PathConstraintData); + if (!constraint) throw new Error(`Couldn't find path constraint ${constraintName} for skin ${skinMap.name}.`); + skin.constraints.push(constraint); + } + } + if (skinMap.physics) { + for (let ii = 0; ii < skinMap.physics.length; ii++) { + const constraintName = skinMap.physics[ii]; + const constraint = skeletonData.findConstraint(constraintName, PhysicsConstraintData); + if (!constraint) throw new Error(`Couldn't find physics constraint ${constraintName} for skin ${skinMap.name}.`); + skin.constraints.push(constraint); + } + } + if (skinMap.slider) { + for (let ii = 0; ii < skinMap.slider.length; ii++) { + const constraintName = skinMap.slider[ii]; + const constraint = skeletonData.findConstraint(constraintName, SliderData); + if (!constraint) throw new Error(`Couldn't find slider constraint ${constraintName} for skin ${skinMap.name}.`); + skin.constraints.push(constraint); + } + } + for (const slotName in skinMap.attachments) { + const slot = skeletonData.findSlot(slotName); + if (!slot) throw new Error(`Couldn't find skin slot ${slotName} for skin ${skinMap.name}.`); + const slotMap = skinMap.attachments[slotName]; + for (const entryName in slotMap) { + const attachment = this.readAttachment(slotMap[entryName], skin, slot.index, entryName, skeletonData); + if (attachment) skin.setAttachment(slot.index, entryName, attachment); + } + } + skeletonData.skins.push(skin); + if (skin.name === "default") skeletonData.defaultSkin = skin; + } + } + for (let i = 0, n = this.linkedMeshes.length; i < n; i++) { + const linkedMesh = this.linkedMeshes[i]; + const skin = !linkedMesh.skin ? skeletonData.defaultSkin : skeletonData.findSkin(linkedMesh.skin); + if (!skin) throw new Error(`Skin not found: ${linkedMesh.skin}`); + const source = skin.getAttachment(linkedMesh.sourceIndex, linkedMesh.source); + if (!source) throw new Error(`Source mesh not found: ${linkedMesh.source}`); + linkedMesh.mesh.timelineAttachment = linkedMesh.inheritTimelines ? source : linkedMesh.mesh; + linkedMesh.mesh.setSourceMesh(source); + linkedMesh.mesh.updateSequence(); + outer: + if (linkedMesh.inheritTimelines && linkedMesh.slotIndex !== linkedMesh.sourceIndex) { + const slots = source.timelineSlots; + for (const existing of slots) + if (existing === linkedMesh.slotIndex) break outer; + const newSlots = [...slots]; + newSlots[slots.length] = linkedMesh.slotIndex; + source.timelineSlots = newSlots; + } + } + this.linkedMeshes.length = 0; + if (root.events) { + for (const eventName in root.events) { + const eventMap = root.events[eventName]; + const data = new EventData(eventName); + const setup = data.setupPose; + setup.intValue = getValue(eventMap, "int", 0); + setup.floatValue = getValue(eventMap, "float", 0); + setup.stringValue = getValue(eventMap, "string", ""); + data._audioPath = getValue(eventMap, "audio", null); + if (data.audioPath) { + setup.volume = getValue(eventMap, "volume", setup.volume); + setup.balance = getValue(eventMap, "balance", setup.balance); + } + skeletonData.events.push(data); + } + } + if (root.animations) { + for (const animationName in root.animations) { + const animationMap = root.animations[animationName]; + this.readAnimation(animationMap, animationName, skeletonData); + } + } + if (root.constraints) { + for (const animationName in root.constraints) { + const animationMap = root.constraints[animationName]; + if (animationMap.type === "slider") { + const data = skeletonData.findConstraint(animationMap.name, SliderData); + const animationName2 = animationMap.animation; + const animation = skeletonData.findAnimation(animationName2); + if (!animation) throw new Error(`Slider animation not found: ${animationName2}`); + data.animation = animation; + } + } + } + return skeletonData; + } + fromProperty(type) { + let from; + switch (type) { + case "rotate": + from = new FromRotate(); + break; + case "x": + from = new FromX(); + break; + case "y": + from = new FromY(); + break; + case "scaleX": + from = new FromScaleX(); + break; + case "scaleY": + from = new FromScaleY(); + break; + case "shearY": + from = new FromShearY(); + break; + default: + throw new Error(`Invalid transform constraint from property: ${type}`); + } + return from; + } + propertyScale(type, scale) { + switch (type) { + case "x": + case "y": + return scale; + default: + return 1; + } + } + // biome-ignore lint/suspicious/noExplicitAny: it is any until we define a schema + readAttachment(map, skin, slotIndex, placeholder, skeletonData) { + const scale = this.scale; + const name = getValue(map, "name", placeholder); + switch (getValue(map, "type", "region")) { + case "region": { + const path = getValue(map, "path", name); + const sequence = this.readSequence(getValue(map, "sequence", null)); + const region = this.attachmentLoader.newRegionAttachment(skin, placeholder, name, path, sequence); + if (!region) return null; + region.path = path; + region.x = getValue(map, "x", 0) * scale; + region.y = getValue(map, "y", 0) * scale; + region.scaleX = getValue(map, "scaleX", 1); + region.scaleY = getValue(map, "scaleY", 1); + region.rotation = getValue(map, "rotation", 0); + region.width = map.width * scale; + region.height = map.height * scale; + const color = getValue(map, "color", null); + if (color) region.color.setFromString(color); + region.updateSequence(); + return region; + } + case "boundingbox": { + const box = this.attachmentLoader.newBoundingBoxAttachment(skin, placeholder, name); + if (!box) return null; + this.readVertices(map, box, map.vertexCount << 1); + const color = getValue(map, "color", null); + if (color) box.color.setFromString(color); + return box; + } + case "mesh": + case "linkedmesh": { + const path = getValue(map, "path", name); + const sequence = this.readSequence(getValue(map, "sequence", null)); + const mesh = this.attachmentLoader.newMeshAttachment(skin, placeholder, name, path, sequence); + if (!mesh) return null; + mesh.path = path; + const color = getValue(map, "color", null); + if (color) mesh.color.setFromString(color); + mesh.width = getValue(map, "width", 0) * scale; + mesh.height = getValue(map, "height", 0) * scale; + const source = getValue(map, "source", null); + if (source) { + let sourceIndex = slotIndex; + const slot = getValue(map, "slot", null); + if (slot) { + const sourceSlot = skeletonData.findSlot(slot); + if (!sourceSlot) throw new Error(`Source mesh slot not found: ${slot}`); + sourceIndex = sourceSlot.index; + } + this.linkedMeshes.push(new LinkedMesh2( + mesh, + getValue(map, "skin", null), + slotIndex, + sourceIndex, + source, + getValue(map, "timelines", true) + )); + return mesh; + } + const uvs = map.uvs; + this.readVertices(map, mesh, uvs.length); + mesh.triangles = map.triangles; + mesh.regionUVs = uvs; + mesh.edges = getValue(map, "edges", null); + mesh.hullLength = getValue(map, "hull", 0) * 2; + mesh.updateSequence(); + return mesh; + } + case "path": { + const path = this.attachmentLoader.newPathAttachment(skin, placeholder, name); + if (!path) return null; + path.closed = getValue(map, "closed", false); + path.constantSpeed = getValue(map, "constantSpeed", true); + const vertexCount = map.vertexCount; + this.readVertices(map, path, vertexCount << 1); + const lengths = Utils.newArray(vertexCount / 3, 0); + for (let i = 0; i < map.lengths.length; i++) + lengths[i] = map.lengths[i] * scale; + path.lengths = lengths; + const color = getValue(map, "color", null); + if (color) path.color.setFromString(color); + return path; + } + case "point": { + const point = this.attachmentLoader.newPointAttachment(skin, placeholder, name); + if (!point) return null; + point.x = getValue(map, "x", 0) * scale; + point.y = getValue(map, "y", 0) * scale; + point.rotation = getValue(map, "rotation", 0); + const color = getValue(map, "color", null); + if (color) point.color.setFromString(color); + return point; + } + case "clipping": { + const clip = this.attachmentLoader.newClippingAttachment(skin, placeholder, name); + if (!clip) return null; + const end = getValue(map, "end", null); + if (end) clip.endSlot = skeletonData.findSlot(end); + clip.convex = getValue(map, "convex", false); + clip.inverse = getValue(map, "inverse", false); + const vertexCount = map.vertexCount; + this.readVertices(map, clip, vertexCount << 1); + const color = getValue(map, "color", null); + if (color) clip.color.setFromString(color); + return clip; + } + } + return null; + } + readSequence(map) { + if (map == null) return new Sequence(1, false); + const sequence = new Sequence(getValue(map, "count", 0), true); + sequence.start = getValue(map, "start", 1); + sequence.digits = getValue(map, "digits", 0); + sequence.setupIndex = getValue(map, "setup", 0); + return sequence; + } + // biome-ignore lint/suspicious/noExplicitAny: it is any until we define a schema + readVertices(map, attachment, verticesLength) { + const scale = this.scale; + attachment.worldVerticesLength = verticesLength; + const vertices = map.vertices; + if (verticesLength === vertices.length) { + const scaledVertices = Utils.toFloatArray(vertices); + if (scale !== 1) { + for (let i = 0, n = vertices.length; i < n; i++) + scaledVertices[i] *= scale; + } + attachment.vertices = scaledVertices; + return; + } + const weights = []; + const bones = []; + for (let i = 0, n = vertices.length; i < n; ) { + const boneCount = vertices[i++]; + bones.push(boneCount); + for (let nn = i + boneCount * 4; i < nn; i += 4) { + bones.push(vertices[i]); + weights.push(vertices[i + 1] * scale); + weights.push(vertices[i + 2] * scale); + weights.push(vertices[i + 3]); + } + } + attachment.bones = bones; + attachment.vertices = Utils.toFloatArray(weights); + } + // biome-ignore lint/suspicious/noExplicitAny: it is any untile we define a schema + readAnimation(map, name, skeletonData) { + const scale = this.scale; + const timelines = []; + if (map.slots) { + for (const slotName in map.slots) { + const slotMap = map.slots[slotName]; + const slot = skeletonData.findSlot(slotName); + if (!slot) throw new Error(`Slot not found: ${slotName}`); + const slotIndex = slot.index; + for (const timelineName in slotMap) { + const timelineMap = slotMap[timelineName]; + if (!timelineMap) continue; + const frames = timelineMap.length; + switch (timelineName) { + case "attachment": { + const timeline = new AttachmentTimeline(frames, slotIndex); + for (let frame = 0; frame < frames; frame++) { + const keyMap = timelineMap[frame]; + timeline.setFrame(frame, getValue(keyMap, "time", 0), getValue(keyMap, "name", null)); + } + timelines.push(timeline); + break; + } + case "rgba": { + const timeline = new RGBATimeline(frames, frames << 2, slotIndex); + let keyMap = timelineMap[0]; + let time = getValue(keyMap, "time", 0); + let color2 = Color.fromString(keyMap.color); + for (let frame = 0, bezier = 0; ; frame++) { + timeline.setFrame(frame, time, color2.r, color2.g, color2.b, color2.a); + const nextMap = timelineMap[frame + 1]; + if (!nextMap) { + timeline.shrink(bezier); + break; + } + const time2 = getValue(nextMap, "time", 0); + const newColor = Color.fromString(nextMap.color); + const curve = keyMap.curve; + if (curve) { + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, color2.r, newColor.r, 1); + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, color2.g, newColor.g, 1); + bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, color2.b, newColor.b, 1); + bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, color2.a, newColor.a, 1); + } + time = time2; + color2 = newColor; + keyMap = nextMap; + } + timelines.push(timeline); + break; + } + case "rgb": { + const timeline = new RGBTimeline(frames, frames * 3, slotIndex); + let keyMap = timelineMap[0]; + let time = getValue(keyMap, "time", 0); + let color2 = Color.fromString(keyMap.color); + for (let frame = 0, bezier = 0; ; frame++) { + timeline.setFrame(frame, time, color2.r, color2.g, color2.b); + const nextMap = timelineMap[frame + 1]; + if (!nextMap) { + timeline.shrink(bezier); + break; + } + const time2 = getValue(nextMap, "time", 0); + const newColor = Color.fromString(nextMap.color); + const curve = keyMap.curve; + if (curve) { + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, color2.r, newColor.r, 1); + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, color2.g, newColor.g, 1); + bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, color2.b, newColor.b, 1); + } + time = time2; + color2 = newColor; + keyMap = nextMap; + } + timelines.push(timeline); + break; + } + case "alpha": { + readTimeline12(timelines, timelineMap, new AlphaTimeline(frames, frames, slotIndex), 0, 1); + break; + } + case "rgba2": { + const timeline = new RGBA2Timeline(frames, frames * 7, slotIndex); + let keyMap = timelineMap[0]; + let time = getValue(keyMap, "time", 0); + let color2 = Color.fromString(keyMap.light); + let color22 = Color.fromString(keyMap.dark); + for (let frame = 0, bezier = 0; ; frame++) { + timeline.setFrame(frame, time, color2.r, color2.g, color2.b, color2.a, color22.r, color22.g, color22.b); + const nextMap = timelineMap[frame + 1]; + if (!nextMap) { + timeline.shrink(bezier); + break; + } + const time2 = getValue(nextMap, "time", 0); + const newColor = Color.fromString(nextMap.light); + const newColor2 = Color.fromString(nextMap.dark); + const curve = keyMap.curve; + if (curve) { + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, color2.r, newColor.r, 1); + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, color2.g, newColor.g, 1); + bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, color2.b, newColor.b, 1); + bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, color2.a, newColor.a, 1); + bezier = readCurve(curve, timeline, bezier, frame, 4, time, time2, color22.r, newColor2.r, 1); + bezier = readCurve(curve, timeline, bezier, frame, 5, time, time2, color22.g, newColor2.g, 1); + bezier = readCurve(curve, timeline, bezier, frame, 6, time, time2, color22.b, newColor2.b, 1); + } + time = time2; + color2 = newColor; + color22 = newColor2; + keyMap = nextMap; + } + timelines.push(timeline); + break; + } + case "rgb2": { + const timeline = new RGB2Timeline(frames, frames * 6, slotIndex); + let keyMap = timelineMap[0]; + let time = getValue(keyMap, "time", 0); + let color2 = Color.fromString(keyMap.light); + let color22 = Color.fromString(keyMap.dark); + for (let frame = 0, bezier = 0; ; frame++) { + timeline.setFrame(frame, time, color2.r, color2.g, color2.b, color22.r, color22.g, color22.b); + const nextMap = timelineMap[frame + 1]; + if (!nextMap) { + timeline.shrink(bezier); + break; + } + const time2 = getValue(nextMap, "time", 0); + const newColor = Color.fromString(nextMap.light); + const newColor2 = Color.fromString(nextMap.dark); + const curve = keyMap.curve; + if (curve) { + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, color2.r, newColor.r, 1); + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, color2.g, newColor.g, 1); + bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, color2.b, newColor.b, 1); + bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, color22.r, newColor2.r, 1); + bezier = readCurve(curve, timeline, bezier, frame, 4, time, time2, color22.g, newColor2.g, 1); + bezier = readCurve(curve, timeline, bezier, frame, 5, time, time2, color22.b, newColor2.b, 1); + } + time = time2; + color2 = newColor; + color22 = newColor2; + keyMap = nextMap; + } + timelines.push(timeline); + break; + } + default: + throw new Error(`Invalid timeline type for a slot: ${timelineMap.name} (${slotMap.name})`); + } + } + } + } + if (map.bones) { + for (const boneName in map.bones) { + const boneMap = map.bones[boneName]; + const bone = skeletonData.findBone(boneName); + if (!bone) throw new Error(`Bone not found: ${boneName}`); + const boneIndex = bone.index; + for (const timelineName in boneMap) { + const timelineMap = boneMap[timelineName]; + const frames = timelineMap.length; + if (frames === 0) continue; + switch (timelineName) { + case "rotate": + readTimeline12(timelines, timelineMap, new RotateTimeline(frames, frames, boneIndex), 0, 1); + break; + case "translate": + readTimeline22(timelines, timelineMap, new TranslateTimeline(frames, frames << 1, boneIndex), "x", "y", 0, scale); + break; + case "translatex": + readTimeline12(timelines, timelineMap, new TranslateXTimeline(frames, frames, boneIndex), 0, scale); + break; + case "translatey": + readTimeline12(timelines, timelineMap, new TranslateYTimeline(frames, frames, boneIndex), 0, scale); + break; + case "scale": + readTimeline22(timelines, timelineMap, new ScaleTimeline(frames, frames << 1, boneIndex), "x", "y", 1, 1); + break; + case "scalex": + readTimeline12(timelines, timelineMap, new ScaleXTimeline(frames, frames, boneIndex), 1, 1); + break; + case "scaley": + readTimeline12(timelines, timelineMap, new ScaleYTimeline(frames, frames, boneIndex), 1, 1); + break; + case "shear": + readTimeline22(timelines, timelineMap, new ShearTimeline(frames, frames << 1, boneIndex), "x", "y", 0, 1); + break; + case "shearx": + readTimeline12(timelines, timelineMap, new ShearXTimeline(frames, frames, boneIndex), 0, 1); + break; + case "sheary": + readTimeline12(timelines, timelineMap, new ShearYTimeline(frames, frames, boneIndex), 0, 1); + break; + case "inherit": { + const timeline = new InheritTimeline(frames, bone.index); + for (let frame = 0; frame < timelineMap.length; frame++) { + const aFrame = timelineMap[frame]; + timeline.setFrame(frame, getValue(aFrame, "time", 0), Utils.enumValue(Inherit, getValue(aFrame, "inherit", "Normal"))); + } + timelines.push(timeline); + break; + } + default: + throw new Error(`Invalid timeline type for a bone: ${timelineMap.name} (${boneMap.name})`); + } + } + } + } + if (map.ik) { + for (const constraintName in map.ik) { + const constraintMap = map.ik[constraintName]; + let keyMap = constraintMap[0]; + if (!keyMap) continue; + const constraint = skeletonData.findConstraint(constraintName, IkConstraintData); + if (!constraint) throw new Error(`IK Constraint not found: ${constraintName}`); + const timeline = new IkConstraintTimeline( + constraintMap.length, + constraintMap.length << 1, + skeletonData.constraints.indexOf(constraint) + ); + let time = getValue(keyMap, "time", 0); + let mix = getValue(keyMap, "mix", 1); + let softness = getValue(keyMap, "softness", 0) * scale; + for (let frame = 0, bezier = 0; ; frame++) { + timeline.setFrame(frame, time, mix, softness, getValue(keyMap, "bendPositive", true) ? 1 : -1, getValue(keyMap, "compress", false), getValue(keyMap, "stretch", false)); + const nextMap = constraintMap[frame + 1]; + if (!nextMap) { + timeline.shrink(bezier); + break; + } + const time2 = getValue(nextMap, "time", 0); + const mix2 = getValue(nextMap, "mix", 1); + const softness2 = getValue(nextMap, "softness", 0) * scale; + const curve = keyMap.curve; + if (curve) { + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, mix, mix2, 1); + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, softness, softness2, scale); + } + time = time2; + mix = mix2; + softness = softness2; + keyMap = nextMap; + } + timelines.push(timeline); + } + } + if (map.transform) { + for (const constraintName in map.transform) { + const timelineMap = map.transform[constraintName]; + let keyMap = timelineMap[0]; + if (!keyMap) continue; + const constraint = skeletonData.findConstraint(constraintName, TransformConstraintData); + if (!constraint) throw new Error(`Transform constraint not found: ${constraintName}`); + const timeline = new TransformConstraintTimeline( + timelineMap.length, + timelineMap.length * 6, + skeletonData.constraints.indexOf(constraint) + ); + let time = getValue(keyMap, "time", 0); + let mixRotate = getValue(keyMap, "mixRotate", 1); + let mixX = getValue(keyMap, "mixX", 1), mixY = getValue(keyMap, "mixY", mixX); + let mixScaleX = getValue(keyMap, "mixScaleX", 1), mixScaleY = getValue(keyMap, "mixScaleY", 1); + let mixShearY = getValue(keyMap, "mixShearY", 1); + for (let frame = 0, bezier = 0; ; frame++) { + timeline.setFrame(frame, time, mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY); + const nextMap = timelineMap[frame + 1]; + if (!nextMap) { + timeline.shrink(bezier); + break; + } + const time2 = getValue(nextMap, "time", 0); + const mixRotate2 = getValue(nextMap, "mixRotate", 1); + const mixX2 = getValue(nextMap, "mixX", 1), mixY2 = getValue(nextMap, "mixY", mixX2); + const mixScaleX2 = getValue(nextMap, "mixScaleX", 1), mixScaleY2 = getValue(nextMap, "mixScaleY", 1); + const mixShearY2 = getValue(nextMap, "mixShearY", 1); + const curve = keyMap.curve; + if (curve) { + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, mixRotate, mixRotate2, 1); + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, mixX, mixX2, 1); + bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, mixY, mixY2, 1); + bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, mixScaleX, mixScaleX2, 1); + bezier = readCurve(curve, timeline, bezier, frame, 4, time, time2, mixScaleY, mixScaleY2, 1); + bezier = readCurve(curve, timeline, bezier, frame, 5, time, time2, mixShearY, mixShearY2, 1); + } + time = time2; + mixRotate = mixRotate2; + mixX = mixX2; + mixY = mixY2; + mixScaleX = mixScaleX2; + mixScaleY = mixScaleY2; + mixShearY = mixShearY2; + keyMap = nextMap; + } + timelines.push(timeline); + } + } + if (map.path) { + for (const constraintName in map.path) { + const constraintMap = map.path[constraintName]; + const constraint = skeletonData.findConstraint(constraintName, PathConstraintData); + if (!constraint) throw new Error(`Path constraint not found: ${constraintName}`); + const index = skeletonData.constraints.indexOf(constraint); + for (const timelineName in constraintMap) { + const timelineMap = constraintMap[timelineName]; + let keyMap = timelineMap[0]; + if (!keyMap) continue; + const frames = timelineMap.length; + switch (timelineName) { + case "position": { + const timeline = new PathConstraintPositionTimeline(frames, frames, index); + readTimeline12(timelines, timelineMap, timeline, 0, constraint.positionMode === 0 /* Fixed */ ? scale : 1); + break; + } + case "spacing": { + const timeline = new PathConstraintSpacingTimeline(frames, frames, index); + readTimeline12(timelines, timelineMap, timeline, 0, constraint.spacingMode === 0 /* Length */ || constraint.spacingMode === 1 /* Fixed */ ? scale : 1); + break; + } + case "mix": { + const timeline = new PathConstraintMixTimeline(frames, frames * 3, index); + let time = getValue(keyMap, "time", 0); + let mixRotate = getValue(keyMap, "mixRotate", 1); + let mixX = getValue(keyMap, "mixX", 1); + let mixY = getValue(keyMap, "mixY", mixX); + for (let frame = 0, bezier = 0; ; frame++) { + timeline.setFrame(frame, time, mixRotate, mixX, mixY); + const nextMap = timelineMap[frame + 1]; + if (!nextMap) { + timeline.shrink(bezier); + break; + } + const time2 = getValue(nextMap, "time", 0); + const mixRotate2 = getValue(nextMap, "mixRotate", 1); + const mixX2 = getValue(nextMap, "mixX", 1); + const mixY2 = getValue(nextMap, "mixY", mixX2); + const curve = keyMap.curve; + if (curve) { + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, mixRotate, mixRotate2, 1); + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, mixX, mixX2, 1); + bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, mixY, mixY2, 1); + } + time = time2; + mixRotate = mixRotate2; + mixX = mixX2; + mixY = mixY2; + keyMap = nextMap; + } + timelines.push(timeline); + break; + } + } + } + } + } + if (map.physics) { + for (const constraintName in map.physics) { + const constraintMap = map.physics[constraintName]; + let index = -1; + if (constraintName.length > 0) { + const constraint = skeletonData.findConstraint(constraintName, PhysicsConstraintData); + if (!constraint) throw new Error(`Physics constraint not found: ${constraintName}`); + index = skeletonData.constraints.indexOf(constraint); + } + for (const timelineName in constraintMap) { + const timelineMap = constraintMap[timelineName]; + let keyMap = timelineMap[0]; + if (!keyMap) continue; + const frames = timelineMap.length; + let timeline; + let defaultValue = 0; + if (timelineName === "reset") { + const resetTimeline = new PhysicsConstraintResetTimeline(frames, index); + for (let frame = 0; keyMap != null; keyMap = timelineMap[frame + 1], frame++) + resetTimeline.setFrame(frame, getValue(keyMap, "time", 0)); + timelines.push(resetTimeline); + continue; + } + switch (timelineName) { + case "inertia": + timeline = new PhysicsConstraintInertiaTimeline(frames, frames, index); + break; + case "strength": + timeline = new PhysicsConstraintStrengthTimeline(frames, frames, index); + break; + case "damping": + timeline = new PhysicsConstraintDampingTimeline(frames, frames, index); + break; + case "mass": + timeline = new PhysicsConstraintMassTimeline(frames, frames, index); + break; + case "wind": + timeline = new PhysicsConstraintWindTimeline(frames, frames, index); + break; + case "gravity": + timeline = new PhysicsConstraintGravityTimeline(frames, frames, index); + break; + case "mix": { + defaultValue = 1; + timeline = new PhysicsConstraintMixTimeline(frames, frames, index); + break; + } + default: + continue; + } + readTimeline12(timelines, timelineMap, timeline, defaultValue, 1); + } + } + } + if (map.slider) { + for (const constraintName in map.slider) { + const constraintMap = map.slider[constraintName]; + const constraint = skeletonData.findConstraint(constraintName, SliderData); + if (!constraint) throw new Error(`Slider not found: ${constraintName}`); + const index = skeletonData.constraints.indexOf(constraint); + for (const timelineName in constraintMap) { + const timelineMap = constraintMap[timelineName]; + const keyMap = timelineMap[0]; + if (!keyMap) continue; + const frames = timelineMap.length; + switch (timelineName) { + case "time": + readTimeline12(timelines, timelineMap, new SliderTimeline(frames, frames, index), 1, 1); + break; + case "mix": + readTimeline12(timelines, timelineMap, new SliderMixTimeline(frames, frames, index), 1, 1); + break; + } + } + } + } + if (map.attachments) { + for (const attachmentsName in map.attachments) { + const attachmentsMap = map.attachments[attachmentsName]; + const skin = skeletonData.findSkin(attachmentsName); + if (!skin) throw new Error(`Skin not found: ${attachmentsName}`); + for (const slotMapName in attachmentsMap) { + const slotMap = attachmentsMap[slotMapName]; + const slot = skeletonData.findSlot(slotMapName); + if (!slot) throw new Error(`Attachment slot not found: ${slotMapName}`); + const slotIndex = slot.index; + for (const attachmentMapName in slotMap) { + const attachmentMap = slotMap[attachmentMapName]; + const attachment = skin.getAttachment(slotIndex, attachmentMapName); + if (!attachment) throw new Error(`Timeline attachment not found: ${attachmentMapName}`); + for (const timelineMapName in attachmentMap) { + const timelineMap = attachmentMap[timelineMapName]; + let keyMap = timelineMap[0]; + if (!keyMap) continue; + if (timelineMapName === "deform") { + const weighted = attachment.bones; + const vertices = attachment.vertices; + const deformLength = weighted ? vertices.length / 3 * 2 : vertices.length; + const timeline = new DeformTimeline(timelineMap.length, timelineMap.length, slotIndex, attachment); + let time = getValue(keyMap, "time", 0); + for (let frame = 0, bezier = 0; ; frame++) { + let deform; + const verticesValue = getValue(keyMap, "vertices", null); + if (!verticesValue) + deform = weighted ? Utils.newFloatArray(deformLength) : vertices; + else { + deform = Utils.newFloatArray(deformLength); + const start = getValue(keyMap, "offset", 0); + Utils.arrayCopy(verticesValue, 0, deform, start, verticesValue.length); + if (scale !== 1) { + for (let i = start, n = i + verticesValue.length; i < n; i++) + deform[i] *= scale; + } + if (!weighted) { + for (let i = 0; i < deformLength; i++) + deform[i] += vertices[i]; + } + } + timeline.setFrame(frame, time, deform); + const nextMap = timelineMap[frame + 1]; + if (!nextMap) { + timeline.shrink(bezier); + break; + } + const time2 = getValue(nextMap, "time", 0); + const curve = keyMap.curve; + if (curve) bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, 0, 1, 1); + time = time2; + keyMap = nextMap; + } + timelines.push(timeline); + } else if (timelineMapName === "sequence") { + const timeline = new SequenceTimeline(timelineMap.length, slotIndex, attachment); + let lastDelay = 0; + for (let frame = 0; frame < timelineMap.length; frame++) { + const delay = getValue(keyMap, "delay", lastDelay); + const time = getValue(keyMap, "time", 0); + const mode = SequenceMode[getValue(keyMap, "mode", "hold")]; + const index = getValue(keyMap, "index", 0); + timeline.setFrame(frame, time, mode, index, delay); + lastDelay = delay; + keyMap = timelineMap[frame + 1]; + } + timelines.push(timeline); + } + } + } + } + } + } + if (map.drawOrder) { + const timeline = new DrawOrderTimeline(map.drawOrder.length); + const slotCount = skeletonData.slots.length; + let frame = 0; + for (const drawOrderMap of map.drawOrder) { + timeline.setFrame(frame++, getValue(drawOrderMap, "time", 0), readDrawOrder2(skeletonData, drawOrderMap, slotCount, null)); + } + timelines.push(timeline); + } + if (map.drawOrderFolder) { + for (const timelineMap of map.drawOrderFolder) { + const slotEntries = getValue(timelineMap, "slots", []); + const folderSlots = new Array(slotEntries.length); + let ii = 0; + for (const slotEntry of slotEntries) { + const slot = skeletonData.findSlot(slotEntry); + if (!slot) throw new Error(`Draw order folder slot not found: ${slotEntry}`); + folderSlots[ii++] = slot.index; + } + const drawOrderFolderEntries = getValue(timelineMap, "keys", []); + const timeline = new DrawOrderFolderTimeline(drawOrderFolderEntries.length, folderSlots, skeletonData.slots.length); + let frame = 0; + for (const drawOrderFolderMap of drawOrderFolderEntries) { + timeline.setFrame(frame++, getValue(drawOrderFolderMap, "time", 0), readDrawOrder2(skeletonData, drawOrderFolderMap, folderSlots.length, folderSlots)); + } + timelines.push(timeline); + } + } + if (map.events) { + const timeline = new EventTimeline(map.events.length); + let frame = 0; + for (let i = 0; i < map.events.length; i++, frame++) { + const eventMap = map.events[i]; + const data = skeletonData.findEvent(eventMap.name); + if (!data) throw new Error(`Event not found: ${eventMap.name}`); + const setup = data.setupPose; + const event = new Event(Utils.toSinglePrecision(getValue(eventMap, "time", 0)), data); + event.intValue = getValue(eventMap, "int", setup.intValue); + event.floatValue = getValue(eventMap, "float", setup.floatValue); + event.stringValue = getValue(eventMap, "string", setup.stringValue); + if (event.data.audioPath) { + event.volume = getValue(eventMap, "volume", setup.volume); + event.balance = getValue(eventMap, "balance", setup.volume); + } + timeline.setFrame(frame, event); + } + timelines.push(timeline); + } + let duration = 0; + for (let i = 0, n = timelines.length; i < n; i++) + duration = Math.max(duration, timelines[i].getDuration()); + const animation = new Animation(name, timelines, duration); + const color = getValue(map, "color", null); + if (color !== null) animation.color.setFromString(color); + skeletonData.animations.push(animation); + } + }; + var LinkedMesh2 = class { + source; + skin; + slotIndex; + sourceIndex; + mesh; + inheritTimelines; + constructor(mesh, skin, slotIndex, sourceIndex, source, inheritTimelines) { + this.mesh = mesh; + this.skin = skin; + this.slotIndex = slotIndex; + this.sourceIndex = sourceIndex; + this.source = source; + this.inheritTimelines = inheritTimelines; + } + }; + function readTimeline12(timelines, keys, timeline, defaultValue, scale) { + let keyMap = keys[0]; + let time = keyMap.time ?? 0; + let value = (keyMap.value ?? defaultValue) * scale; + let bezier = 0; + for (let frame = 0; ; frame++) { + timeline.setFrame(frame, time, value); + const nextMap = keys[frame + 1]; + if (!nextMap) { + timeline.shrink(bezier); + timelines.push(timeline); + return; + } + const time2 = nextMap.time ?? 0; + const value2 = (nextMap.value ?? defaultValue) * scale; + if (keyMap.curve) bezier = readCurve(keyMap.curve, timeline, bezier, frame, 0, time, time2, value, value2, scale); + time = time2; + value = value2; + keyMap = nextMap; + } + } + function readTimeline22(timelines, keys, timeline, name1, name2, defaultValue, scale) { + let keyMap = keys[0]; + let time = keyMap.time ?? 0; + let value1 = (keyMap[name1] ?? defaultValue) * scale; + let value2 = (keyMap[name2] ?? defaultValue) * scale; + let bezier = 0; + for (let frame = 0; ; frame++) { + timeline.setFrame(frame, time, value1, value2); + const nextMap = keys[frame + 1]; + if (!nextMap) { + timeline.shrink(bezier); + timelines.push(timeline); + return; + } + const time2 = nextMap.time ?? 0; + const nvalue1 = (nextMap[name1] ?? defaultValue) * scale; + const nvalue2 = (nextMap[name2] ?? defaultValue) * scale; + const curve = keyMap.curve; + if (curve) { + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, value1, nvalue1, scale); + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, value2, nvalue2, scale); + } + time = time2; + value1 = nvalue1; + value2 = nvalue2; + keyMap = nextMap; + } + } + function readDrawOrder2(skeletonData, keys, slotCount, folderSlots) { + const changes = keys.offsets; + if (!changes) return null; + const drawOrder = new Array(slotCount).fill(-1); + const unchanged = new Array(slotCount - changes.length); + let originalIndex = 0, unchangedIndex = 0; + for (const offsetMap of changes) { + const slot = skeletonData.findSlot(offsetMap.slot); + if (slot == null) throw new Error(`Draw order slot not found: ${offsetMap.slot}`); + let index = 0; + if (!folderSlots) + index = slot.index; + else { + index = -1; + for (let i = 0; i < slotCount; i++) { + if (folderSlots[i] === slot.index) { + index = i; + break; + } + } + if (index === -1) throw new Error(`Slot not in folder: ${offsetMap.slot}`); + } + while (originalIndex !== index) + unchanged[unchangedIndex++] = originalIndex++; + drawOrder[originalIndex + offsetMap.offset] = originalIndex++; + } + while (originalIndex < slotCount) + unchanged[unchangedIndex++] = originalIndex++; + for (let i = slotCount - 1; i >= 0; i--) + if (drawOrder[i] === -1) drawOrder[i] = unchanged[--unchangedIndex]; + return drawOrder; + } + function readCurve(curve, timeline, bezier, frame, value, time1, time2, value1, value2, scale) { + if (curve === "stepped") { + timeline.setStepped(frame); + return bezier; + } + const i = value << 2; + const cx1 = curve[i]; + const cy1 = curve[i + 1] * scale; + const cx2 = curve[i + 2]; + const cy2 = curve[i + 3] * scale; + timeline.setBezier(bezier, frame, value, time1, value1, cx1, cy1, cx2, cy2, time2, value2); + return bezier + 1; + } + function getValue(map, property, defaultValue) { + return map[property] !== void 0 ? map[property] : defaultValue; + } + + // spine-core/src/SkeletonPhysicsMovement.ts + var SkeletonPhysicsMovement = class { + /** + * Creates a movement tracker for a skeleton displayed by a host runtime object. + * @param skeleton The skeleton whose physics constraints receive inherited movement. + * @param adapter Runtime-specific hooks used to read and convert the host object's transform. + * @param options Initial movement inheritance values. + */ + constructor(skeleton, adapter, options = {}) { + this.skeleton = skeleton; + this.adapter = adapter; + this.positionInheritanceFactorX = options.positionInheritanceX ?? 0; + this.positionInheritanceFactorY = options.positionInheritanceY ?? 0; + this.rotationInheritanceFactor = options.rotationInheritance ?? 0; + } + positionInheritanceFactorX = 0; + positionInheritanceFactorY = 0; + rotationInheritanceFactor = 0; + hasLastTransform = false; + lastX = 0; + lastY = 0; + lastZ = 0; + lastRotation = 0; + currentTransform = { x: 0, y: 0, z: 0, rotation: 0 }; + currentPosition = { x: 0, y: 0, z: 0 }; + lastPosition = { x: 0, y: 0, z: 0 }; + /** Horizontal position inheritance factor. `0` disables horizontal position inheritance. */ + get positionInheritanceX() { + return this.positionInheritanceFactorX; + } + /** Vertical position inheritance factor. `0` disables vertical position inheritance. */ + get positionInheritanceY() { + return this.positionInheritanceFactorY; + } + /** + * Sets how much host object translation is inherited by skeleton physics constraints. + * Use `(1, 1)` for normal inheritance, or `(0, 0)` to disable position inheritance. + * @param x The horizontal position inheritance factor. + * @param y The vertical position inheritance factor. + */ + setPositionInheritance(x, y) { + const wasDisabled = this.positionInheritanceFactorX === 0 && this.positionInheritanceFactorY === 0; + const isEnabled = x !== 0 || y !== 0; + this.positionInheritanceFactorX = x; + this.positionInheritanceFactorY = y; + if (wasDisabled && isEnabled) this.resetPosition(); + } + /** Rotation inheritance factor. `0` disables rotation inheritance. */ + get rotationInheritance() { + return this.rotationInheritanceFactor; + } + /** + * Sets how much host object rotation is inherited by skeleton physics constraints. + * @param value The rotation inheritance factor. + */ + set rotationInheritance(value) { + const wasDisabled = this.rotationInheritanceFactor === 0; + this.rotationInheritanceFactor = value; + if (wasDisabled && value !== 0) this.resetRotation(); + } + /** Resets the previous position used to calculate inherited translation. */ + resetPosition() { + const transform = this.currentTransform; + const readRotation = !this.hasLastTransform && this.rotationInheritanceFactor !== 0; + this.adapter.readTransform(transform, readRotation); + this.lastX = transform.x; + this.lastY = transform.y; + this.lastZ = transform.z; + if (readRotation) this.lastRotation = transform.rotation; + this.hasLastTransform = true; + } + /** Resets the previous rotation used to calculate inherited rotation. */ + resetRotation() { + const transform = this.currentTransform; + this.adapter.readTransform(transform, true); + this.lastRotation = transform.rotation; + if (!this.hasLastTransform) { + this.lastX = transform.x; + this.lastY = transform.y; + this.lastZ = transform.z; + } + this.hasLastTransform = true; + } + /** Resets both previous position and previous rotation. */ + resetTransform() { + const transform = this.currentTransform; + this.adapter.readTransform(transform, true); + this.setLastTransform(transform.x, transform.y, transform.z, transform.rotation); + } + /** + * Applies host object transform movement since the previous call to the skeleton's physics constraints. + * + * The first call records the current transform as the baseline and does not apply movement. + */ + applyTransformMovement() { + const inheritPosition = this.positionInheritanceFactorX !== 0 || this.positionInheritanceFactorY !== 0; + const inheritRotation = this.rotationInheritanceFactor !== 0; + if (!inheritPosition && !inheritRotation) return; + const transform = this.currentTransform; + this.adapter.readTransform(transform, inheritRotation); + const { x, y, z } = transform; + const currentRotation = inheritRotation ? transform.rotation : this.lastRotation; + const positionChanged = x !== this.lastX || y !== this.lastY || z !== this.lastZ; + if (this.hasLastTransform) { + if (!positionChanged && currentRotation === this.lastRotation) return; + if (inheritPosition && positionChanged) this.applyPositionMovement(x, y, z); + if (inheritRotation && currentRotation !== this.lastRotation) this.applyRotationMovement(currentRotation); + } + this.setLastTransform(x, y, z, currentRotation); + } + applyPositionMovement(currentX, currentY, currentZ) { + const currentPosition = this.currentPosition; + currentPosition.x = currentX; + currentPosition.y = currentY; + currentPosition.z = currentZ; + this.adapter.worldToSkeleton(currentPosition); + const lastPosition = this.lastPosition; + lastPosition.x = this.lastX; + lastPosition.y = this.lastY; + lastPosition.z = this.lastZ; + this.adapter.worldToSkeleton(lastPosition); + this.skeleton.physicsTranslate( + (currentPosition.x - lastPosition.x) * this.positionInheritanceFactorX, + (currentPosition.y - lastPosition.y) * this.positionInheritanceFactorY + ); + } + applyRotationMovement(currentRotation) { + const rotationFactor = this.rotationInheritanceFactor; + if (rotationFactor === 0) return; + this.skeleton.physicsRotate(0, 0, this.getRotationDelta(currentRotation, this.lastRotation) * rotationFactor); + } + setLastTransform(x, y, z, rotation) { + this.lastX = x; + this.lastY = y; + this.lastZ = z; + this.lastRotation = rotation; + this.hasLastTransform = true; + } + getRotationDelta(current, previous) { + let delta = current - previous; + delta = (delta + 180) % 360 - 180; + return delta < -180 ? delta + 360 : delta; + } + }; + + // spine-core/src/SkeletonRendererCore.ts + var SkeletonRendererCore = class { + commandPool = new CommandPool(); + worldVertices = new Float32Array(12 * 1024); + quadIndices = new Uint16Array([0, 1, 2, 2, 3, 0]); + clipping = new SkeletonClipping(); + renderCommands = []; + render(skeleton, pma = false, inColor, stride = 2, slotZOffset = 0) { + this.commandPool.reset(); + this.renderCommands.length = 0; + const clipper = this.clipping; + let z = 0; + const drawOrder = skeleton.drawOrder.appliedPose; + for (let i = 0, n = drawOrder.length; i < n; i++) { + const slot = drawOrder[i]; + if (!slot.bone.active) { + clipper.clipEnd(slot); + continue; + } + const pose = slot.appliedPose; + const attachment = pose.attachment; + if (!attachment) { + clipper.clipEnd(slot); + continue; + } + const slotColor = pose.color; + const alpha = slotColor.a; + if (alpha === 0 && !(attachment instanceof ClippingAttachment)) { + clipper.clipEnd(slot); + continue; + } + let vertices; + let verticesCount; + let uvs; + let indices; + let indicesCount; + let attachmentColor; + let texture; + if (attachment instanceof RegionAttachment) { + attachmentColor = attachment.color; + if (attachmentColor.a === 0) { + clipper.clipEnd(slot); + continue; + } + const sequence = attachment.sequence; + const sequenceIndex = sequence.resolveIndex(pose); + attachment.computeWorldVertices(slot, attachment.getOffsets(pose), this.worldVertices, 0, stride); + vertices = this.worldVertices; + verticesCount = 4; + uvs = sequence.getUVs(sequenceIndex); + indices = this.quadIndices; + indicesCount = 6; + texture = sequence.regions[sequenceIndex]?.texture; + } else if (attachment instanceof MeshAttachment) { + attachmentColor = attachment.color; + if (attachmentColor.a === 0) { + clipper.clipEnd(slot); + continue; + } + if (this.worldVertices.length < attachment.worldVerticesLength) + this.worldVertices = new Float32Array(attachment.worldVerticesLength); + attachment.computeWorldVertices(skeleton, slot, 0, attachment.worldVerticesLength, this.worldVertices, 0, stride); + vertices = this.worldVertices; + verticesCount = attachment.worldVerticesLength >> 1; + const sequence = attachment.sequence; + const sequenceIndex = sequence.resolveIndex(pose); + uvs = sequence.getUVs(sequenceIndex); + indices = attachment.triangles; + indicesCount = indices.length; + texture = sequence.regions[sequenceIndex]?.texture; + } else if (attachment instanceof ClippingAttachment) { + clipper.clipEnd(slot); + clipper.clipStart(skeleton, slot, attachment); + continue; + } else { + clipper.clipEnd(slot); + continue; + } + if (!texture) { + clipper.clipEnd(slot); + continue; + } + const skelColor = skeleton.color; + let color, darkColor; + if (pma) { + let a; + if (inColor) { + a = Math.floor(inColor[3] * skelColor.a * slotColor.a * attachmentColor.a * 255); + const r = Math.floor(a * inColor[0] * skelColor.r * slotColor.r * attachmentColor.r); + const g = Math.floor(a * inColor[1] * skelColor.g * slotColor.g * attachmentColor.g); + const b = Math.floor(a * inColor[2] * skelColor.b * slotColor.b * attachmentColor.b); + color = a << 24 | r << 16 | g << 8 | b; + } else { + a = Math.floor(skelColor.a * slotColor.a * attachmentColor.a * 255); + const r = Math.floor(a * skelColor.r * slotColor.r * attachmentColor.r); + const g = Math.floor(a * skelColor.g * slotColor.g * attachmentColor.g); + const b = Math.floor(a * skelColor.b * slotColor.b * attachmentColor.b); + color = a << 24 | r << 16 | g << 8 | b; + } + darkColor = 4278190080; + if (pose.darkColor) { + const { r, g, b } = pose.darkColor; + darkColor = 4278190080 | Math.floor(r * a) << 16 | Math.floor(g * a) << 8 | Math.floor(b * a); + } + } else { + if (inColor) { + const a = Math.floor(inColor[3] * skelColor.a * slotColor.a * attachmentColor.a * 255); + const r = Math.floor(inColor[0] * skelColor.r * slotColor.r * attachmentColor.r * 255); + const g = Math.floor(inColor[1] * skelColor.g * slotColor.g * attachmentColor.g * 255); + const b = Math.floor(inColor[2] * skelColor.b * slotColor.b * attachmentColor.b * 255); + color = a << 24 | r << 16 | g << 8 | b; + } else { + const a = Math.floor(skelColor.a * slotColor.a * attachmentColor.a * 255); + const r = Math.floor(skelColor.r * slotColor.r * attachmentColor.r * 255); + const g = Math.floor(skelColor.g * slotColor.g * attachmentColor.g * 255); + const b = Math.floor(skelColor.b * slotColor.b * attachmentColor.b * 255); + color = a << 24 | r << 16 | g << 8 | b; + } + darkColor = 0; + if (pose.darkColor) { + const { r, g, b } = pose.darkColor; + darkColor = Math.floor(r * 255) << 16 | Math.floor(g * 255) << 8 | Math.floor(b * 255); + } + } + if (clipper.isClipping()) { + clipper.clipTrianglesUnpacked(vertices, 0, indices, indicesCount, uvs, stride); + vertices = clipper.clippedVerticesTyped; + verticesCount = clipper.clippedVerticesLength / stride; + uvs = clipper.clippedUVsTyped; + indices = clipper.clippedTrianglesTyped; + indicesCount = clipper.clippedTrianglesLength; + } + const cmd = this.commandPool.getCommand(verticesCount, indicesCount, stride); + cmd.blendMode = slot.data.blendMode; + cmd.texture = texture; + cmd.positions.set(vertices.subarray(0, verticesCount * stride)); + if (stride >= 3) { + for (let j = 2, n2 = verticesCount * stride; j < n2; j += stride) + cmd.positions[j] = z; + } + cmd.uvs.set(uvs.subarray(0, verticesCount << 1)); + for (let j = 0; j < verticesCount; j++) { + cmd.colors[j] = color; + cmd.darkColors[j] = darkColor; + } + if (indices instanceof Uint16Array) { + cmd.indices.set(indices.subarray(0, indicesCount)); + } else { + cmd.indices.set(indices.slice(0, indicesCount)); + } + this.renderCommands.push(cmd); + z += slotZOffset; + clipper.clipEnd(slot); + } + clipper.clipEnd(); + return this.batchCommands(stride); + } + batchSubCommands(commands, first, last, numVertices, numIndices, stride) { + const firstCmd = commands[first]; + const batched = this.commandPool.getCommand(numVertices, numIndices, stride); + batched.blendMode = firstCmd.blendMode; + batched.texture = firstCmd.texture; + let positionsOffset = 0; + let uvsOffset = 0; + let colorsOffset = 0; + let indicesOffset = 0; + let vertexOffset = 0; + for (let i = first; i <= last; i++) { + const cmd = commands[i]; + batched.positions.set(cmd.positions, positionsOffset); + positionsOffset += cmd.numVertices * stride; + batched.uvs.set(cmd.uvs, uvsOffset); + uvsOffset += cmd.numVertices << 1; + batched.colors.set(cmd.colors, colorsOffset); + batched.darkColors.set(cmd.darkColors, colorsOffset); + colorsOffset += cmd.numVertices; + for (let j = 0; j < cmd.numIndices; j++) + batched.indices[indicesOffset + j] = cmd.indices[j] + vertexOffset; + indicesOffset += cmd.numIndices; + vertexOffset += cmd.numVertices; + } + return batched; + } + batchCommands(stride) { + if (this.renderCommands.length === 0) return void 0; + let root; + let last; + let first = this.renderCommands[0]; + let startIndex = 0; + let i = 1; + let numVertices = first.numVertices; + let numIndices = first.numIndices; + while (i <= this.renderCommands.length) { + const cmd = i < this.renderCommands.length ? this.renderCommands[i] : null; + if (cmd && cmd.numVertices === 0 && cmd.numIndices === 0) { + i++; + continue; + } + const canBatch = cmd !== null && cmd.texture === first.texture && cmd.blendMode === first.blendMode && cmd.colors[0] === first.colors[0] && cmd.darkColors[0] === first.darkColors[0] && numIndices + cmd.numIndices < 65535; + if (canBatch) { + numVertices += cmd.numVertices; + numIndices += cmd.numIndices; + } else { + const batched = this.batchSubCommands( + this.renderCommands, + startIndex, + i - 1, + numVertices, + numIndices, + stride + ); + if (!last) { + root = last = batched; + } else { + last.next = batched; + last = batched; + } + if (i === this.renderCommands.length) break; + first = this.renderCommands[i]; + startIndex = i; + numVertices = first.numVertices; + numIndices = first.numIndices; + } + i++; + } + return root; + } + }; + var CommandPool = class { + pool = []; + inUse = []; + getCommand(numVertices, numIndices, stride) { + let cmd; + for (const c of this.pool) { + if (c._positions.length >= numVertices * stride && c._indices.length >= numIndices) { + cmd = c; + break; + } + } + if (!cmd) { + const _positions = new Float32Array(numVertices * stride); + const _uvs = new Float32Array(numVertices << 1); + const _colors = new Uint32Array(numVertices); + const _darkColors = new Uint32Array(numVertices); + const _indices = new Uint16Array(numIndices); + cmd = { + positions: _positions, + uvs: _uvs, + colors: _colors, + darkColors: _darkColors, + indices: _indices, + _positions, + _uvs, + _colors, + _darkColors, + _indices, + numVertices, + numIndices, + blendMode: 0 /* Normal */, + texture: null + }; + } else { + this.pool.splice(this.pool.indexOf(cmd), 1); + cmd.next = void 0; + cmd.numVertices = numVertices; + cmd.numIndices = numIndices; + cmd.positions = cmd._positions.subarray(0, numVertices * stride); + cmd.uvs = cmd._uvs.subarray(0, numVertices << 1); + cmd.colors = cmd._colors.subarray(0, numVertices); + cmd.darkColors = cmd._darkColors.subarray(0, numVertices); + cmd.indices = cmd._indices.subarray(0, numIndices); + } + this.inUse.push(cmd); + return cmd; + } + reset() { + this.pool.push(...this.inUse); + this.inUse.length = 0; + } + }; + + // spine-canvas/src/CanvasTexture.ts + var CanvasTexture = class extends Texture { + constructor(image) { + super(image); + } + setFilters(minFilter, magFilter) { + } + setWraps(uWrap, vWrap) { + } + dispose() { + } + }; + + // spine-canvas/src/AssetManager.ts + var AssetManager = class extends AssetManagerBase { + constructor(pathPrefix = "", downloader = new Downloader()) { + super((image) => { + return new CanvasTexture(image); + }, pathPrefix, downloader); + } + }; + + // spine-canvas/src/SkeletonRenderer.ts + var worldVertices = Utils.newFloatArray(8); + var SkeletonRenderer = class _SkeletonRenderer { + static QUAD_TRIANGLES = [0, 1, 2, 2, 3, 0]; + static VERTEX_SIZE = 2 + 2 + 4; + ctx; + triangleRendering = false; + debugRendering = false; + vertices = Utils.newFloatArray(8 * 1024); + tempColor = new Color(); + constructor(context) { + this.ctx = context; + } + draw(skeleton) { + if (this.triangleRendering) this.drawTriangles(skeleton); + else this.drawImages(skeleton); + } + drawImages(skeleton) { + const ctx = this.ctx; + const color = this.tempColor; + const skeletonColor = skeleton.color; + const drawOrder = skeleton.drawOrder.appliedPose; + if (this.debugRendering) ctx.strokeStyle = "green"; + for (let i = 0, n = drawOrder.length; i < n; i++) { + const slot = drawOrder[i]; + const bone = slot.bone; + if (!bone.active) continue; + const pose = slot.appliedPose; + const attachment = pose.attachment; + if (!(attachment instanceof RegionAttachment)) continue; + const sequence = attachment.sequence; + const sequenceIndex = sequence.resolveIndex(pose); + attachment.computeWorldVertices(slot, attachment.getOffsets(pose), worldVertices, 0, 2); + const region = sequence.regions[sequenceIndex]; + const image = region.texture.getImage(); + const slotColor = pose.color; + const regionColor = attachment.color; + color.set( + skeletonColor.r * slotColor.r * regionColor.r, + skeletonColor.g * slotColor.g * regionColor.g, + skeletonColor.b * slotColor.b * regionColor.b, + skeletonColor.a * slotColor.a * regionColor.a + ); + ctx.save(); + const boneApplied = bone.appliedPose; + ctx.transform(boneApplied.a, boneApplied.c, boneApplied.b, boneApplied.d, boneApplied.worldX, boneApplied.worldY); + const offsets = attachment.getOffsets(pose); + ctx.translate(offsets[0], offsets[1]); + ctx.rotate(attachment.rotation * Math.PI / 180); + const atlasScale = attachment.width / region.originalWidth; + ctx.scale(atlasScale * attachment.scaleX, atlasScale * attachment.scaleY); + let w = region.width, h = region.height; + ctx.translate(w / 2, h / 2); + if (region.degrees === 90) { + const t = w; + w = h; + h = t; + ctx.rotate(-Math.PI / 2); + } + ctx.scale(1, -1); + ctx.translate(-w / 2, -h / 2); + ctx.globalAlpha = color.a; + ctx.drawImage(image, image.width * region.u, image.height * region.v, w, h, 0, 0, w, h); + if (this.debugRendering) ctx.strokeRect(0, 0, w, h); + ctx.restore(); + } + } + drawTriangles(skeleton) { + const ctx = this.ctx; + const color = this.tempColor; + const skeletonColor = skeleton.color; + const drawOrder = skeleton.drawOrder.appliedPose; + let blendMode = null; + let vertices = this.vertices; + let triangles = null; + for (let i = 0, n = drawOrder.length; i < n; i++) { + const slot = drawOrder[i]; + const pose = slot.appliedPose; + const attachment = pose.attachment; + let texture; + if (attachment instanceof RegionAttachment) { + const sequence = attachment.sequence; + const sequenceIndex = sequence.resolveIndex(pose); + const uvs = sequence.getUVs(sequenceIndex); + const offsets = attachment.getOffsets(pose); + vertices = this.computeRegionVertices(slot, attachment, offsets, uvs, false); + triangles = _SkeletonRenderer.QUAD_TRIANGLES; + texture = (sequence.regions[sequenceIndex]?.texture).getImage(); + } else if (attachment instanceof MeshAttachment) { + const sequence = attachment.sequence; + const sequenceIndex = sequence.resolveIndex(pose); + const uvs = sequence.getUVs(sequenceIndex); + vertices = this.computeMeshVertices(slot, attachment, uvs, false); + triangles = attachment.triangles; + texture = (sequence.regions[sequenceIndex]?.texture).getImage(); + } else + continue; + if (texture) { + if (slot.data.blendMode !== blendMode) blendMode = slot.data.blendMode; + const slotColor = pose.color; + const attachmentColor = attachment.color; + color.set( + skeletonColor.r * slotColor.r * attachmentColor.r, + skeletonColor.g * slotColor.g * attachmentColor.g, + skeletonColor.b * slotColor.b * attachmentColor.b, + skeletonColor.a * slotColor.a * attachmentColor.a + ); + ctx.globalAlpha = color.a; + for (let j = 0; j < triangles.length; j += 3) { + const t1 = triangles[j] * 8, t2 = triangles[j + 1] * 8, t3 = triangles[j + 2] * 8; + const x0 = vertices[t1], y0 = vertices[t1 + 1], u0 = vertices[t1 + 6], v0 = vertices[t1 + 7]; + const x1 = vertices[t2], y1 = vertices[t2 + 1], u1 = vertices[t2 + 6], v1 = vertices[t2 + 7]; + const x2 = vertices[t3], y2 = vertices[t3 + 1], u2 = vertices[t3 + 6], v2 = vertices[t3 + 7]; + this.drawTriangle(texture, x0, y0, u0, v0, x1, y1, u1, v1, x2, y2, u2, v2); + if (this.debugRendering) { + ctx.strokeStyle = "green"; + ctx.beginPath(); + ctx.moveTo(x0, y0); + ctx.lineTo(x1, y1); + ctx.lineTo(x2, y2); + ctx.lineTo(x0, y0); + ctx.stroke(); + } + } + } + } + this.ctx.globalAlpha = 1; + } + // Adapted from http://extremelysatisfactorytotalitarianism.com/blog/?p=2120 + // Apache 2 licensed + drawTriangle(img, x0, y0, u0, v0, x1, y1, u1, v1, x2, y2, u2, v2) { + const ctx = this.ctx; + const width = img.width - 1; + const height = img.height - 1; + u0 *= width; + v0 *= height; + u1 *= width; + v1 *= height; + u2 *= width; + v2 *= height; + ctx.beginPath(); + ctx.moveTo(x0, y0); + ctx.lineTo(x1, y1); + ctx.lineTo(x2, y2); + ctx.closePath(); + x1 -= x0; + y1 -= y0; + x2 -= x0; + y2 -= y0; + u1 -= u0; + v1 -= v0; + u2 -= u0; + v2 -= v0; + let det = u1 * v2 - u2 * v1; + if (det === 0) return; + det = 1 / det; + const a = (v2 * x1 - v1 * x2) * det; + const b = (v2 * y1 - v1 * y2) * det; + const c = (u1 * x2 - u2 * x1) * det; + const d = (u1 * y2 - u2 * y1) * det; + const e = x0 - a * u0 - c * v0; + const f = y0 - b * u0 - d * v0; + ctx.save(); + ctx.transform(a, b, c, d, e, f); + ctx.clip(); + ctx.drawImage(img, 0, 0); + ctx.restore(); + } + computeRegionVertices(slot, region, offsets, uvs, pma) { + const skeletonColor = slot.skeleton.color; + const slotColor = slot.appliedPose.color; + const regionColor = region.color; + const alpha = skeletonColor.a * slotColor.a * regionColor.a; + const multiplier = pma ? alpha : 1; + const color = this.tempColor; + color.set( + skeletonColor.r * slotColor.r * regionColor.r * multiplier, + skeletonColor.g * slotColor.g * regionColor.g * multiplier, + skeletonColor.b * slotColor.b * regionColor.b * multiplier, + alpha + ); + region.computeWorldVertices(slot, offsets, this.vertices, 0, _SkeletonRenderer.VERTEX_SIZE); + const vertices = this.vertices; + vertices[RegionAttachment.C1R] = color.r; + vertices[RegionAttachment.C1G] = color.g; + vertices[RegionAttachment.C1B] = color.b; + vertices[RegionAttachment.C1A] = color.a; + vertices[RegionAttachment.U1] = uvs[0]; + vertices[RegionAttachment.V1] = uvs[1]; + vertices[RegionAttachment.C2R] = color.r; + vertices[RegionAttachment.C2G] = color.g; + vertices[RegionAttachment.C2B] = color.b; + vertices[RegionAttachment.C2A] = color.a; + vertices[RegionAttachment.U2] = uvs[2]; + vertices[RegionAttachment.V2] = uvs[3]; + vertices[RegionAttachment.C3R] = color.r; + vertices[RegionAttachment.C3G] = color.g; + vertices[RegionAttachment.C3B] = color.b; + vertices[RegionAttachment.C3A] = color.a; + vertices[RegionAttachment.U3] = uvs[4]; + vertices[RegionAttachment.V3] = uvs[5]; + vertices[RegionAttachment.C4R] = color.r; + vertices[RegionAttachment.C4G] = color.g; + vertices[RegionAttachment.C4B] = color.b; + vertices[RegionAttachment.C4A] = color.a; + vertices[RegionAttachment.U4] = uvs[6]; + vertices[RegionAttachment.V4] = uvs[7]; + return vertices; + } + computeMeshVertices(slot, mesh, uvs, pma) { + const skeleton = slot.skeleton; + const skeletonColor = skeleton.color; + const slotColor = slot.appliedPose.color; + const regionColor = mesh.color; + const alpha = skeletonColor.a * slotColor.a * regionColor.a; + const multiplier = pma ? alpha : 1; + const color = this.tempColor; + color.set( + skeletonColor.r * slotColor.r * regionColor.r * multiplier, + skeletonColor.g * slotColor.g * regionColor.g * multiplier, + skeletonColor.b * slotColor.b * regionColor.b * multiplier, + alpha + ); + const vertexCount = mesh.worldVerticesLength / 2; + let vertices = this.vertices; + if (vertices.length < mesh.worldVerticesLength) this.vertices = vertices = Utils.newFloatArray(mesh.worldVerticesLength); + mesh.computeWorldVertices(skeleton, slot, 0, mesh.worldVerticesLength, vertices, 0, _SkeletonRenderer.VERTEX_SIZE); + for (let i = 0, u = 0, v = 2; i < vertexCount; i++) { + vertices[v++] = color.r; + vertices[v++] = color.g; + vertices[v++] = color.b; + vertices[v++] = color.a; + vertices[v++] = uvs[u++]; + vertices[v++] = uvs[u++]; + v += 2; + } + return vertices; + } + }; + return __toCommonJS(index_exports); +})(); +//# sourceMappingURL=spine-canvas.js.map diff --git a/html/assets/spine/spine-canvas-clipping.js b/html/assets/spine/spine-canvas-clipping.js new file mode 100644 index 0000000..e055548 --- /dev/null +++ b/html/assets/spine/spine-canvas-clipping.js @@ -0,0 +1,243 @@ +'use strict'; + +// The official spine-canvas SkeletonRenderer does not process clipping +// attachments. Keep its triangle rasterizer, but feed it geometry produced by +// SkeletonClipping so the viewer matches Spine's draw order and clipping rules. + +const QUAD_TRIANGLES = [0, 1, 2, 2, 3, 0]; +const BLEND_MODES = ['source-over', 'lighter', 'multiply', 'screen']; +const unpremultipliedImages = new WeakMap(); + +function rendererConstructor(spine) { + return (spine.canvas && spine.canvas.SkeletonRenderer) || spine.SkeletonRenderer; +} + +function imageFromAttachment(attachment) { + const region = attachment && attachment.region; + // 4.0 stores the texture on the atlas page, while the other bundled + // runtimes expose it directly on the attachment region. + const renderRegion = region && region.renderObject; + const texture = region && (region.texture + || (region.page && region.page.texture) + || (renderRegion && renderRegion.texture) + || (renderRegion && renderRegion.page && renderRegion.page.texture)); + return texture && typeof texture.getImage === 'function' ? texture.getImage() : null; +} + +// Canvas 2D always applies source-alpha blending. A Spine texture exported +// with PMA already has RGB multiplied by alpha, so drawing it directly would +// multiply translucent edge pixels a second time. Convert it once to straight +// alpha and cache the result; Canvas can then blend it correctly. +function imageForCanvas(image, context, premultipliedAlpha) { + if (!premultipliedAlpha || !image || (typeof image !== 'object' && typeof image !== 'function')) return image; + const cached = unpremultipliedImages.get(image); + if (cached) return cached; + try { + const ownerDocument = context.canvas && context.canvas.ownerDocument; + const doc = ownerDocument || (typeof document !== 'undefined' ? document : null); + if (!image.width || !image.height) return image; + let canvas; + if (doc) canvas = doc.createElement('canvas'); + else { + try { canvas = require('@napi-rs/canvas').createCanvas(image.width, image.height); } + catch (e) { return image; } + } + canvas.width = image.width; + canvas.height = image.height; + const ctx = canvas.getContext('2d', { willReadFrequently: true }); + ctx.drawImage(image, 0, 0); + const pixels = ctx.getImageData(0, 0, canvas.width, canvas.height); + const data = pixels.data; + for (let i = 0; i < data.length; i += 4) { + const alpha = data[i + 3]; + if (alpha > 0 && alpha < 255) { + const factor = 255 / alpha; + data[i] = Math.min(255, Math.round(data[i] * factor)); + data[i + 1] = Math.min(255, Math.round(data[i + 1] * factor)); + data[i + 2] = Math.min(255, Math.round(data[i + 2] * factor)); + } + } + ctx.putImageData(pixels, 0, 0); + unpremultipliedImages.set(image, canvas); + return canvas; + } catch (e) { + // If pixel access is unavailable, retain the original texture so preview + // remains usable instead of failing the whole frame. + return image; + } +} + +function unpackVertices(vertices, count) { + const positions = new Array(count * 2); + const uvs = new Array(count * 2); + for (let i = 0, p = 0; i < count; i++, p += 2) { + const source = i * 8; + positions[p] = vertices[source]; + positions[p + 1] = vertices[source + 1]; + uvs[p] = vertices[source + 6]; + uvs[p + 1] = vertices[source + 7]; + } + return { positions, uvs }; +} + +function drawInterleaved(base, image, vertices, triangles, darkColor) { + for (let i = 0; i < triangles.length; i += 3) { + const a = triangles[i] * 8; + const b = triangles[i + 1] * 8; + const c = triangles[i + 2] * 8; + base.drawTriangle(image, + vertices[a], vertices[a + 1], vertices[a + 6], vertices[a + 7], + vertices[b], vertices[b + 1], vertices[b + 6], vertices[b + 7], + vertices[c], vertices[c + 1], vertices[c + 6], vertices[c + 7], + [...vertices.slice(a+2,a+6), ...vertices.slice(b+2,b+6), ...vertices.slice(c+2,c+6)], + darkColor ? Array.from({length:3}, () => [darkColor.r,darkColor.g,darkColor.b,0]).flat() : undefined); + } +} + +function createLegacyRenderer(spine, context) { + const Renderer = rendererConstructor(spine); + if (!Renderer || !spine.SkeletonClipping) throw new Error('Spine Canvas 渲染器不完整'); + const base = new Renderer(context); + base.triangleRendering = true; + base.premultipliedAlpha = false; + const clipper = new spine.SkeletonClipping(); + const dark = new spine.Color(0, 0, 0, 0); + + base.draw = function drawWithClipping(skeleton) { + const composite = context.globalCompositeOperation; + const drawOrder = skeleton.drawOrder; + const pma = !!base.premultipliedAlpha; + try { + for (let i = 0; i < drawOrder.length; i++) { + const slot = drawOrder[i]; + if (slot.bone && slot.bone.active === false) { + clipper.clipEndWithSlot(slot); + continue; + } + const attachment = slot.getAttachment(); + if (attachment instanceof spine.ClippingAttachment) { + clipper.clipStart(slot, attachment); + continue; + } + + try { + if (!attachment) continue; + let vertices; + let triangles; + let vertexCount; + if (attachment instanceof spine.RegionAttachment) { + vertices = base.computeRegionVertices(slot, attachment, false); + triangles = Renderer.QUAD_TRIANGLES || QUAD_TRIANGLES; + vertexCount = 4; + } else if (attachment instanceof spine.MeshAttachment) { + vertices = base.computeMeshVertices(slot, attachment, false); + triangles = attachment.triangles; + vertexCount = attachment.worldVerticesLength >> 1; + } else { + continue; + } + + const sourceImage = imageFromAttachment(attachment); + const image = base.gpuDrawing ? sourceImage : imageForCanvas(sourceImage, context, pma); + if (!image) continue; + context.globalAlpha = base.tempColor.a; + context.globalCompositeOperation = BLEND_MODES[slot.data.blendMode] || 'source-over'; + if (clipper.isClipping()) { + const unpacked = unpackVertices(vertices, vertexCount); + clipper.clipTriangles(unpacked.positions, unpacked.positions.length, + triangles, triangles.length, unpacked.uvs, base.tempColor, dark, false); + drawInterleaved(base, image, clipper.clippedVertices, clipper.clippedTriangles, slot.darkColor); + } else { + drawInterleaved(base, image, vertices, triangles, slot.darkColor); + } + } finally { + clipper.clipEndWithSlot(slot); + } + } + } finally { + clipper.clipEnd(); + context.globalAlpha = 1; + context.globalCompositeOperation = composite; + } + }; + base.clipper = clipper; + return base; +} + +// 3.4/3.5 predate clipping attachments and compute packed vertices on attachments. +function create34Renderer(spine, context) { + const base = new (rendererConstructor(spine))(context); + base.triangleRendering = true; + base.premultipliedAlpha = false; + base.draw = skeleton => { + const composite = context.globalCompositeOperation; + try { + for (const slot of skeleton.drawOrder) { + const attachment = slot.getAttachment(); + if (!(attachment instanceof spine.RegionAttachment) && !(attachment instanceof spine.MeshAttachment)) continue; + const vertices = attachment.updateWorldVertices(slot, false); + const sourceImage = imageFromAttachment(attachment); + const image = base.gpuDrawing ? sourceImage : imageForCanvas(sourceImage, context, base.premultipliedAlpha); + if (!image) continue; + context.globalAlpha = vertices[5]; + context.globalCompositeOperation = BLEND_MODES[slot.data.blendMode] || 'source-over'; + drawInterleaved(base, image, vertices, attachment instanceof spine.MeshAttachment ? attachment.triangles : QUAD_TRIANGLES); + } + } finally { context.globalAlpha = 1; context.globalCompositeOperation = composite; } + }; + return base; +} + +function create43Renderer(spine, context) { + const Renderer = rendererConstructor(spine); + if (!Renderer || !spine.SkeletonRendererCore) throw new Error('Spine 4.3 Canvas 渲染器不完整'); + const base = new Renderer(context); + base.triangleRendering = true; + base.premultipliedAlpha = false; + const core = new spine.SkeletonRendererCore(); + + base.draw = function drawWithCoreClipping(skeleton) { + const composite = context.globalCompositeOperation; + try { + const pma = !!base.premultipliedAlpha; + for (let command = core.render(skeleton, false); command; command = command.next) { + const sourceImage = command.texture && command.texture.getImage && command.texture.getImage(); + const image = base.gpuDrawing ? sourceImage : imageForCanvas(sourceImage, context, pma); + if (!image) continue; + context.globalAlpha = ((command.colors[0] >>> 24) & 255) / 255; + context.globalCompositeOperation = BLEND_MODES[command.blendMode] || 'source-over'; + const positions = command.positions; + const uvs = command.uvs; + const indices = command.indices; + for (let i = 0; i < command.numIndices; i += 3) { + const a = indices[i] * 2; + const b = indices[i + 1] * 2; + const c = indices[i + 2] * 2; + const unpack = packed => [((packed>>>16)&255)/255,((packed>>>8)&255)/255,(packed&255)/255,((packed>>>24)&255)/255]; + const colors = [a,b,c].flatMap(index => unpack(command.colors[index/2])); + const darkColors = [a,b,c].flatMap(index => unpack(command.darkColors[index/2])); + base.drawTriangle(image, + positions[a], positions[a + 1], uvs[a], uvs[a + 1], + positions[b], positions[b + 1], uvs[b], uvs[b + 1], + positions[c], positions[c + 1], uvs[c], uvs[c + 1], colors, darkColors); + } + } + } finally { + context.globalAlpha = 1; + context.globalCompositeOperation = composite; + } + }; + base.clipper = core.clipping; + return base; +} + +function createRenderer(spine, context, runtimeDir) { + const renderer = (runtimeDir === '3.4' || runtimeDir === '3.5') ? create34Renderer(spine, context) : runtimeDir === '4.3' + ? create43Renderer(spine, context) + : createLegacyRenderer(spine, context); + // Explicitly disable debug drawing in every supported runtime. + renderer.debugRendering = false; + return require('./spine-webgl-rasterizer.js').attach(renderer, context); +} + +module.exports = { createRenderer }; diff --git a/html/assets/spine/spine-webgl-rasterizer.js b/html/assets/spine/spine-webgl-rasterizer.js new file mode 100644 index 0000000..8aa147d --- /dev/null +++ b/html/assets/spine/spine-webgl-rasterizer.js @@ -0,0 +1,177 @@ +'use strict'; + +// Decode file bytes without the Canvas 2D premultiply/unpremultiply round trip. +// In low-alpha PMA pixels that round trip destroys RGB precision before blending. +const preparedTextures = new WeakMap(); +async function prepareTexture(image, blob) { + if (typeof createImageBitmap !== 'function') return; + const options = { colorSpaceConversion: 'none' }; + const [pma, straight] = await Promise.all([ + createImageBitmap(blob, { ...options, premultiplyAlpha: 'none' }), + createImageBitmap(blob, { ...options, premultiplyAlpha: 'premultiply' }) + ]); + preparedTextures.set(image, { pma, straight }); +} + +// Rasterize adjacent mesh triangles together, with GPU edge ownership rules. +// Canvas clip()+drawImage() anti-aliases each triangle separately and exposes +// the background along shared edges, which looks like a wireframe overlay. +function createRasterizer(context) { + const document = context.canvas && context.canvas.ownerDocument; + if (!document || typeof context.getTransform !== 'function') return null; + const canvas = document.createElement('canvas'); + const gl = canvas.getContext('webgl', { + alpha: true, premultipliedAlpha: true, antialias: true, + preserveDrawingBuffer: true, depth: false, stencil: false + }); + if (!gl) return null; + function shader(type, source) { + const shader = gl.createShader(type); + gl.shaderSource(shader, source); gl.compileShader(shader); + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(shader)); + return shader; + } + const vertex = shader(gl.VERTEX_SHADER, ` + attribute vec2 position; + attribute vec2 uv; + attribute vec4 light; + attribute vec4 dark; + uniform vec2 resolution; + varying vec2 textureUV; + varying vec4 lightColor; + varying vec4 darkColor; + void main() { + vec2 p = position / resolution * 2.0 - 1.0; + gl_Position = vec4(p.x, -p.y, 0.0, 1.0); + textureUV = uv; + lightColor = light; darkColor = dark; + }`); + const fragment = shader(gl.FRAGMENT_SHADER, ` + precision mediump float; + uniform sampler2D image; + varying vec2 textureUV; + varying vec4 lightColor; + varying vec4 darkColor; + void main() { + vec4 color = texture2D(image, textureUV); + float alpha = color.a * lightColor.a; + // Textures are PMA in GPU memory. Light/dark colors arrive straight. + vec3 rgb = (color.rgb * lightColor.rgb + (color.a - color.rgb) * darkColor.rgb) * lightColor.a; + gl_FragColor = vec4(rgb, alpha); + }`); + const program = gl.createProgram(); gl.attachShader(program, vertex); gl.attachShader(program, fragment); gl.linkProgram(program); + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) throw new Error(gl.getProgramInfoLog(program)); + gl.deleteShader(vertex); gl.deleteShader(fragment); + const buffer = gl.createBuffer(); + const position = gl.getAttribLocation(program, 'position'), uv = gl.getAttribLocation(program, 'uv'); + const resolution = gl.getUniformLocation(program, 'resolution'); + const light = gl.getAttribLocation(program, 'light'), dark = gl.getAttribLocation(program, 'dark'); + const textures = new Map(); + let coordinates = [], currentImage = null, currentAlpha = 1, currentMode = 'source-over', transform; + let disposed = false, lost = false; + let premultipliedAlpha = false; + canvas.addEventListener('webglcontextlost', event => { event.preventDefault(); lost = true; }); + function texture(image) { + const pma = image !== context.canvas && premultipliedAlpha; + let pair = textures.get(image); + if (!pair) { pair = {}; textures.set(image, pair); } + const key = pma ? 'pma' : 'straight'; + if (pair[key] && image !== context.canvas) return pair[key]; + const t = pair[key] || gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, t); + // Premultiply before interpolation so translucent edges do not darken. + gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, !pma); + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + const prepared = preparedTextures.get(image); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, prepared ? prepared[key] : image); + pair[key] = t; return t; + } + function flush() { + if (!coordinates.length) return; + gl.bindTexture(gl.TEXTURE_2D, texture(currentImage)); + // Premultiplied RGB factors; keep source-over alpha independently. + const factors = currentMode === 'lighter' ? [gl.ONE, gl.ONE] + : currentMode === 'multiply' ? [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA] + : currentMode === 'screen' ? [gl.ONE, gl.ONE_MINUS_SRC_COLOR] + : [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + gl.blendFuncSeparate(factors[0], factors[1], gl.ONE, gl.ONE_MINUS_SRC_ALPHA); + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(coordinates), gl.STREAM_DRAW); + gl.drawArrays(gl.TRIANGLES, 0, coordinates.length / 12); + coordinates.length = 0; + } + return { + begin(pma = false) { + if (disposed || lost || gl.isContextLost()) return false; + premultipliedAlpha = pma; + const target = context.canvas; + if (canvas.width !== target.width) canvas.width = target.width; + if (canvas.height !== target.height) canvas.height = target.height; + transform = context.getTransform(); coordinates.length = 0; currentImage = null; + gl.viewport(0, 0, canvas.width, canvas.height); + gl.clearColor(0, 0, 0, 0); gl.clear(gl.COLOR_BUFFER_BIT); + gl.useProgram(program); gl.bindBuffer(gl.ARRAY_BUFFER, buffer); + gl.enableVertexAttribArray(position); gl.vertexAttribPointer(position, 2, gl.FLOAT, false, 48, 0); + gl.enableVertexAttribArray(uv); gl.vertexAttribPointer(uv, 2, gl.FLOAT, false, 48, 8); + gl.enableVertexAttribArray(light); gl.vertexAttribPointer(light, 4, gl.FLOAT, false, 48, 16); + gl.enableVertexAttribArray(dark); gl.vertexAttribPointer(dark, 4, gl.FLOAT, false, 48, 32); + gl.uniform2f(resolution, canvas.width, canvas.height); + gl.activeTexture(gl.TEXTURE0); gl.uniform1i(gl.getUniformLocation(program, 'image'), 0); + gl.enable(gl.BLEND); gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); + // Seed the same framebuffer with the actual Canvas pixels (solid or + // checkerboard background). CSS backgrounds cannot participate in blending. + currentImage = target; currentAlpha = 1; currentMode = 'source-over'; + const w = canvas.width, h = canvas.height; + for (const vertex of [[0,0,0,0],[w,0,1,0],[w,h,1,1],[0,0,0,0],[w,h,1,1],[0,h,0,1]]) + coordinates.push(...vertex, 1,1,1,1, 0,0,0,0); + flush(); currentImage = null; + return true; + }, + triangle(image, x0, y0, u0, v0, x1, y1, u1, v1, x2, y2, u2, v2, colors, darkColors) { + const alpha = context.globalAlpha; + const mode = context.globalCompositeOperation || 'source-over'; + if (image !== currentImage || alpha !== currentAlpha || mode !== currentMode) { flush(); currentImage = image; currentAlpha = alpha; currentMode = mode; } + const t = transform; + const points = [[x0,y0,u0,v0],[x1,y1,u1,v1],[x2,y2,u2,v2]]; + for (let i=0; i<3; i++) { + const [x,y,u,v] = points[i], j=i*4; + coordinates.push(t.a*x+t.c*y+t.e, t.b*x+t.d*y+t.f, u,v, + ...(colors ? colors.slice(j,j+4) : [1,1,1,alpha]), + ...(darkColors ? darkColors.slice(j,j+4) : [0,0,0,0])); + } + }, + end() { + flush(); + context.save(); + try { context.setTransform(1, 0, 0, 1, 0, 0); context.globalAlpha = 1; context.globalCompositeOperation = 'copy'; context.drawImage(canvas, 0, 0); } + finally { context.restore(); } + }, + dispose() { + if (disposed) return; disposed = true; + for (const pair of textures.values()) for (const value of Object.values(pair)) gl.deleteTexture(value); + textures.clear(); gl.deleteBuffer(buffer); gl.deleteProgram(program); + const extension = gl.getExtension('WEBGL_lose_context'); if (extension) extension.loseContext(); + } + }; +} + +function attach(renderer, context) { + let rasterizer; + try { rasterizer = createRasterizer(context); } catch (e) { return renderer; } + if (!rasterizer) return renderer; + const draw = renderer.draw.bind(renderer), triangle = renderer.drawTriangle.bind(renderer); + renderer.draw = skeleton => { + if (!rasterizer.begin(!!renderer.premultipliedAlpha)) return draw(skeleton); + renderer.gpuDrawing = true; + renderer.drawTriangle = rasterizer.triangle; + try { draw(skeleton); rasterizer.end(); } + finally { renderer.drawTriangle = triangle; renderer.gpuDrawing = false; } + }; + renderer.dispose = rasterizer.dispose; + renderer.rasterization = 'webgl'; + return renderer; +} + +module.exports = { createRasterizer, attach, prepareTexture }; diff --git a/html/assets/ui-animlist.png b/html/assets/ui-animlist.png new file mode 100644 index 0000000..8e8aa86 Binary files /dev/null and b/html/assets/ui-animlist.png differ diff --git a/html/assets/ui-timeline.png b/html/assets/ui-timeline.png new file mode 100644 index 0000000..0aff328 Binary files /dev/null and b/html/assets/ui-timeline.png differ diff --git a/html/assets/ui-toolbar.png b/html/assets/ui-toolbar.png new file mode 100644 index 0000000..15c9570 Binary files /dev/null and b/html/assets/ui-toolbar.png differ diff --git a/html/assets/ui-window.png b/html/assets/ui-window.png new file mode 100644 index 0000000..a2f1e11 Binary files /dev/null and b/html/assets/ui-window.png differ diff --git a/html/index.html b/html/index.html new file mode 100644 index 0000000..5e8b857 --- /dev/null +++ b/html/index.html @@ -0,0 +1,434 @@ + + + + + +
+ SpineViewer 接管 skel / skel.bytes / json / atlas / atlas.txt,
+ 自动在素材库中配对骨骼、图集与贴图,识别版本并加载本地运行时。
+ 点开任意一个文件,就能播放动画、切换皮肤、导出缩略图。
+
+ 3.4.02)
+ 下面每一项都对应插件的真实行为:跨 item 文件夹配对、旧版二进制解析、WebGL 渲染、缩略图补刷……不只是“能看动画”。
+官方运行时全部拷贝在插件本地 lib/ 目录,无 CDN、无网络请求。断网、内网环境同样可用。
Eagle 会把每个文件拆成独立 item 文件夹。插件扫描同级目录,忽略 -pro / -ess / -pma 等后缀,自动关联骨骼、atlas 与贴图;点开贴图或 atlas 也能找到整套资源。
从骨骼数据读取版本号并匹配对应运行时:3.4 / 3.5 / 3.6 / 3.7 / 3.8 / 4.0 / 4.1 / 4.2 / 4.3,带 -beta 的完整版本号也会显示在水印中。
3.4–3.7 的 .skel / .skel.bytes 由移植自官方 SkeletonBinary 的解析层处理,保留网格、权重、变形、约束、裁剪与绘制顺序,无需编译或联网。
左上角浮动动画列表按需自适应,条目多时独立滚动,没有动画数据时自动隐藏;切换皮肤后按 setup pose 重建槽位附件,画面立即刷新。
+拖动定位、显示动画时长与帧号、秒刻度,蓝色短线标出骨骼 / 插槽 / 约束的关键帧位置,悬停即可查看时间与帧号。
+空格播放 / 暂停、循环开关、0–3x 速度调节;零时长动画自动按单帧静态姿势预览,不会出现空白画面。
+WebGL 绘制网格三角形避免细线接缝,贴图直传 GPU,避免低透明度颜色被量化、光晕分层;预乘默认读取 atlas 的 pma 字段,手动切换后按素材记住。
导入即渲染首帧缩略图,带官方 badge 与版本号水印;资源暂时缺失时显示回退图,并在后台观察约两分钟,文件补齐稳定后自动生成、通知 Eagle 刷新。
+skel.bytes / atlas.txtmetadata.json截图取自下方的交互演示,与插件查看器的控件布局、文案完全一致。浮动控制栏、动画列表与时间轴全部悬浮在画布上方,不挤占预览区域。
+
+
+
+
+ 复刻查看器的控件布局与文案,直接在页面上操作:切换动画、拖动时间轴、缩放画布试试。画面使用官方示例 Raptor(Spine 4.3.75-beta 导出,5 个动画),由 4.3 官方运行时 + 插件同款渲染模块在本地实时渲染,断网或直接双击打开同样可用。
+空格 播放/暂停 · 左键拖拽平移 · 滚轮缩放 · 右键回到中心 · 拖动时间轴定位帧
+覆盖 Spine 3.4 – 4.3:新版二进制直接支持,旧版二进制由同版本运行时与解析层处理。
+| 版本 | 支持格式 | 说明 |
|---|---|---|
3.4 / 3.5 |
+ JSON · SKEL · SKEL.BYTES · ATLAS · ATLAS.TXT | +使用官方 3.4.02 / 3.5.51 运行时与匹配的二进制分支,已验证多组官方示例的姿势对比。 | +
3.6 / 3.7 |
+ 同上 + 二进制解析兼容层 | +移植自官方同版本 spine-libgdx/SkeletonBinary.java;支持 3.7 的 IK 压缩/拉伸与音频事件字段,无需同时导入 JSON。 |
+
3.8 – 4.3 |
+ JSON · SKEL / SKEL.BYTES · ATLAS | +二进制文件完整支持;4.3 等带 -beta 后缀的完整版本号也会显示在缩略图水印中。 |
+
损坏、截断或版本不匹配的文件会直接报告解析错误,不会以错误姿势渲染。
+把 .skel / .json + .atlas + .png 放在同一个文件夹导入 Eagle。
点击任一文件,插件从同级 item 文件夹定位骨骼、atlas 与贴图,配套文件不在同一目录也能找到。
+读取骨骼数据中的版本号(JSON 读 skeleton.spine,SKEL 读二进制头部),加载匹配的本地运行时。
实时播放动画;缩略图自动生成,资源稍后补齐时会在后台自动重渲并通知 Eagle 刷新列表。
+在 Eagle 插件社区获取插件包,然后在 Eagle 插件面板(快捷键 P)安装即可。
+原生二进制需要与目标平台匹配;如需自行构建,可在源码目录执行 npm ci --omit=dev 后从 Eagle 插件面板打包。
双击 .eagleplugin 安装;或在 Eagle 插件面板中右键选择“打包/加载插件”。
克隆源码后在插件目录执行 npm ci --omit=dev,安装可选的 @napi-rs/canvas,再从插件面板加载。
把同一套 Spine 导出文件放进同一个文件夹导入 Eagle,点击任意一个文件即可预览。
+不会。插件只接管 skel、skel.bytes、json、atlas、atlas.txt(以及为复合后缀注册的 bytes / txt)。普通图片仍由 Eagle 原生预览,普通 TXT 与 JSON 也会保持原样。
保存在 Eagle 用户数据目录的 SpineViewer/preview-settings/ 下,按素材库与素材 ID 区分。不会修改 Eagle 的 metadata.json、标签或注释,也不会随素材导出到其他电脑。默认值读取配套 atlas 里的 pma 字段。
只有当 skeleton.spine 是有效版本号时,插件才按 Spine 骨骼数据处理。普通 JSON、JSONC、空版本字段会立即放行,不写缩略图、不扫描资源库,查看器此时显示格式化的 JSON 文本。
解析层移植自官方同版本 spine-libgdx/SkeletonBinary.java,并已验证官方 3.6.32 / 3.7.90 的七套示例。损坏、截断或版本不匹配会报告解析错误;官方 Raptor 数据中一条 IK 阶梯曲线的已知差异也已在测试中单独记录。
使用 @napi-rs/canvas 在 Node 中跨 item 文件夹读取资源并渲染骨骼首帧,尺寸 400×400,右下角为 Spine 官方 badge,左下角为完整导出版本号。也可以在查看器里用当前帧一键生成;手动缩略图跟随当前背景开关。
缺少骨骼、atlas 或贴图时,前台最多短暂等待约半秒便显示回退图;后台会以逐步降频的方式观察约两分钟,链路补齐且文件稳定后自动生成缩略图并通知 Eagle 刷新。稍后补拖的文件会重新触发识别。
+没有找到答案?欢迎把问题或建议发到 1181558906@qq.com。
+