mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-03-26 22:49:01 +08:00
160 lines
5.5 KiB
TypeScript
160 lines
5.5 KiB
TypeScript
/** ****************************************************************************
|
|
* Spine Runtimes License Agreement
|
|
* Last updated July 28, 2023. Replaces all prior versions.
|
|
*
|
|
* Copyright (c) 2013-2023, 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 {
|
|
checkExtension,
|
|
copySearchParams,
|
|
DOMAdapter,
|
|
extensions,
|
|
ExtensionType,
|
|
LoaderParserPriority,
|
|
path,
|
|
Resolver,
|
|
TextureSource
|
|
} from 'pixi.js';
|
|
import { SpineTexture } from '../SpineTexture.js';
|
|
import { TextureAtlas } from '@esotericsoftware/spine-core';
|
|
|
|
import type { AssetExtension, Loader, ResolvedAsset, Texture, UnresolvedAsset } from 'pixi.js';
|
|
|
|
type RawAtlas = string;
|
|
|
|
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(Resolver.RETINA_PREFIX?.exec(value)?.[1] ?? '1'),
|
|
format: split[split.length - 2],
|
|
src: value,
|
|
};
|
|
},
|
|
},
|
|
|
|
loader: {
|
|
extension: {
|
|
type: ExtensionType.LoadParser,
|
|
priority: LoaderParserPriority.Normal,
|
|
name: 'spineTextureAtlasLoader',
|
|
},
|
|
|
|
test (url: string): boolean {
|
|
return checkExtension(url, '.atlas');
|
|
},
|
|
|
|
async load (url: string): Promise<RawAtlas> {
|
|
const response = await DOMAdapter.get().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';
|
|
|
|
return Promise.resolve(isExtensionRight && isString);
|
|
},
|
|
|
|
unload (atlas: TextureAtlas) {
|
|
atlas.dispose();
|
|
},
|
|
|
|
async parse (asset: RawAtlas, options: ResolvedAsset, loader: Loader): Promise<TextureAtlas> {
|
|
const metadata: ISpineAtlasMetadata = options.data || {};
|
|
let basePath = path.dirname(options.src as string);
|
|
|
|
if (basePath && basePath.lastIndexOf('/') !== basePath.length - 1) {
|
|
basePath += '/';
|
|
}
|
|
|
|
// Retval is going to be a texture atlas. However we need to wait for it's 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 TextureSource || typeof metadata.images === 'string') {
|
|
const pixiTexture = metadata.images;
|
|
|
|
metadata.images = {} as Record<string, TextureSource | 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: Promise<any>[] = [];
|
|
|
|
// fill the pages
|
|
for (const page of retval.pages) {
|
|
const pageName = page.name;
|
|
const providedPage = metadata?.images ? metadata.images[pageName] : undefined;
|
|
|
|
if (providedPage instanceof TextureSource) {
|
|
page.setTexture(SpineTexture.from(providedPage));
|
|
}
|
|
else {
|
|
// eslint-disable-next-line max-len
|
|
const url: string = providedPage ?? path.normalize([...basePath.split(path.sep), pageName].join(path.sep));
|
|
|
|
const assetsToLoadIn = {
|
|
src: copySearchParams(url, options.src as string),
|
|
data: {
|
|
...metadata.imageMetadata,
|
|
alphaMode: page.pma ? 'premultiplied-alpha' : 'premultiply-alpha-on-upload'
|
|
}
|
|
};
|
|
|
|
const pixiPromise = loader.load<Texture>(assetsToLoadIn).then((texture) => {
|
|
page.setTexture(SpineTexture.from(texture.source));
|
|
});
|
|
|
|
textureLoadingPromises.push(pixiPromise);
|
|
}
|
|
}
|
|
|
|
await Promise.all(textureLoadingPromises);
|
|
|
|
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
|
|
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?: TextureSource | string | Record<string, TextureSource | string>;
|
|
}
|