2025-10-27 15:54:58 +01:00

156 lines
6.3 KiB
TypeScript

/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2025, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software
* or otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
import { TextureAtlas } from "@esotericsoftware/spine-core";
import type { AssetExtension, Loader, ResolvedAsset, UnresolvedAsset } from "@pixi/assets";
import { Assets, checkExtension, copySearchParams, LoaderParserPriority } from "@pixi/assets";
import type { Texture } from "@pixi/core";
import { ALPHA_MODES, BaseTexture, ExtensionType, extensions, settings, utils } from "@pixi/core";
import { SpineTexture } from "../SpineTexture.js";
type RawAtlas = string;
const loaderName = "spineTextureAtlasLoader";
const spineTextureAtlasLoader: AssetExtension<RawAtlas | TextureAtlas, ISpineAtlasMetadata> = {
extension: ExtensionType.Asset,
resolver: {
test: (value: string): boolean => checkExtension(value, ".atlas"),
parse: (value: string): UnresolvedAsset => {
const split = value.split('.');
return {
resolution: parseFloat(settings.RETINA_PREFIX?.exec(value)?.[1] ?? '1'),
format: split[split.length - 2],
src: value,
};
},
},
loader: {
name: loaderName,
extension: {
type: ExtensionType.LoadParser,
priority: LoaderParserPriority.Normal,
name: loaderName,
},
test (url: string): boolean {
return checkExtension(url, ".atlas");
},
async load (url: string): Promise<RawAtlas> {
const response = await settings.ADAPTER.fetch(url);
const txt = await response.text();
return txt;
},
testParse (asset: unknown, options: ResolvedAsset): Promise<boolean> {
const isExtensionRight = checkExtension(options.src as string, ".atlas");
const isString = typeof asset === "string";
const isExplicitLoadParserSet = options.loadParser === loaderName;
return Promise.resolve((isExtensionRight || isExplicitLoadParserSet) && isString);
},
unload (atlas: TextureAtlas) {
atlas.dispose();
},
async parse (asset: RawAtlas, options: { src: string, data: ISpineAtlasMetadata }, loader: Loader): Promise<TextureAtlas> {
const metadata: ISpineAtlasMetadata = options.data || {};
let basePath = utils.path.dirname(options.src);
if (basePath && basePath.lastIndexOf("/") !== basePath.length - 1) {
basePath += "/";
}
// Retval is going to be a texture atlas. However, we need to wait for its callback to resolve this promise.
const retval = new TextureAtlas(asset);
// If the user gave me only one texture, that one is assumed to be the "first" texture in the atlas
if (metadata.images instanceof BaseTexture || typeof metadata.images === "string") {
const pixiTexture = metadata.images;
metadata.images = {} as Record<string, BaseTexture | string>;
metadata.images[retval.pages[0].name] = pixiTexture;
}
// we will wait for all promises for the textures at the same time at the end.
const textureLoadingPromises = [];
// setting preferCreateImageBitmap to false for loadTextures loader to allow loading PMA images
let oldPreferCreateImageBitmap = true;
for (const parser of loader.parsers) {
if (parser.name === "loadTextures") {
oldPreferCreateImageBitmap = parser.config?.preferCreateImageBitmap;
break;
}
}
Assets.setPreferences({ preferCreateImageBitmap: false });
// fill the pages
for (const page of retval.pages) {
const pageName = page.name;
const providedPage = metadata?.images ? metadata.images[pageName] : undefined;
if (providedPage instanceof BaseTexture) {
page.setTexture(SpineTexture.from(providedPage));
} else {
const url: string = providedPage ?? utils.path.normalize([...basePath.split(utils.path.sep), pageName].join(utils.path.sep));
const assetsToLoadIn = { src: copySearchParams(url, options.src as string), data: { ...metadata.imageMetadata, ...{ alphaMode: page.pma ? ALPHA_MODES.PMA : ALPHA_MODES.UNPACK } } };
const pixiPromise = loader.load<Texture>(assetsToLoadIn)
.then((texture) => {
page.setTexture(SpineTexture.from(texture.baseTexture));
});
textureLoadingPromises.push(pixiPromise);
}
}
await Promise.all(textureLoadingPromises);
// restoring preferCreateImageBitmap old value for loadTextures loader
Assets.setPreferences({ preferCreateImageBitmap: oldPreferCreateImageBitmap });
return retval;
},
},
} as AssetExtension<RawAtlas | TextureAtlas, ISpineAtlasMetadata>;
extensions.add(spineTextureAtlasLoader);
export interface ISpineAtlasMetadata {
// If you are downloading an .atlas file, this metadata will go to the Texture loader
// biome-ignore lint/suspicious/noExplicitAny: user can pass any
imageMetadata?: any;
// If you already have atlas pages loaded as pixi textures and want to use that to create the atlas, you can pass them here
images?: BaseTexture | string | Record<string, BaseTexture | string>;
}