mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-04 14:24:53 +08:00
[ts] Add support across all runtimes for premultiplying non-PMA textures on upload.
This commit is contained in:
parent
86981a0935
commit
632fb227b6
15
CHANGELOG.md
15
CHANGELOG.md
@ -758,6 +758,8 @@
|
|||||||
- Added `Skeleton` properties `windX`, `windY`, `gravityX`, `gravityY` to allow rotating physics force directions
|
- Added `Skeleton` properties `windX`, `windY`, `gravityX`, `gravityY` to allow rotating physics force directions
|
||||||
- Added `SequenceTimeline` for sequence animation
|
- Added `SequenceTimeline` for sequence animation
|
||||||
- Added `allowMissingRegions` parameter to `AtlasAttachmentLoader` constructor to support skeletons exported with per-skin atlases
|
- Added `allowMissingRegions` parameter to `AtlasAttachmentLoader` constructor to support skeletons exported with per-skin atlases
|
||||||
|
- Added `TextureLoader` type with optional `pma?: boolean` parameter to `AssetManagerBase`. `AssetManagerBase` now tracks and passes PMA metadata from atlas pages to texture loaders, allowing runtimes to automatically premultiply textures on upload
|
||||||
|
- Added `SkeletonRendererCore` class to reduce complexity of runtime-specific render code
|
||||||
|
|
||||||
- **Breaking changes**
|
- **Breaking changes**
|
||||||
- `Bone` now extends `PosedActive` with separate pose, constrained, and applied states
|
- `Bone` now extends `PosedActive` with separate pose, constrained, and applied states
|
||||||
@ -869,6 +871,12 @@
|
|||||||
|
|
||||||
- **Breaking changes**
|
- **Breaking changes**
|
||||||
- Updated to use new TypeScript/JavaScript runtime
|
- Updated to use new TypeScript/JavaScript runtime
|
||||||
|
- `GLTexture` constructor now requires `pma: boolean` parameter (automatically read from atlas page metadata)
|
||||||
|
- Removed `GLTexture.DISABLE_UNPACK_PREMULTIPLIED_ALPHA_WEBGL` static property
|
||||||
|
- `SkeletonRenderer` and `SkeletonDebugRenderer` no longer have `premultipliedAlpha` property - PMA is handled automatically
|
||||||
|
- `SceneRenderer.drawSkeleton()` and `drawSkeletonDebug()` no longer take `premultipliedAlpha` parameter
|
||||||
|
- `PolygonBatcher.setBlendMode()` no longer takes `premultipliedAlpha` parameter
|
||||||
|
- `LoadingScreen` no longer accepts PMA parameters
|
||||||
|
|
||||||
### Canvas backend
|
### Canvas backend
|
||||||
|
|
||||||
@ -885,11 +893,13 @@
|
|||||||
|
|
||||||
- **Breaking changes**
|
- **Breaking changes**
|
||||||
- Updated to use new TypeScript/JavaScript runtime
|
- Updated to use new TypeScript/JavaScript runtime
|
||||||
|
- `AssetManager` constructor no longer takes `pma` parameter - PMA is handled automatically
|
||||||
|
|
||||||
### Player
|
### Player
|
||||||
|
|
||||||
- **Breaking changes**
|
- **Breaking changes**
|
||||||
- Updated to use new TypeScript/JavaScript runtime
|
- Updated to use new TypeScript/JavaScript runtime
|
||||||
|
- Removed `premultipliedAlpha` option from `SpinePlayerConfig` - PMA is now handled automatically
|
||||||
|
|
||||||
### Pixi v7
|
### Pixi v7
|
||||||
|
|
||||||
@ -917,17 +927,22 @@
|
|||||||
|
|
||||||
- **Breaking changes**
|
- **Breaking changes**
|
||||||
- Updated to use new TypeScript/JavaScript runtime
|
- Updated to use new TypeScript/JavaScript runtime
|
||||||
|
- `SpinePlugin.spineAtlas()` loader no longer takes `premultipliedAlpha` parameter - PMA is handled automatically
|
||||||
|
- `SpinePlugin.createSkeleton()` no longer takes `premultipliedAlpha` parameter
|
||||||
|
|
||||||
### Phaser v4
|
### Phaser v4
|
||||||
|
|
||||||
- **Breaking changes**
|
- **Breaking changes**
|
||||||
- Updated to use new TypeScript/JavaScript runtime
|
- Updated to use new TypeScript/JavaScript runtime
|
||||||
|
- `SpinePlugin.spineAtlas()` loader no longer takes `premultipliedAlpha` parameter - PMA is handled automatically
|
||||||
|
- `SpinePlugin.createSkeleton()` no longer takes `premultipliedAlpha` parameter
|
||||||
|
|
||||||
### Web Components
|
### Web Components
|
||||||
|
|
||||||
- **Breaking changes**
|
- **Breaking changes**
|
||||||
- Updated to use new TypeScript/JavaScript runtime
|
- Updated to use new TypeScript/JavaScript runtime
|
||||||
- Updated skeleton and overlay component implementations
|
- Updated skeleton and overlay component implementations
|
||||||
|
- Removed `pma` property from `SpineWebComponentSkeleton` - PMA is handled automatically
|
||||||
|
|
||||||
# 4.2
|
# 4.2
|
||||||
|
|
||||||
|
|||||||
@ -35,20 +35,19 @@ type AssetData = (Uint8Array | string | Texture | TextureAtlas | object) & Parti
|
|||||||
type AssetCallback<T extends AssetData> = (path: string, data: T) => void;
|
type AssetCallback<T extends AssetData> = (path: string, data: T) => void;
|
||||||
type ErrorCallback = (path: string, message: string) => void;
|
type ErrorCallback = (path: string, message: string) => void;
|
||||||
|
|
||||||
|
export type TextureLoader = (image: HTMLImageElement | ImageBitmap, pma?: boolean) => Texture;
|
||||||
|
|
||||||
export class AssetManagerBase implements Disposable {
|
export class AssetManagerBase implements Disposable {
|
||||||
private pathPrefix: string = "";
|
|
||||||
private textureLoader: (image: HTMLImageElement | ImageBitmap) => Texture;
|
|
||||||
private downloader: Downloader;
|
|
||||||
private cache: AssetCache;
|
|
||||||
private errors: StringMap<string> = {};
|
private errors: StringMap<string> = {};
|
||||||
private toLoad = 0;
|
private toLoad = 0;
|
||||||
private loaded = 0;
|
private loaded = 0;
|
||||||
|
private texturePmaInfo: Record<string, boolean> = {};
|
||||||
|
|
||||||
constructor (textureLoader: (image: HTMLImageElement | ImageBitmap) => Texture, pathPrefix: string = "", downloader = new Downloader(), cache = new AssetCache()) {
|
constructor (
|
||||||
this.textureLoader = textureLoader;
|
private textureLoader: TextureLoader,
|
||||||
this.pathPrefix = pathPrefix;
|
private pathPrefix: string = "",
|
||||||
this.downloader = downloader;
|
private downloader = new Downloader(),
|
||||||
this.cache = cache;
|
private cache = new AssetCache()) {
|
||||||
}
|
}
|
||||||
|
|
||||||
private start (path: string): string {
|
private start (path: string): string {
|
||||||
@ -175,6 +174,7 @@ export class AssetManagerBase implements Disposable {
|
|||||||
|
|
||||||
if (this.reuseAssets(path, success, error)) return;
|
if (this.reuseAssets(path, success, error)) return;
|
||||||
|
|
||||||
|
const pma = this.texturePmaInfo[path];
|
||||||
this.cache.assetsLoaded[path] = new Promise<Texture>((resolve, reject) => {
|
this.cache.assetsLoaded[path] = new Promise<Texture>((resolve, reject) => {
|
||||||
const isBrowser = !!(typeof window !== 'undefined' && typeof navigator !== 'undefined' && window.document);
|
const isBrowser = !!(typeof window !== 'undefined' && typeof navigator !== 'undefined' && window.document);
|
||||||
const isWebWorker = !isBrowser; // && typeof importScripts !== 'undefined';
|
const isWebWorker = !isBrowser; // && typeof importScripts !== 'undefined';
|
||||||
@ -188,7 +188,7 @@ export class AssetManagerBase implements Disposable {
|
|||||||
return blob ? createImageBitmap(blob, { premultiplyAlpha: "none", colorSpaceConversion: "none" }) : null;
|
return blob ? createImageBitmap(blob, { premultiplyAlpha: "none", colorSpaceConversion: "none" }) : null;
|
||||||
}).then((bitmap) => {
|
}).then((bitmap) => {
|
||||||
if (bitmap) {
|
if (bitmap) {
|
||||||
const texture = this.createTexture(path, bitmap);
|
const texture = this.createTexture(path, pma, bitmap);
|
||||||
this.success(success, path, texture);
|
this.success(success, path, texture);
|
||||||
resolve(texture);
|
resolve(texture);
|
||||||
};
|
};
|
||||||
@ -197,7 +197,7 @@ export class AssetManagerBase implements Disposable {
|
|||||||
const image = new Image();
|
const image = new Image();
|
||||||
image.crossOrigin = "anonymous";
|
image.crossOrigin = "anonymous";
|
||||||
image.onload = () => {
|
image.onload = () => {
|
||||||
const texture = this.createTexture(path, image);
|
const texture = this.createTexture(path, pma, image);
|
||||||
this.success(success, path, texture);
|
this.success(success, path, texture);
|
||||||
resolve(texture);
|
resolve(texture);
|
||||||
};
|
};
|
||||||
@ -216,7 +216,7 @@ export class AssetManagerBase implements Disposable {
|
|||||||
path: string,
|
path: string,
|
||||||
success: AssetCallback<TextureAtlas> = () => { },
|
success: AssetCallback<TextureAtlas> = () => { },
|
||||||
error: ErrorCallback = () => { },
|
error: ErrorCallback = () => { },
|
||||||
fileAlias?: { [keyword: string]: string }
|
fileAlias?: Record<string, string>
|
||||||
) {
|
) {
|
||||||
const index = path.lastIndexOf("/");
|
const index = path.lastIndexOf("/");
|
||||||
const parent = index >= 0 ? path.substring(0, index + 1) : "";
|
const parent = index >= 0 ? path.substring(0, index + 1) : "";
|
||||||
@ -227,10 +227,11 @@ export class AssetManagerBase implements Disposable {
|
|||||||
this.cache.assetsLoaded[path] = new Promise<TextureAtlas>((resolve, reject) => {
|
this.cache.assetsLoaded[path] = new Promise<TextureAtlas>((resolve, reject) => {
|
||||||
this.downloader.downloadText(path, (atlasText: string): void => {
|
this.downloader.downloadText(path, (atlasText: string): void => {
|
||||||
try {
|
try {
|
||||||
const atlas = this.createTextureAtlas(path, atlasText);
|
const atlas = this.createTextureAtlas(atlasText, parent, path, fileAlias);
|
||||||
let toLoad = atlas.pages.length, abort = false;
|
let toLoad = atlas.pages.length, abort = false;
|
||||||
for (const page of atlas.pages) {
|
for (const page of atlas.pages) {
|
||||||
this.loadTexture(!fileAlias ? parent + page.name : fileAlias[page.name],
|
this.loadTexture(
|
||||||
|
this.texturePath(parent, page.name, fileAlias),
|
||||||
(imagePath: string, texture: Texture) => {
|
(imagePath: string, texture: Texture) => {
|
||||||
if (!abort) {
|
if (!abort) {
|
||||||
page.setTexture(texture);
|
page.setTexture(texture);
|
||||||
@ -268,6 +269,8 @@ export class AssetManagerBase implements Disposable {
|
|||||||
success: AssetCallback<TextureAtlas> = () => { },
|
success: AssetCallback<TextureAtlas> = () => { },
|
||||||
error: ErrorCallback = () => { },
|
error: ErrorCallback = () => { },
|
||||||
) {
|
) {
|
||||||
|
const index = path.lastIndexOf("/");
|
||||||
|
const parent = index >= 0 ? path.substring(0, index + 1) : "";
|
||||||
path = this.start(path);
|
path = this.start(path);
|
||||||
|
|
||||||
if (this.reuseAssets(path, success, error)) return;
|
if (this.reuseAssets(path, success, error)) return;
|
||||||
@ -275,7 +278,7 @@ export class AssetManagerBase implements Disposable {
|
|||||||
this.cache.assetsLoaded[path] = new Promise<TextureAtlas>((resolve, reject) => {
|
this.cache.assetsLoaded[path] = new Promise<TextureAtlas>((resolve, reject) => {
|
||||||
this.downloader.downloadText(path, (atlasText: string): void => {
|
this.downloader.downloadText(path, (atlasText: string): void => {
|
||||||
try {
|
try {
|
||||||
const atlas = this.createTextureAtlas(path, atlasText);
|
const atlas = this.createTextureAtlas(atlasText, parent, path);
|
||||||
this.success(success, path, atlas);
|
this.success(success, path, atlas);
|
||||||
resolve(atlas);
|
resolve(atlas);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -291,7 +294,6 @@ export class AssetManagerBase implements Disposable {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Promisified versions of load function
|
|
||||||
async loadBinaryAsync (path: string) {
|
async loadBinaryAsync (path: string) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.loadBinary(path,
|
this.loadBinary(path,
|
||||||
@ -413,7 +415,7 @@ export class AssetManagerBase implements Disposable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private createTextureAtlas (path: string, atlasText: string): TextureAtlas {
|
private createTextureAtlas (atlasText: string, parentPath: string, path: string, fileAlias?: Record<string, string>): TextureAtlas {
|
||||||
const atlas = new TextureAtlas(atlasText);
|
const atlas = new TextureAtlas(atlasText);
|
||||||
atlas.dispose = () => {
|
atlas.dispose = () => {
|
||||||
if (this.cache.assetsRefCount[path] <= 0) return;
|
if (this.cache.assetsRefCount[path] <= 0) return;
|
||||||
@ -422,17 +424,26 @@ export class AssetManagerBase implements Disposable {
|
|||||||
page.texture?.dispose();
|
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;
|
return atlas;
|
||||||
}
|
}
|
||||||
|
|
||||||
private createTexture (path: string, image: HTMLImageElement | ImageBitmap): Texture {
|
private createTexture (path: string, pma: boolean, image: HTMLImageElement | ImageBitmap): Texture {
|
||||||
const texture = this.textureLoader(image);
|
const texture = this.textureLoader(image, pma);
|
||||||
const textureDispose = texture.dispose.bind(texture);
|
const textureDispose = texture.dispose.bind(texture);
|
||||||
texture.dispose = () => {
|
texture.dispose = () => {
|
||||||
if (this.disposeAssetInternal(path)) textureDispose();
|
if (this.disposeAssetInternal(path)) textureDispose();
|
||||||
}
|
}
|
||||||
return texture;
|
return texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private texturePath (parentPath: string, pageName: string, fileAlias?: Record<string, string>) {
|
||||||
|
if (!fileAlias) return parentPath + pageName;
|
||||||
|
return fileAlias[pageName];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AssetCache {
|
export class AssetCache {
|
||||||
|
|||||||
@ -324,7 +324,7 @@ export class SkeletonClipping {
|
|||||||
return clipOutputItems != null;
|
return clipOutputItems != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public clipTrianglesUnpacked (vertices: NumberArrayLike, triangles: NumberArrayLike | Uint32Array, trianglesLength: number, uvs: NumberArrayLike) {
|
public clipTrianglesUnpacked (vertices: NumberArrayLike, triangles: NumberArrayLike | Uint32Array, trianglesLength: number, uvs: NumberArrayLike, stride = 2) {
|
||||||
const clipOutput = this.clipOutput;
|
const clipOutput = this.clipOutput;
|
||||||
let clippedVertices = this._clippedVerticesTyped, clippedUVs = this._clippedUVsTyped, clippedTriangles = this._clippedTrianglesTyped;
|
let clippedVertices = this._clippedVerticesTyped, clippedUVs = this._clippedUVsTyped, clippedTriangles = this._clippedTrianglesTyped;
|
||||||
// biome-ignore lint/style/noNonNullAssertion: clipStart define it
|
// biome-ignore lint/style/noNonNullAssertion: clipStart define it
|
||||||
@ -343,17 +343,23 @@ export class SkeletonClipping {
|
|||||||
let clipped = false;
|
let clipped = false;
|
||||||
|
|
||||||
for (let i = 0; i < trianglesLength; i += 3) {
|
for (let i = 0; i < trianglesLength; i += 3) {
|
||||||
let v = triangles[i] << 1;
|
let t = triangles[i];
|
||||||
|
let v = t * stride;
|
||||||
const x1 = vertices[v], y1 = vertices[v + 1];
|
const x1 = vertices[v], y1 = vertices[v + 1];
|
||||||
const u1 = uvs[v], v1 = uvs[v + 1];
|
let uv = t << 1;
|
||||||
|
const u1 = uvs[uv], v1 = uvs[uv + 1];
|
||||||
|
|
||||||
v = triangles[i + 1] << 1;
|
t = triangles[i + 1];
|
||||||
|
v = t * stride;
|
||||||
const x2 = vertices[v], y2 = vertices[v + 1];
|
const x2 = vertices[v], y2 = vertices[v + 1];
|
||||||
const u2 = uvs[v], v2 = uvs[v + 1];
|
uv = t << 1;
|
||||||
|
const u2 = uvs[uv], v2 = uvs[uv + 1];
|
||||||
|
|
||||||
v = triangles[i + 2] << 1;
|
t = triangles[i + 2];
|
||||||
|
v = t * stride;
|
||||||
const x3 = vertices[v], y3 = vertices[v + 1];
|
const x3 = vertices[v], y3 = vertices[v + 1];
|
||||||
const u3 = uvs[v], v3 = uvs[v + 1];
|
uv = t << 1;
|
||||||
|
const u3 = uvs[uv], v3 = uvs[uv + 1];
|
||||||
|
|
||||||
for (let p = 0; p < polygonsCount; p++) {
|
for (let p = 0; p < polygonsCount; p++) {
|
||||||
let s = this.clippedVerticesLength;
|
let s = this.clippedVerticesLength;
|
||||||
@ -367,20 +373,22 @@ export class SkeletonClipping {
|
|||||||
let clipOutputCount = clipOutputLength >> 1;
|
let clipOutputCount = clipOutputLength >> 1;
|
||||||
const clipOutputItems = this.clipOutput;
|
const clipOutputItems = this.clipOutput;
|
||||||
|
|
||||||
const newLength = s + clipOutputCount * 2;
|
const newLength = s + clipOutputCount * stride;
|
||||||
if (clippedVertices.length < newLength) {
|
if (clippedVertices.length < newLength) {
|
||||||
this._clippedVerticesTyped = new Float32Array(newLength * 2);
|
this._clippedVerticesTyped = new Float32Array(newLength * 2);
|
||||||
this._clippedVerticesTyped.set(clippedVertices.subarray(0, s));
|
this._clippedVerticesTyped.set(clippedVertices.subarray(0, s));
|
||||||
this._clippedUVsTyped = new Float32Array(newLength * 2);
|
this._clippedUVsTyped = new Float32Array((this.clippedUVsLength + clipOutputCount * 2) * 2);
|
||||||
this._clippedUVsTyped.set(clippedUVs.subarray(0, s));
|
this._clippedUVsTyped.set(clippedUVs.subarray(0, this.clippedUVsLength));
|
||||||
clippedVertices = this._clippedVerticesTyped;
|
clippedVertices = this._clippedVerticesTyped;
|
||||||
clippedUVs = this._clippedUVsTyped;
|
clippedUVs = this._clippedUVsTyped;
|
||||||
}
|
}
|
||||||
const clippedVerticesItems = clippedVertices;
|
const clippedVerticesItems = clippedVertices;
|
||||||
const clippedUVsItems = clippedUVs;
|
const clippedUVsItems = clippedUVs;
|
||||||
this.clippedVerticesLength = newLength;
|
this.clippedVerticesLength = newLength;
|
||||||
this.clippedUVsLength = newLength;
|
|
||||||
for (let ii = 0; ii < clipOutputLength; ii += 2, s += 2) {
|
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];
|
const x = clipOutputItems[ii], y = clipOutputItems[ii + 1];
|
||||||
clippedVerticesItems[s] = x;
|
clippedVerticesItems[s] = x;
|
||||||
clippedVerticesItems[s + 1] = y;
|
clippedVerticesItems[s + 1] = y;
|
||||||
@ -388,8 +396,8 @@ export class SkeletonClipping {
|
|||||||
const a = (d0 * c0 + d1 * c1) * d;
|
const a = (d0 * c0 + d1 * c1) * d;
|
||||||
const b = (d4 * c0 + d2 * c1) * d;
|
const b = (d4 * c0 + d2 * c1) * d;
|
||||||
const c = 1 - a - b;
|
const c = 1 - a - b;
|
||||||
clippedUVsItems[s] = u1 * a + u2 * b + u3 * c;
|
clippedUVsItems[uvIndex] = u1 * a + u2 * b + u3 * c;
|
||||||
clippedUVsItems[s + 1] = v1 * a + v2 * b + v3 * c;
|
clippedUVsItems[uvIndex + 1] = v1 * a + v2 * b + v3 * c;
|
||||||
}
|
}
|
||||||
|
|
||||||
s = this.clippedTrianglesLength;
|
s = this.clippedTrianglesLength;
|
||||||
@ -411,7 +419,7 @@ export class SkeletonClipping {
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
let newLength = s + 3 * 2;
|
let newLength = s + 3 * stride;
|
||||||
if (clippedVertices.length < newLength) {
|
if (clippedVertices.length < newLength) {
|
||||||
this._clippedVerticesTyped = new Float32Array(newLength * 2);
|
this._clippedVerticesTyped = new Float32Array(newLength * 2);
|
||||||
this._clippedVerticesTyped.set(clippedVertices.subarray(0, s));
|
this._clippedVerticesTyped.set(clippedVertices.subarray(0, s));
|
||||||
@ -419,25 +427,27 @@ export class SkeletonClipping {
|
|||||||
}
|
}
|
||||||
clippedVertices[s] = x1;
|
clippedVertices[s] = x1;
|
||||||
clippedVertices[s + 1] = y1;
|
clippedVertices[s + 1] = y1;
|
||||||
clippedVertices[s + 2] = x2;
|
clippedVertices[s + stride] = x2;
|
||||||
clippedVertices[s + 3] = y2;
|
clippedVertices[s + stride + 1] = y2;
|
||||||
clippedVertices[s + 4] = x3;
|
clippedVertices[s + stride * 2] = x3;
|
||||||
clippedVertices[s + 5] = y3;
|
clippedVertices[s + stride * 2 + 1] = y3;
|
||||||
|
|
||||||
if (clippedUVs.length < newLength) {
|
let uvLength = this.clippedUVsLength + 3 * 2;
|
||||||
this._clippedUVsTyped = new Float32Array(newLength * 2);
|
if (clippedUVs.length < uvLength) {
|
||||||
this._clippedUVsTyped.set(clippedUVs.subarray(0, s));
|
this._clippedUVsTyped = new Float32Array(uvLength * 2);
|
||||||
|
this._clippedUVsTyped.set(clippedUVs.subarray(0, this.clippedUVsLength));
|
||||||
clippedUVs = this._clippedUVsTyped;
|
clippedUVs = this._clippedUVsTyped;
|
||||||
}
|
}
|
||||||
clippedUVs[s] = u1;
|
let uvIndex = this.clippedUVsLength;
|
||||||
clippedUVs[s + 1] = v1;
|
clippedUVs[uvIndex] = u1;
|
||||||
clippedUVs[s + 2] = u2;
|
clippedUVs[uvIndex + 1] = v1;
|
||||||
clippedUVs[s + 3] = v2;
|
clippedUVs[uvIndex + 2] = u2;
|
||||||
clippedUVs[s + 4] = u3;
|
clippedUVs[uvIndex + 3] = v2;
|
||||||
clippedUVs[s + 5] = v3;
|
clippedUVs[uvIndex + 4] = u3;
|
||||||
|
clippedUVs[uvIndex + 5] = v3;
|
||||||
|
|
||||||
this.clippedVerticesLength = newLength;
|
this.clippedVerticesLength = newLength;
|
||||||
this.clippedUVsLength = newLength;
|
this.clippedUVsLength = uvLength;
|
||||||
|
|
||||||
s = this.clippedTrianglesLength;
|
s = this.clippedTrianglesLength;
|
||||||
newLength = s + 3;
|
newLength = s + 3;
|
||||||
|
|||||||
@ -40,7 +40,7 @@ export class SkeletonRendererCore {
|
|||||||
private clipping = new SkeletonClipping();
|
private clipping = new SkeletonClipping();
|
||||||
private renderCommands: RenderCommand[] = [];
|
private renderCommands: RenderCommand[] = [];
|
||||||
|
|
||||||
render (skeleton: Skeleton): RenderCommand | undefined {
|
render (skeleton: Skeleton, pma = false, inColor?: [number, number, number, number], stride = 2): RenderCommand | undefined {
|
||||||
this.commandPool.reset();
|
this.commandPool.reset();
|
||||||
this.renderCommands.length = 0;
|
this.renderCommands.length = 0;
|
||||||
|
|
||||||
@ -56,8 +56,8 @@ export class SkeletonRendererCore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const slotApplied = slot.applied;
|
const slotApplied = slot.applied;
|
||||||
const color = slotApplied.color;
|
const slotColor = slotApplied.color;
|
||||||
const alpha = color.a;
|
const alpha = slotColor.a;
|
||||||
if ((alpha === 0 || !slot.bone.active) && !(attachment instanceof ClippingAttachment)) {
|
if ((alpha === 0 || !slot.bone.active) && !(attachment instanceof ClippingAttachment)) {
|
||||||
clipper.clipEnd(slot);
|
clipper.clipEnd(slot);
|
||||||
continue;
|
continue;
|
||||||
@ -80,7 +80,7 @@ export class SkeletonRendererCore {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
attachment.computeWorldVertices(slot, this.worldVertices, 0, 2);
|
attachment.computeWorldVertices(slot, this.worldVertices, 0, stride);
|
||||||
vertices = this.worldVertices;
|
vertices = this.worldVertices;
|
||||||
verticesCount = 4;
|
verticesCount = 4;
|
||||||
uvs = attachment.uvs as Float32Array;
|
uvs = attachment.uvs as Float32Array;
|
||||||
@ -99,7 +99,7 @@ export class SkeletonRendererCore {
|
|||||||
if (this.worldVertices.length < attachment.worldVerticesLength)
|
if (this.worldVertices.length < attachment.worldVerticesLength)
|
||||||
this.worldVertices = new Float32Array(attachment.worldVerticesLength);
|
this.worldVertices = new Float32Array(attachment.worldVerticesLength);
|
||||||
|
|
||||||
attachment.computeWorldVertices(skeleton, slot, 0, attachment.worldVerticesLength, this.worldVertices, 0, 2);
|
attachment.computeWorldVertices(skeleton, slot, 0, attachment.worldVerticesLength, this.worldVertices, 0, stride);
|
||||||
vertices = this.worldVertices;
|
vertices = this.worldVertices;
|
||||||
verticesCount = attachment.worldVerticesLength >> 1;
|
verticesCount = attachment.worldVerticesLength >> 1;
|
||||||
uvs = attachment.uvs as Float32Array;
|
uvs = attachment.uvs as Float32Array;
|
||||||
@ -115,38 +115,71 @@ export class SkeletonRendererCore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const skelColor = skeleton.color;
|
const skelColor = skeleton.color;
|
||||||
const r = Math.floor(skelColor.r * slotApplied.color.r * attachmentColor.r * 255);
|
let color: number, darkColor: number;
|
||||||
const g = Math.floor(skelColor.g * slotApplied.color.g * attachmentColor.g * 255);
|
if (pma) {
|
||||||
const b = Math.floor(skelColor.b * slotApplied.color.b * attachmentColor.b * 255);
|
let a: number;
|
||||||
const a = Math.floor(skelColor.a * slotApplied.color.a * attachmentColor.a * 255);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
let darkColor = 0xff000000;
|
darkColor = 0xff000000;
|
||||||
if (slotApplied.darkColor) {
|
if (slotApplied.darkColor) {
|
||||||
const { r, g, b } = slotApplied.darkColor;
|
const { r, g, b } = slotApplied.darkColor;
|
||||||
darkColor = 0xff000000 |
|
darkColor = 0xff000000 |
|
||||||
(Math.floor(r * 255) << 16) |
|
(Math.floor(r * a) << 16) |
|
||||||
(Math.floor(g * 255) << 8) |
|
(Math.floor(g * a) << 8) |
|
||||||
Math.floor(b * 255);
|
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 (slotApplied.darkColor) {
|
||||||
|
const { r, g, b } = slotApplied.darkColor;
|
||||||
|
darkColor = (Math.floor(r * 255) << 16) | (Math.floor(g * 255) << 8) | Math.floor(b * 255);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clipper.isClipping()) {
|
if (clipper.isClipping()) {
|
||||||
clipper.clipTrianglesUnpacked(vertices, indices, indicesCount, uvs);
|
clipper.clipTrianglesUnpacked(vertices, indices, indicesCount, uvs, stride);
|
||||||
vertices = clipper.clippedVerticesTyped;
|
vertices = clipper.clippedVerticesTyped;
|
||||||
verticesCount = clipper.clippedVerticesLength >> 1;
|
verticesCount = clipper.clippedVerticesLength / stride;
|
||||||
uvs = clipper.clippedUVsTyped;
|
uvs = clipper.clippedUVsTyped;
|
||||||
indices = clipper.clippedTrianglesTyped;
|
indices = clipper.clippedTrianglesTyped;
|
||||||
indicesCount = clipper.clippedTrianglesLength;
|
indicesCount = clipper.clippedTrianglesLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cmd = this.commandPool.getCommand(verticesCount, indicesCount);
|
const cmd = this.commandPool.getCommand(verticesCount, indicesCount, stride);
|
||||||
cmd.blendMode = slot.data.blendMode;
|
cmd.blendMode = slot.data.blendMode;
|
||||||
cmd.texture = texture;
|
cmd.texture = texture;
|
||||||
|
|
||||||
cmd.positions.set(vertices.subarray(0, verticesCount << 1));
|
cmd.positions.set(vertices.subarray(0, verticesCount * stride));
|
||||||
cmd.uvs.set(uvs.subarray(0, verticesCount << 1));
|
cmd.uvs.set(uvs.subarray(0, verticesCount << 1));
|
||||||
|
|
||||||
for (let j = 0; j < verticesCount; j++) {
|
for (let j = 0; j < verticesCount; j++) {
|
||||||
cmd.colors[j] = (a << 24) | (r << 16) | (g << 8) | b;
|
cmd.colors[j] = color;
|
||||||
cmd.darkColors[j] = darkColor;
|
cmd.darkColors[j] = darkColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,14 +194,14 @@ export class SkeletonRendererCore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
clipper.clipEnd();
|
clipper.clipEnd();
|
||||||
return this.batchCommands();
|
return this.batchCommands(stride);
|
||||||
}
|
}
|
||||||
|
|
||||||
private batchSubCommands (commands: RenderCommand[], first: number, last: number,
|
private batchSubCommands (commands: RenderCommand[], first: number, last: number,
|
||||||
numVertices: number, numIndices: number): RenderCommand {
|
numVertices: number, numIndices: number, stride: number): RenderCommand {
|
||||||
|
|
||||||
const firstCmd = commands[first];
|
const firstCmd = commands[first];
|
||||||
const batched = this.commandPool.getCommand(numVertices, numIndices);
|
const batched = this.commandPool.getCommand(numVertices, numIndices, stride);
|
||||||
|
|
||||||
batched.blendMode = firstCmd.blendMode;
|
batched.blendMode = firstCmd.blendMode;
|
||||||
batched.texture = firstCmd.texture;
|
batched.texture = firstCmd.texture;
|
||||||
@ -183,7 +216,7 @@ export class SkeletonRendererCore {
|
|||||||
const cmd = commands[i];
|
const cmd = commands[i];
|
||||||
|
|
||||||
batched.positions.set(cmd.positions, positionsOffset);
|
batched.positions.set(cmd.positions, positionsOffset);
|
||||||
positionsOffset += cmd.numVertices << 1;
|
positionsOffset += cmd.numVertices * stride;
|
||||||
|
|
||||||
batched.uvs.set(cmd.uvs, uvsOffset);
|
batched.uvs.set(cmd.uvs, uvsOffset);
|
||||||
uvsOffset += cmd.numVertices << 1;
|
uvsOffset += cmd.numVertices << 1;
|
||||||
@ -203,7 +236,7 @@ export class SkeletonRendererCore {
|
|||||||
return batched;
|
return batched;
|
||||||
}
|
}
|
||||||
|
|
||||||
private batchCommands (): RenderCommand | undefined {
|
private batchCommands (stride: number): RenderCommand | undefined {
|
||||||
if (this.renderCommands.length === 0) return undefined;
|
if (this.renderCommands.length === 0) return undefined;
|
||||||
|
|
||||||
let root: RenderCommand | undefined;
|
let root: RenderCommand | undefined;
|
||||||
@ -234,7 +267,7 @@ export class SkeletonRendererCore {
|
|||||||
numIndices += cmd.numIndices;
|
numIndices += cmd.numIndices;
|
||||||
} else {
|
} else {
|
||||||
const batched = this.batchSubCommands(this.renderCommands, startIndex, i - 1,
|
const batched = this.batchSubCommands(this.renderCommands, startIndex, i - 1,
|
||||||
numVertices, numIndices);
|
numVertices, numIndices, stride);
|
||||||
|
|
||||||
if (!last) {
|
if (!last) {
|
||||||
root = last = batched;
|
root = last = batched;
|
||||||
@ -257,6 +290,8 @@ export class SkeletonRendererCore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// values with under score is the original sized array, bigger than necessary
|
||||||
|
// values without under score is a view of the orignal array, sized as needed
|
||||||
interface RenderCommand {
|
interface RenderCommand {
|
||||||
positions: Float32Array;
|
positions: Float32Array;
|
||||||
uvs: Float32Array;
|
uvs: Float32Array;
|
||||||
@ -280,17 +315,17 @@ class CommandPool {
|
|||||||
private pool: RenderCommand[] = [];
|
private pool: RenderCommand[] = [];
|
||||||
private inUse: RenderCommand[] = [];
|
private inUse: RenderCommand[] = [];
|
||||||
|
|
||||||
getCommand (numVertices: number, numIndices: number): RenderCommand {
|
getCommand (numVertices: number, numIndices: number, stride: number): RenderCommand {
|
||||||
let cmd: RenderCommand | undefined;
|
let cmd: RenderCommand | undefined;
|
||||||
for (const c of this.pool) {
|
for (const c of this.pool) {
|
||||||
if (c._positions.length >= numVertices << 1 && c._indices.length >= numIndices) {
|
if (c._positions.length >= numVertices * stride && c._indices.length >= numIndices) {
|
||||||
cmd = c;
|
cmd = c;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!cmd) {
|
if (!cmd) {
|
||||||
const _positions = new Float32Array(numVertices << 1);
|
const _positions = new Float32Array(numVertices * stride);
|
||||||
const _uvs = new Float32Array(numVertices << 1);
|
const _uvs = new Float32Array(numVertices << 1);
|
||||||
const _colors = new Uint32Array(numVertices);
|
const _colors = new Uint32Array(numVertices);
|
||||||
const _darkColors = new Uint32Array(numVertices);
|
const _darkColors = new Uint32Array(numVertices);
|
||||||
@ -317,8 +352,8 @@ class CommandPool {
|
|||||||
cmd.numVertices = numVertices;
|
cmd.numVertices = numVertices;
|
||||||
cmd.numIndices = numIndices;
|
cmd.numIndices = numIndices;
|
||||||
|
|
||||||
cmd.positions = cmd._positions.subarray(0, numVertices << 1);
|
cmd.positions = cmd._positions.subarray(0, numVertices * stride);
|
||||||
cmd.uvs = cmd._uvs.subarray(0, numVertices * 2);
|
cmd.uvs = cmd._uvs.subarray(0, numVertices << 1);
|
||||||
cmd.colors = cmd._colors.subarray(0, numVertices);
|
cmd.colors = cmd._colors.subarray(0, numVertices);
|
||||||
cmd.darkColors = cmd._darkColors.subarray(0, numVertices);
|
cmd.darkColors = cmd._darkColors.subarray(0, numVertices);
|
||||||
cmd.indices = cmd._indices.subarray(0, numIndices);
|
cmd.indices = cmd._indices.subarray(0, numIndices);
|
||||||
|
|||||||
@ -93,7 +93,7 @@ class BasicExample extends Phaser.Scene {
|
|||||||
|
|
||||||
async preload() {
|
async preload() {
|
||||||
// manually add the text atlas to the game cache
|
// manually add the text atlas to the game cache
|
||||||
this.game.cache.text.add(this.atlasKey, { data: atlasString, premultipliedAlpha: true });
|
this.game.cache.text.add(this.atlasKey, atlasString);
|
||||||
|
|
||||||
// manually add the json skeleton to the game cache
|
// manually add the json skeleton to the game cache
|
||||||
this.game.cache.json.add("spineboy-data", skeletonJson);
|
this.game.cache.json.add("spineboy-data", skeletonJson);
|
||||||
|
|||||||
@ -226,7 +226,6 @@ export class SpineGameObject extends DepthMixin(
|
|||||||
animationState: AnimationState;
|
animationState: AnimationState;
|
||||||
beforeUpdateWorldTransforms: (object: SpineGameObject) => void = () => { };
|
beforeUpdateWorldTransforms: (object: SpineGameObject) => void = () => { };
|
||||||
afterUpdateWorldTransforms: (object: SpineGameObject) => void = () => { };
|
afterUpdateWorldTransforms: (object: SpineGameObject) => void = () => { };
|
||||||
private premultipliedAlpha = false;
|
|
||||||
private offsetX = 0;
|
private offsetX = 0;
|
||||||
private offsetY = 0;
|
private offsetY = 0;
|
||||||
|
|
||||||
@ -243,7 +242,6 @@ export class SpineGameObject extends DepthMixin(
|
|||||||
super(scene, (window as any).SPINE_GAME_OBJECT_TYPE ? (window as any).SPINE_GAME_OBJECT_TYPE : SPINE_GAME_OBJECT_TYPE);
|
super(scene, (window as any).SPINE_GAME_OBJECT_TYPE ? (window as any).SPINE_GAME_OBJECT_TYPE : SPINE_GAME_OBJECT_TYPE);
|
||||||
this.setPosition(x, y);
|
this.setPosition(x, y);
|
||||||
|
|
||||||
this.premultipliedAlpha = this.plugin.isAtlasPremultiplied(atlasKey);
|
|
||||||
this.skeleton = this.plugin.createSkeleton(dataKey, atlasKey);
|
this.skeleton = this.plugin.createSkeleton(dataKey, atlasKey);
|
||||||
this.animationStateData = new AnimationStateData(this.skeleton.data);
|
this.animationStateData = new AnimationStateData(this.skeleton.data);
|
||||||
this.animationState = new AnimationState(this.animationStateData);
|
this.animationState = new AnimationState(this.animationStateData);
|
||||||
@ -374,7 +372,6 @@ export class SpineGameObject extends DepthMixin(
|
|||||||
|
|
||||||
sceneRenderer.drawSkeleton(
|
sceneRenderer.drawSkeleton(
|
||||||
src.skeleton,
|
src.skeleton,
|
||||||
src.premultipliedAlpha,
|
|
||||||
-1,
|
-1,
|
||||||
-1,
|
-1,
|
||||||
(vertices, numVertices, stride) => {
|
(vertices, numVertices, stride) => {
|
||||||
|
|||||||
@ -58,7 +58,7 @@ export interface SpineGameObjectConfig extends Phaser.Types.GameObjects.GameObje
|
|||||||
* The scene's {@link LoaderPlugin} (`Scene.load`) gets these additional functions:
|
* The scene's {@link LoaderPlugin} (`Scene.load`) gets these additional functions:
|
||||||
* * `spineBinary(key: string, url: string, xhrSettings?: XHRSettingsObject)`: loads a skeleton binary `.skel` file from the `url`.
|
* * `spineBinary(key: string, url: string, xhrSettings?: XHRSettingsObject)`: loads a skeleton binary `.skel` file from the `url`.
|
||||||
* * `spineJson(key: string, url: string, xhrSettings?: XHRSettingsObject)`: loads a skeleton binary `.skel` file from the `url`.
|
* * `spineJson(key: string, url: string, xhrSettings?: XHRSettingsObject)`: loads a skeleton binary `.skel` file from the `url`.
|
||||||
* * `spineAtlas(key: string, url: string, premultipliedAlpha: boolean = true, xhrSettings?: XHRSettingsObject)`: loads a texture atlas `.atlas` file from the `url` as well as its correponding texture atlas page images.
|
* * `spineAtlas(key: string, url: string, xhrSettings?: XHRSettingsObject)`: loads a texture atlas `.atlas` file from the `url` as well as its correponding texture atlas page images.
|
||||||
*
|
*
|
||||||
* The scene's {@link GameObjectFactory} (`Scene.add`) gets these additional functions:
|
* The scene's {@link GameObjectFactory} (`Scene.add`) gets these additional functions:
|
||||||
* * `spine(x: number, y: number, dataKey: string, atlasKey: string, boundsProvider: SpineGameObjectBoundsProvider = SetupPoseBoundsProvider())`:
|
* * `spine(x: number, y: number, dataKey: string, atlasKey: string, boundsProvider: SpineGameObjectBoundsProvider = SetupPoseBoundsProvider())`:
|
||||||
@ -70,7 +70,7 @@ export interface SpineGameObjectConfig extends Phaser.Types.GameObjects.GameObje
|
|||||||
* The plugin has additional public methods to work with Spine Runtime core API objects:
|
* The plugin has additional public methods to work with Spine Runtime core API objects:
|
||||||
* * `getAtlas(atlasKey: string)`: returns the {@link TextureAtlas} instance for the given atlas key.
|
* * `getAtlas(atlasKey: string)`: returns the {@link TextureAtlas} instance for the given atlas key.
|
||||||
* * `getSkeletonData(skeletonDataKey: string)`: returns the {@link SkeletonData} instance for the given skeleton data key.
|
* * `getSkeletonData(skeletonDataKey: string)`: returns the {@link SkeletonData} instance for the given skeleton data key.
|
||||||
* * `createSkeleton(skeletonDataKey: string, atlasKey: string, premultipliedAlpha: boolean = true)`: creates a new {@link Skeleton} instance from the given skeleton data and atlas key.
|
* * `createSkeleton(skeletonDataKey: string, atlasKey: string)`: creates a new {@link Skeleton} instance from the given skeleton data and atlas key.
|
||||||
* * `isPremultipliedAlpha(atlasKey: string)`: returns `true` if the atlas with the given key has premultiplied alpha.
|
* * `isPremultipliedAlpha(atlasKey: string)`: returns `true` if the atlas with the given key has premultiplied alpha.
|
||||||
*/
|
*/
|
||||||
export class SpinePlugin extends Phaser.Plugins.ScenePlugin {
|
export class SpinePlugin extends Phaser.Plugins.ScenePlugin {
|
||||||
@ -119,9 +119,8 @@ export class SpinePlugin extends Phaser.Plugins.ScenePlugin {
|
|||||||
|
|
||||||
const atlasFileCallback = function (this: Phaser.Loader.LoaderPlugin, key: string,
|
const atlasFileCallback = function (this: Phaser.Loader.LoaderPlugin, key: string,
|
||||||
url: string,
|
url: string,
|
||||||
premultipliedAlpha: boolean,
|
|
||||||
xhrSettings: Phaser.Types.Loader.XHRSettingsObject) {
|
xhrSettings: Phaser.Types.Loader.XHRSettingsObject) {
|
||||||
const file = new SpineAtlasFile(this, key, url, premultipliedAlpha, xhrSettings);
|
const file = new SpineAtlasFile(this, key, url, xhrSettings);
|
||||||
this.addFile(file.files);
|
this.addFile(file.files);
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
@ -225,35 +224,26 @@ export class SpinePlugin extends Phaser.Plugins.ScenePlugin {
|
|||||||
|
|
||||||
/** Returns the TextureAtlas instance for the given key */
|
/** Returns the TextureAtlas instance for the given key */
|
||||||
getAtlas (atlasKey: string) {
|
getAtlas (atlasKey: string) {
|
||||||
let atlas: TextureAtlas;
|
if (this.atlasCache.exists(atlasKey)) return this.atlasCache.get(atlasKey);
|
||||||
if (this.atlasCache.exists(atlasKey)) {
|
|
||||||
atlas = this.atlasCache.get(atlasKey);
|
const atlas = new TextureAtlas(this.game.cache.text.get(atlasKey));
|
||||||
} else {
|
|
||||||
const atlasFile = this.game.cache.text.get(atlasKey) as { data: string, premultipliedAlpha: boolean };
|
|
||||||
atlas = new TextureAtlas(atlasFile.data);
|
|
||||||
if (this.isWebGL && this.gl) {
|
if (this.isWebGL && this.gl) {
|
||||||
const gl = this.gl;
|
const gl = this.gl;
|
||||||
const phaserUnpackPmaValue = gl.getParameter(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL);
|
for (const atlasPage of atlas.pages)
|
||||||
if (phaserUnpackPmaValue) gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
|
atlasPage.setTexture(new GLTexture(gl, this.game.textures.get(`${atlasKey}!${atlasPage.name}`).getSourceImage() as HTMLImageElement | ImageBitmap, atlasPage.pma, false));
|
||||||
for (const atlasPage of atlas.pages) {
|
|
||||||
atlasPage.setTexture(new GLTexture(gl, this.game.textures.get(`${atlasKey}!${atlasPage.name}`).getSourceImage() as HTMLImageElement | ImageBitmap, false));
|
|
||||||
}
|
|
||||||
if (phaserUnpackPmaValue) gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
|
|
||||||
} else {
|
} else {
|
||||||
for (const atlasPage of atlas.pages) {
|
for (const atlasPage of atlas.pages)
|
||||||
atlasPage.setTexture(new CanvasTexture(this.game.textures.get(`${atlasKey}!${atlasPage.name}`).getSourceImage() as HTMLImageElement | ImageBitmap));
|
atlasPage.setTexture(new CanvasTexture(this.game.textures.get(`${atlasKey}!${atlasPage.name}`).getSourceImage() as HTMLImageElement | ImageBitmap));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
this.atlasCache.add(atlasKey, atlas);
|
this.atlasCache.add(atlasKey, atlas);
|
||||||
}
|
|
||||||
return atlas;
|
return atlas;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns whether the TextureAtlas uses premultiplied alpha */
|
/** Returns whether the TextureAtlas uses premultiplied alpha */
|
||||||
isAtlasPremultiplied (atlasKey: string) {
|
isAtlasPremultiplied (atlasKey: string) {
|
||||||
const atlasFile = this.game.cache.text.get(atlasKey);
|
const atlas: TextureAtlas = this.atlasCache.get(atlasKey);
|
||||||
if (!atlasFile) return false;
|
if (!atlas || atlas.pages.length === 0) return false;
|
||||||
return atlasFile.premultipliedAlpha;
|
return atlas.pages[0].pma;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the SkeletonData instance for the given data and atlas key */
|
/** Returns the SkeletonData instance for the given data and atlas key */
|
||||||
@ -337,17 +327,15 @@ class SpineSkeletonDataFile extends Phaser.Loader.MultiFile {
|
|||||||
interface SpineAtlasFileConfig {
|
interface SpineAtlasFileConfig {
|
||||||
key: string;
|
key: string;
|
||||||
url: string;
|
url: string;
|
||||||
premultipliedAlpha?: boolean;
|
|
||||||
xhrSettings?: Phaser.Types.Loader.XHRSettingsObject;
|
xhrSettings?: Phaser.Types.Loader.XHRSettingsObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
class SpineAtlasFile extends Phaser.Loader.MultiFile {
|
class SpineAtlasFile extends Phaser.Loader.MultiFile {
|
||||||
constructor (loader: Phaser.Loader.LoaderPlugin, key: string | SpineAtlasFileConfig, url?: string, public premultipliedAlpha?: boolean, xhrSettings?: Phaser.Types.Loader.XHRSettingsObject) {
|
constructor (loader: Phaser.Loader.LoaderPlugin, key: string | SpineAtlasFileConfig, url?: string, xhrSettings?: Phaser.Types.Loader.XHRSettingsObject) {
|
||||||
if (typeof key !== "string") {
|
if (typeof key !== "string") {
|
||||||
const config = key;
|
const config = key;
|
||||||
key = config.key;
|
key = config.key;
|
||||||
url = config.url;
|
url = config.url;
|
||||||
premultipliedAlpha = config.premultipliedAlpha;
|
|
||||||
xhrSettings = config.xhrSettings;
|
xhrSettings = config.xhrSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -406,11 +394,6 @@ class SpineAtlasFile extends Phaser.Loader.MultiFile {
|
|||||||
textureManager.addImage(file.key, file.data);
|
textureManager.addImage(file.key, file.data);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.premultipliedAlpha = this.premultipliedAlpha ?? (file.data.indexOf("pma: true") >= 0 || file.data.indexOf("pma:true") >= 0);
|
|
||||||
file.data = {
|
|
||||||
data: file.data,
|
|
||||||
premultipliedAlpha: this.premultipliedAlpha,
|
|
||||||
};
|
|
||||||
file.addToCache();
|
file.addToCache();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -48,7 +48,7 @@ declare global {
|
|||||||
export interface LoaderPlugin {
|
export interface LoaderPlugin {
|
||||||
spineJson (key: string, url: string, xhrSettings?: Phaser.Types.Loader.XHRSettingsObject): LoaderPlugin;
|
spineJson (key: string, url: string, xhrSettings?: Phaser.Types.Loader.XHRSettingsObject): LoaderPlugin;
|
||||||
spineBinary (key: string, url: string, xhrSettings?: Phaser.Types.Loader.XHRSettingsObject): LoaderPlugin;
|
spineBinary (key: string, url: string, xhrSettings?: Phaser.Types.Loader.XHRSettingsObject): LoaderPlugin;
|
||||||
spineAtlas (key: string, url: string, premultipliedAlpha?: boolean, xhrSettings?: Phaser.Types.Loader.XHRSettingsObject): LoaderPlugin;
|
spineAtlas (key: string, url: string, xhrSettings?: Phaser.Types.Loader.XHRSettingsObject): LoaderPlugin;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -93,7 +93,7 @@ class BasicExample extends Phaser.Scene {
|
|||||||
|
|
||||||
async preload() {
|
async preload() {
|
||||||
// manually add the text atlas to the game cache
|
// manually add the text atlas to the game cache
|
||||||
this.game.cache.text.add(this.atlasKey, { data: atlasString, premultipliedAlpha: true });
|
this.game.cache.text.add(this.atlasKey, atlasString);
|
||||||
|
|
||||||
// manually add the json skeleton to the game cache
|
// manually add the json skeleton to the game cache
|
||||||
this.game.cache.json.add("spineboy-data", skeletonJson);
|
this.game.cache.json.add("spineboy-data", skeletonJson);
|
||||||
|
|||||||
@ -226,7 +226,6 @@ export class SpineGameObject extends DepthMixin(
|
|||||||
animationState: AnimationState;
|
animationState: AnimationState;
|
||||||
beforeUpdateWorldTransforms: (object: SpineGameObject) => void = () => { };
|
beforeUpdateWorldTransforms: (object: SpineGameObject) => void = () => { };
|
||||||
afterUpdateWorldTransforms: (object: SpineGameObject) => void = () => { };
|
afterUpdateWorldTransforms: (object: SpineGameObject) => void = () => { };
|
||||||
private premultipliedAlpha = false;
|
|
||||||
private offsetX = 0;
|
private offsetX = 0;
|
||||||
private offsetY = 0;
|
private offsetY = 0;
|
||||||
|
|
||||||
@ -243,7 +242,6 @@ export class SpineGameObject extends DepthMixin(
|
|||||||
super(scene, (window as any).SPINE_GAME_OBJECT_TYPE ? (window as any).SPINE_GAME_OBJECT_TYPE : SPINE_GAME_OBJECT_TYPE);
|
super(scene, (window as any).SPINE_GAME_OBJECT_TYPE ? (window as any).SPINE_GAME_OBJECT_TYPE : SPINE_GAME_OBJECT_TYPE);
|
||||||
this.setPosition(x, y);
|
this.setPosition(x, y);
|
||||||
|
|
||||||
this.premultipliedAlpha = this.plugin.isAtlasPremultiplied(atlasKey);
|
|
||||||
this.skeleton = this.plugin.createSkeleton(dataKey, atlasKey);
|
this.skeleton = this.plugin.createSkeleton(dataKey, atlasKey);
|
||||||
this.animationStateData = new AnimationStateData(this.skeleton.data);
|
this.animationStateData = new AnimationStateData(this.skeleton.data);
|
||||||
this.animationState = new AnimationState(this.animationStateData);
|
this.animationState = new AnimationState(this.animationStateData);
|
||||||
@ -393,7 +391,6 @@ export class SpineGameObject extends DepthMixin(
|
|||||||
|
|
||||||
sceneRenderer.drawSkeleton(
|
sceneRenderer.drawSkeleton(
|
||||||
src.skeleton,
|
src.skeleton,
|
||||||
src.premultipliedAlpha,
|
|
||||||
-1,
|
-1,
|
||||||
-1,
|
-1,
|
||||||
(vertices, numVertices, stride) => {
|
(vertices, numVertices, stride) => {
|
||||||
|
|||||||
@ -58,7 +58,7 @@ export interface SpineGameObjectConfig extends Phaser.Types.GameObjects.GameObje
|
|||||||
* The scene's {@link LoaderPlugin} (`Scene.load`) gets these additional functions:
|
* The scene's {@link LoaderPlugin} (`Scene.load`) gets these additional functions:
|
||||||
* * `spineBinary(key: string, url: string, xhrSettings?: XHRSettingsObject)`: loads a skeleton binary `.skel` file from the `url`.
|
* * `spineBinary(key: string, url: string, xhrSettings?: XHRSettingsObject)`: loads a skeleton binary `.skel` file from the `url`.
|
||||||
* * `spineJson(key: string, url: string, xhrSettings?: XHRSettingsObject)`: loads a skeleton binary `.skel` file from the `url`.
|
* * `spineJson(key: string, url: string, xhrSettings?: XHRSettingsObject)`: loads a skeleton binary `.skel` file from the `url`.
|
||||||
* * `spineAtlas(key: string, url: string, premultipliedAlpha: boolean = true, xhrSettings?: XHRSettingsObject)`: loads a texture atlas `.atlas` file from the `url` as well as its correponding texture atlas page images.
|
* * `spineAtlas(key: string, url: string, xhrSettings?: XHRSettingsObject)`: loads a texture atlas `.atlas` file from the `url` as well as its correponding texture atlas page images.
|
||||||
*
|
*
|
||||||
* The scene's {@link GameObjectFactory} (`Scene.add`) gets these additional functions:
|
* The scene's {@link GameObjectFactory} (`Scene.add`) gets these additional functions:
|
||||||
* * `spine(x: number, y: number, dataKey: string, atlasKey: string, boundsProvider: SpineGameObjectBoundsProvider = SetupPoseBoundsProvider())`:
|
* * `spine(x: number, y: number, dataKey: string, atlasKey: string, boundsProvider: SpineGameObjectBoundsProvider = SetupPoseBoundsProvider())`:
|
||||||
@ -70,8 +70,7 @@ export interface SpineGameObjectConfig extends Phaser.Types.GameObjects.GameObje
|
|||||||
* The plugin has additional public methods to work with Spine Runtime core API objects:
|
* The plugin has additional public methods to work with Spine Runtime core API objects:
|
||||||
* * `getAtlas(atlasKey: string)`: returns the {@link TextureAtlas} instance for the given atlas key.
|
* * `getAtlas(atlasKey: string)`: returns the {@link TextureAtlas} instance for the given atlas key.
|
||||||
* * `getSkeletonData(skeletonDataKey: string)`: returns the {@link SkeletonData} instance for the given skeleton data key.
|
* * `getSkeletonData(skeletonDataKey: string)`: returns the {@link SkeletonData} instance for the given skeleton data key.
|
||||||
* * `createSkeleton(skeletonDataKey: string, atlasKey: string, premultipliedAlpha: boolean = true)`: creates a new {@link Skeleton} instance from the given skeleton data and atlas key.
|
* * `createSkeleton(atlasKey: string)`: creates a new {@link Skeleton} instance from the given skeleton data and atlas key.
|
||||||
* * `isPremultipliedAlpha(atlasKey: string)`: returns `true` if the atlas with the given key has premultiplied alpha.
|
|
||||||
*/
|
*/
|
||||||
export class SpinePlugin extends Phaser.Plugins.ScenePlugin {
|
export class SpinePlugin extends Phaser.Plugins.ScenePlugin {
|
||||||
game: Phaser.Game;
|
game: Phaser.Game;
|
||||||
@ -116,9 +115,8 @@ export class SpinePlugin extends Phaser.Plugins.ScenePlugin {
|
|||||||
|
|
||||||
const atlasFileCallback = function (this: Phaser.Loader.LoaderPlugin, key: string,
|
const atlasFileCallback = function (this: Phaser.Loader.LoaderPlugin, key: string,
|
||||||
url: string,
|
url: string,
|
||||||
premultipliedAlpha: boolean,
|
|
||||||
xhrSettings: Phaser.Types.Loader.XHRSettingsObject) {
|
xhrSettings: Phaser.Types.Loader.XHRSettingsObject) {
|
||||||
const file = new SpineAtlasFile(this, key, url, premultipliedAlpha, xhrSettings);
|
const file = new SpineAtlasFile(this, key, url, xhrSettings);
|
||||||
this.addFile(file.files);
|
this.addFile(file.files);
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
@ -204,35 +202,26 @@ export class SpinePlugin extends Phaser.Plugins.ScenePlugin {
|
|||||||
|
|
||||||
/** Returns the TextureAtlas instance for the given key */
|
/** Returns the TextureAtlas instance for the given key */
|
||||||
getAtlas (atlasKey: string) {
|
getAtlas (atlasKey: string) {
|
||||||
let atlas: TextureAtlas;
|
if (this.atlasCache.exists(atlasKey)) return this.atlasCache.get(atlasKey);
|
||||||
if (this.atlasCache.exists(atlasKey)) {
|
|
||||||
atlas = this.atlasCache.get(atlasKey);
|
const atlas = new TextureAtlas(this.game.cache.text.get(atlasKey));
|
||||||
} else {
|
|
||||||
const atlasFile = this.game.cache.text.get(atlasKey) as { data: string, premultipliedAlpha: boolean };
|
|
||||||
atlas = new TextureAtlas(atlasFile.data);
|
|
||||||
if (this.isWebGL && this.gl) {
|
if (this.isWebGL && this.gl) {
|
||||||
const gl = this.gl;
|
const gl = this.gl;
|
||||||
const phaserUnpackPmaValue = gl.getParameter(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL);
|
for (const atlasPage of atlas.pages)
|
||||||
if (phaserUnpackPmaValue) gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
|
atlasPage.setTexture(new GLTexture(gl, this.game.textures.get(`${atlasKey}!${atlasPage.name}`).getSourceImage() as HTMLImageElement | ImageBitmap, atlasPage.pma, false));
|
||||||
for (const atlasPage of atlas.pages) {
|
|
||||||
atlasPage.setTexture(new GLTexture(gl, this.game.textures.get(`${atlasKey}!${atlasPage.name}`).getSourceImage() as HTMLImageElement | ImageBitmap, false));
|
|
||||||
}
|
|
||||||
if (phaserUnpackPmaValue) gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
|
|
||||||
} else {
|
} else {
|
||||||
for (const atlasPage of atlas.pages) {
|
for (const atlasPage of atlas.pages)
|
||||||
atlasPage.setTexture(new CanvasTexture(this.game.textures.get(`${atlasKey}!${atlasPage.name}`).getSourceImage() as HTMLImageElement | ImageBitmap));
|
atlasPage.setTexture(new CanvasTexture(this.game.textures.get(`${atlasKey}!${atlasPage.name}`).getSourceImage() as HTMLImageElement | ImageBitmap));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
this.atlasCache.add(atlasKey, atlas);
|
this.atlasCache.add(atlasKey, atlas);
|
||||||
}
|
|
||||||
return atlas;
|
return atlas;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns whether the TextureAtlas uses premultiplied alpha */
|
/** Returns whether the TextureAtlas uses premultiplied alpha */
|
||||||
isAtlasPremultiplied (atlasKey: string) {
|
isAtlasPremultiplied (atlasKey: string) {
|
||||||
const atlasFile = this.game.cache.text.get(atlasKey);
|
const atlas: TextureAtlas = this.atlasCache.get(atlasKey);
|
||||||
if (!atlasFile) return false;
|
if (!atlas || atlas.pages.length === 0) return false;
|
||||||
return atlasFile.premultipliedAlpha;
|
return atlas.pages[0].pma;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the SkeletonData instance for the given data and atlas key */
|
/** Returns the SkeletonData instance for the given data and atlas key */
|
||||||
@ -326,17 +315,15 @@ class SpineSkeletonDataFile extends Phaser.Loader.MultiFile {
|
|||||||
interface SpineAtlasFileConfig {
|
interface SpineAtlasFileConfig {
|
||||||
key: string;
|
key: string;
|
||||||
url: string;
|
url: string;
|
||||||
premultipliedAlpha?: boolean;
|
|
||||||
xhrSettings?: Phaser.Types.Loader.XHRSettingsObject;
|
xhrSettings?: Phaser.Types.Loader.XHRSettingsObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
class SpineAtlasFile extends Phaser.Loader.MultiFile {
|
class SpineAtlasFile extends Phaser.Loader.MultiFile {
|
||||||
constructor (loader: Phaser.Loader.LoaderPlugin, key: string | SpineAtlasFileConfig, url?: string, public premultipliedAlpha?: boolean, xhrSettings?: Phaser.Types.Loader.XHRSettingsObject) {
|
constructor (loader: Phaser.Loader.LoaderPlugin, key: string | SpineAtlasFileConfig, url?: string, xhrSettings?: Phaser.Types.Loader.XHRSettingsObject) {
|
||||||
if (typeof key !== "string") {
|
if (typeof key !== "string") {
|
||||||
const config = key;
|
const config = key;
|
||||||
key = config.key;
|
key = config.key;
|
||||||
url = config.url;
|
url = config.url;
|
||||||
premultipliedAlpha = config.premultipliedAlpha;
|
|
||||||
xhrSettings = config.xhrSettings;
|
xhrSettings = config.xhrSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -395,11 +382,6 @@ class SpineAtlasFile extends Phaser.Loader.MultiFile {
|
|||||||
textureManager.addImage(file.key, file.data);
|
textureManager.addImage(file.key, file.data);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.premultipliedAlpha = this.premultipliedAlpha ?? (file.data.indexOf("pma: true") >= 0 || file.data.indexOf("pma:true") >= 0);
|
|
||||||
file.data = {
|
|
||||||
data: file.data,
|
|
||||||
premultipliedAlpha: this.premultipliedAlpha,
|
|
||||||
};
|
|
||||||
file.addToCache();
|
file.addToCache();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -48,7 +48,7 @@ declare global {
|
|||||||
export interface LoaderPlugin {
|
export interface LoaderPlugin {
|
||||||
spineJson (key: string, url: string, xhrSettings?: Phaser.Types.Loader.XHRSettingsObject): LoaderPlugin;
|
spineJson (key: string, url: string, xhrSettings?: Phaser.Types.Loader.XHRSettingsObject): LoaderPlugin;
|
||||||
spineBinary (key: string, url: string, xhrSettings?: Phaser.Types.Loader.XHRSettingsObject): LoaderPlugin;
|
spineBinary (key: string, url: string, xhrSettings?: Phaser.Types.Loader.XHRSettingsObject): LoaderPlugin;
|
||||||
spineAtlas (key: string, url: string, premultipliedAlpha?: boolean, xhrSettings?: Phaser.Types.Loader.XHRSettingsObject): LoaderPlugin;
|
spineAtlas (key: string, url: string, xhrSettings?: Phaser.Types.Loader.XHRSettingsObject): LoaderPlugin;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -671,7 +671,6 @@ export class Spine extends Container {
|
|||||||
const skeletonColor = skeleton.color;
|
const skeletonColor = skeleton.color;
|
||||||
const slotColor = pose.color;
|
const slotColor = pose.color;
|
||||||
const alpha = skeletonColor.a * slotColor.a * attachmentColor.a;
|
const alpha = skeletonColor.a * slotColor.a * attachmentColor.a;
|
||||||
// cannot premultiply the colors because the default mesh renderer already does that
|
|
||||||
this.lightColor.set(
|
this.lightColor.set(
|
||||||
skeletonColor.r * slotColor.r * attachmentColor.r,
|
skeletonColor.r * slotColor.r * attachmentColor.r,
|
||||||
skeletonColor.g * slotColor.g * attachmentColor.g,
|
skeletonColor.g * slotColor.g * attachmentColor.g,
|
||||||
|
|||||||
@ -27,7 +27,6 @@
|
|||||||
skeleton: "/assets/spineboy-pro.skel",
|
skeleton: "/assets/spineboy-pro.skel",
|
||||||
atlas: "/assets/spineboy-pma.atlas",
|
atlas: "/assets/spineboy-pma.atlas",
|
||||||
animation: "run",
|
animation: "run",
|
||||||
premultipliedAlpha: true,
|
|
||||||
backgroundColor: "#cccccc",
|
backgroundColor: "#cccccc",
|
||||||
viewport: {
|
viewport: {
|
||||||
debugRender: true,
|
debugRender: true,
|
||||||
|
|||||||
@ -15,7 +15,7 @@
|
|||||||
<script>
|
<script>
|
||||||
var code =
|
var code =
|
||||||
'<script src="https://unpkg.com/@esotericsoftware/spine-player@4.3.*/dist/iife/spine-player.js"></' +
|
'<script src="https://unpkg.com/@esotericsoftware/spine-player@4.3.*/dist/iife/spine-player.js"></' +
|
||||||
'script>\n<link rel="stylesheet" href="https://unpkg.com/@esotericsoftware/spine-player@4.3.*/dist/spine-player.css">\n\n<div id="player-container" style="width: 100%; height: 100vh;"></div>\n\n<script>\nnew spine.SpinePlayer("player-container", {\n skeleton: "http://esotericsoftware.com/files/examples/4.3/spineboy/export/spineboy-pro.json",\n atlas: "http://esotericsoftware.com/files/examples/4.3/spineboy/export/spineboy.atlas",\n premultipliedAlpha: false\n});\n</' +
|
'script>\n<link rel="stylesheet" href="https://unpkg.com/@esotericsoftware/spine-player@4.3.*/dist/spine-player.css">\n\n<div id="player-container" style="width: 100%; height: 100vh;"></div>\n\n<script>\nnew spine.SpinePlayer("player-container", {\n skeleton: "http://esotericsoftware.com/files/examples/4.3/spineboy/export/spineboy-pro.json",\n atlas: "http://esotericsoftware.com/files/examples/4.3/spineboy/export/spineboy.atlas"\n});\n</' +
|
||||||
"script>".trim();
|
"script>".trim();
|
||||||
spine.SpinePlayerEditor.DEFAULT_CODE = code;
|
spine.SpinePlayerEditor.DEFAULT_CODE = code;
|
||||||
var player = new spine.SpinePlayerEditor(
|
var player = new spine.SpinePlayerEditor(
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -27,7 +27,6 @@
|
|||||||
skeleton: "/assets/spineboy-pro.skel",
|
skeleton: "/assets/spineboy-pro.skel",
|
||||||
atlas: "/assets/spineboy-pma.atlas",
|
atlas: "/assets/spineboy-pma.atlas",
|
||||||
animation: "run",
|
animation: "run",
|
||||||
premultipliedAlpha: true,
|
|
||||||
backgroundColor: "#cccccc",
|
backgroundColor: "#cccccc",
|
||||||
viewport: {
|
viewport: {
|
||||||
debugRender: true,
|
debugRender: true,
|
||||||
@ -46,7 +45,6 @@
|
|||||||
atlas: "/assets/raptor-pma.atlas",
|
atlas: "/assets/raptor-pma.atlas",
|
||||||
animation: "walk",
|
animation: "walk",
|
||||||
showControls: false,
|
showControls: false,
|
||||||
premultipliedAlpha: true,
|
|
||||||
backgroundColor: "#00000000",
|
backgroundColor: "#00000000",
|
||||||
alpha: true,
|
alpha: true,
|
||||||
preserveDrawingBuffer: true,
|
preserveDrawingBuffer: true,
|
||||||
|
|||||||
@ -66,9 +66,6 @@ export interface SpinePlayerConfig {
|
|||||||
/* Optional: List of skin names from which the user can choose. Default: all skins */
|
/* Optional: List of skin names from which the user can choose. Default: all skins */
|
||||||
skins?: string[]
|
skins?: string[]
|
||||||
|
|
||||||
/* Optional: Whether the skeleton's atlas images use premultiplied alpha. Default: true */
|
|
||||||
premultipliedAlpha?: boolean
|
|
||||||
|
|
||||||
/* Optional: Whether to show the player controls. When false, no external CSS file is needed. Default: true */
|
/* Optional: Whether to show the player controls. When false, no external CSS file is needed. Default: true */
|
||||||
showControls?: boolean
|
showControls?: boolean
|
||||||
|
|
||||||
@ -311,7 +308,6 @@ export class SpinePlayer implements Disposable {
|
|||||||
if (!config.backgroundColor) config.backgroundColor = config.alpha ? "00000000" : "000000";
|
if (!config.backgroundColor) config.backgroundColor = config.alpha ? "00000000" : "000000";
|
||||||
if (!config.fullScreenBackgroundColor) config.fullScreenBackgroundColor = config.backgroundColor;
|
if (!config.fullScreenBackgroundColor) config.fullScreenBackgroundColor = config.backgroundColor;
|
||||||
if (config.backgroundImage && !config.backgroundImage.url) config.backgroundImage = undefined;
|
if (config.backgroundImage && !config.backgroundImage.url) config.backgroundImage = undefined;
|
||||||
if (config.premultipliedAlpha === void 0) config.premultipliedAlpha = true;
|
|
||||||
if (config.preserveDrawingBuffer === void 0) config.preserveDrawingBuffer = false;
|
if (config.preserveDrawingBuffer === void 0) config.preserveDrawingBuffer = false;
|
||||||
if (config.mipmaps === void 0) config.mipmaps = true;
|
if (config.mipmaps === void 0) config.mipmaps = true;
|
||||||
if (config.interactive === void 0) config.interactive = true;
|
if (config.interactive === void 0) config.interactive = true;
|
||||||
@ -919,7 +915,7 @@ export class SpinePlayer implements Disposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Draw the skeleton and debug output.
|
// Draw the skeleton and debug output.
|
||||||
renderer.drawSkeleton(skeleton, config.premultipliedAlpha);
|
renderer.drawSkeleton(skeleton);
|
||||||
if (Number(renderer.skeletonDebugRenderer.drawBones = config.debug!.bones! ?? false)
|
if (Number(renderer.skeletonDebugRenderer.drawBones = config.debug!.bones! ?? false)
|
||||||
+ Number(renderer.skeletonDebugRenderer.drawBoundingBoxes = config.debug!.bounds! ?? false)
|
+ Number(renderer.skeletonDebugRenderer.drawBoundingBoxes = config.debug!.bounds! ?? false)
|
||||||
+ Number(renderer.skeletonDebugRenderer.drawClipping = config.debug!.clipping! ?? false)
|
+ Number(renderer.skeletonDebugRenderer.drawClipping = config.debug!.clipping! ?? false)
|
||||||
@ -928,7 +924,7 @@ export class SpinePlayer implements Disposable {
|
|||||||
+ Number(renderer.skeletonDebugRenderer.drawRegionAttachments = config.debug!.regions! ?? false)
|
+ Number(renderer.skeletonDebugRenderer.drawRegionAttachments = config.debug!.regions! ?? false)
|
||||||
+ Number(renderer.skeletonDebugRenderer.drawMeshTriangles = config.debug!.meshes! ?? false) > 0
|
+ Number(renderer.skeletonDebugRenderer.drawMeshTriangles = config.debug!.meshes! ?? false) > 0
|
||||||
) {
|
) {
|
||||||
renderer.drawSkeletonDebug(skeleton, config.premultipliedAlpha);
|
renderer.drawSkeletonDebug(skeleton);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw the control bones.
|
// Draw the control bones.
|
||||||
|
|||||||
@ -44,10 +44,9 @@
|
|||||||
let controls;
|
let controls;
|
||||||
let lastFrameTime = Date.now() / 1000;
|
let lastFrameTime = Date.now() / 1000;
|
||||||
|
|
||||||
let pma = true;
|
|
||||||
let baseUrl = "/assets/";
|
let baseUrl = "/assets/";
|
||||||
let skeletonFile = "celestial-circus-pro.json";
|
let skeletonFile = "celestial-circus-pro.json";
|
||||||
let atlasFile = `celestial-circus${pma ? "-pma" : ""}.atlas`;
|
let atlasFile = `celestial-circus-pma.atlas`;
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
// create the THREE.JS camera, scene and renderer (WebGL)
|
// create the THREE.JS camera, scene and renderer (WebGL)
|
||||||
@ -64,7 +63,7 @@
|
|||||||
controls = new OrbitControls(camera, renderer.domElement);
|
controls = new OrbitControls(camera, renderer.domElement);
|
||||||
|
|
||||||
// load the assets required to display the Raptor model
|
// load the assets required to display the Raptor model
|
||||||
assetManager = new spine.AssetManager(baseUrl, undefined, pma);
|
assetManager = new spine.AssetManager(baseUrl);
|
||||||
assetManager.loadText(skeletonFile);
|
assetManager.loadText(skeletonFile);
|
||||||
assetManager.loadTextureAtlas(atlasFile);
|
assetManager.loadTextureAtlas(atlasFile);
|
||||||
|
|
||||||
|
|||||||
@ -45,8 +45,6 @@
|
|||||||
let controls;
|
let controls;
|
||||||
let lastFrameTime = Date.now() / 1000;
|
let lastFrameTime = Date.now() / 1000;
|
||||||
|
|
||||||
let pma = false;
|
|
||||||
|
|
||||||
let baseUrl = "/assets/";
|
let baseUrl = "/assets/";
|
||||||
let skeletonFile = "raptor-pro.json";
|
let skeletonFile = "raptor-pro.json";
|
||||||
let atlasFile = "raptor.atlas";
|
let atlasFile = "raptor.atlas";
|
||||||
@ -117,7 +115,7 @@
|
|||||||
scene.add(plane)
|
scene.add(plane)
|
||||||
|
|
||||||
// load the assets required to display the Raptor model
|
// load the assets required to display the Raptor model
|
||||||
assetManager = new spine.AssetManager(baseUrl, undefined, pma);
|
assetManager = new spine.AssetManager(baseUrl);
|
||||||
assetManager.loadText(skeletonFile);
|
assetManager.loadText(skeletonFile);
|
||||||
assetManager.loadTextureAtlas(atlasFile);
|
assetManager.loadTextureAtlas(atlasFile);
|
||||||
|
|
||||||
|
|||||||
@ -31,9 +31,11 @@ import { AssetManagerBase, Downloader } from "@esotericsoftware/spine-core"
|
|||||||
import { ThreeJsTexture } from "./ThreeJsTexture.js";
|
import { ThreeJsTexture } from "./ThreeJsTexture.js";
|
||||||
|
|
||||||
export class AssetManager extends AssetManagerBase {
|
export class AssetManager extends AssetManagerBase {
|
||||||
constructor (pathPrefix: string = "", downloader: Downloader = new Downloader(), pma = false) {
|
constructor (pathPrefix: string = "", downloader: Downloader = new Downloader()) {
|
||||||
super((image: HTMLImageElement | ImageBitmap) => {
|
super(
|
||||||
return new ThreeJsTexture(image, pma);
|
(image: HTMLImageElement | ImageBitmap, pma = false) => new ThreeJsTexture(image, pma),
|
||||||
}, pathPrefix, downloader);
|
pathPrefix,
|
||||||
|
downloader,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -499,7 +499,7 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
|
|||||||
|
|
||||||
const tempVector = new Vector3();
|
const tempVector = new Vector3();
|
||||||
for (const widget of this.widgets) {
|
for (const widget of this.widgets) {
|
||||||
const { skeleton, pma, bounds, debug, offsetX, offsetY, dragX, dragY, fit, spinner, loading, clip, drag } = widget;
|
const { skeleton, bounds, debug, offsetX, offsetY, dragX, dragY, fit, spinner, loading, clip, drag } = widget;
|
||||||
|
|
||||||
if (widget.isOffScreenAndWasMoved()) continue;
|
if (widget.isOffScreenAndWasMoved()) continue;
|
||||||
const elementRef = widget.getHostElement();
|
const elementRef = widget.getHostElement();
|
||||||
@ -614,7 +614,7 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
|
|||||||
widget.worldX = worldOffsetX;
|
widget.worldX = worldOffsetX;
|
||||||
widget.worldY = worldOffsetY;
|
widget.worldY = worldOffsetY;
|
||||||
|
|
||||||
renderer.drawSkeleton(skeleton, pma, -1, -1, (vertices, size, vertexSize) => {
|
renderer.drawSkeleton(skeleton, -1, -1, (vertices, size, vertexSize) => {
|
||||||
for (let i = 0; i < size; i += vertexSize) {
|
for (let i = 0; i < size; i += vertexSize) {
|
||||||
vertices[i] = vertices[i] + worldOffsetX;
|
vertices[i] = vertices[i] + worldOffsetX;
|
||||||
vertices[i + 1] = vertices[i + 1] + worldOffsetY;
|
vertices[i + 1] = vertices[i + 1] + worldOffsetY;
|
||||||
|
|||||||
@ -132,7 +132,6 @@ interface WidgetPublicProperties {
|
|||||||
|
|
||||||
// Usage of this properties is discouraged because they can be made private in the future
|
// Usage of this properties is discouraged because they can be made private in the future
|
||||||
interface WidgetInternalProperties {
|
interface WidgetInternalProperties {
|
||||||
pma: boolean
|
|
||||||
dprScale: number
|
dprScale: number
|
||||||
dragging: boolean
|
dragging: boolean
|
||||||
dragX: number
|
dragX: number
|
||||||
@ -648,13 +647,6 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable
|
|||||||
*/
|
*/
|
||||||
public dragging = false;
|
public dragging = false;
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
* If true, the widget has texture with premultiplied alpha
|
|
||||||
* Do not rely on this properties. It might be made private in the future.
|
|
||||||
*/
|
|
||||||
public pma = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If true, indicate {@link dispose} has been called and the widget cannot be used anymore
|
* If true, indicate {@link dispose} has been called and the widget cannot be used anymore
|
||||||
*/
|
*/
|
||||||
@ -972,7 +964,6 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const atlas = this.overlay.assetManager.require(atlasPath) as TextureAtlas;
|
const atlas = this.overlay.assetManager.require(atlasPath) as TextureAtlas;
|
||||||
this.pma = atlas.pages[0]?.pma
|
|
||||||
|
|
||||||
const atlasLoader = new AtlasAttachmentLoader(atlas);
|
const atlasLoader = new AtlasAttachmentLoader(atlas);
|
||||||
|
|
||||||
|
|||||||
@ -132,7 +132,7 @@ var additiveBlendingDemo = function (canvas, bgColor) {
|
|||||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
renderer.begin();
|
renderer.begin();
|
||||||
renderer.drawSkeleton(skeleton, true);
|
renderer.drawSkeleton(skeleton);
|
||||||
|
|
||||||
if (isMobileDevice()) {
|
if (isMobileDevice()) {
|
||||||
gl.lineWidth(2);
|
gl.lineWidth(2);
|
||||||
|
|||||||
@ -99,8 +99,8 @@ var clippingDemo = function (canvas, bgColor) {
|
|||||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
renderer.begin();
|
renderer.begin();
|
||||||
renderer.drawSkeleton(skeleton, true);
|
renderer.drawSkeleton(skeleton);
|
||||||
renderer.drawSkeletonDebug(skeleton, false, ["root"]);
|
renderer.drawSkeletonDebug(skeleton, ["root"]);
|
||||||
renderer.end();
|
renderer.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -127,8 +127,8 @@ var hoverboardDemo = function (canvas, bgColor) {
|
|||||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
renderer.begin();
|
renderer.begin();
|
||||||
renderer.drawSkeleton(skeleton, true);
|
renderer.drawSkeleton(skeleton);
|
||||||
renderer.drawSkeletonDebug(skeleton, false, ["root"]);
|
renderer.drawSkeletonDebug(skeleton, ["root"]);
|
||||||
gl.lineWidth(2);
|
gl.lineWidth(2);
|
||||||
for (var i = 0; i < controlBones.length; i++) {
|
for (var i = 0; i < controlBones.length; i++) {
|
||||||
var bone = skeleton.findBone(controlBones[i]);
|
var bone = skeleton.findBone(controlBones[i]);
|
||||||
|
|||||||
@ -146,7 +146,7 @@ var imageChangesDemo = function (canvas, bgColor) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderer.begin();
|
renderer.begin();
|
||||||
renderer.drawSkeleton(skeleton, true);
|
renderer.drawSkeleton(skeleton);
|
||||||
|
|
||||||
var y = offsetY;
|
var y = offsetY;
|
||||||
var slotsWidth = 0, slotsHeight = 0;
|
var slotsWidth = 0, slotsHeight = 0;
|
||||||
|
|||||||
@ -137,7 +137,7 @@ var meshesDemo = function (canvas, bgColor) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderer.begin();
|
renderer.begin();
|
||||||
renderer.drawSkeleton(skeleton, true);
|
renderer.drawSkeleton(skeleton);
|
||||||
renderer.drawSkeletonDebug(skeleton);
|
renderer.drawSkeletonDebug(skeleton);
|
||||||
renderer.end();
|
renderer.end();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -188,7 +188,7 @@ var skinsDemo = function (canvas, bgColor) {
|
|||||||
skeleton.updateWorldTransform(spine.Physics.update);
|
skeleton.updateWorldTransform(spine.Physics.update);
|
||||||
|
|
||||||
renderer.begin();
|
renderer.begin();
|
||||||
renderer.drawSkeleton(skeleton, true);
|
renderer.drawSkeleton(skeleton);
|
||||||
var texture = assetManager.get("heroes.png");
|
var texture = assetManager.get("heroes.png");
|
||||||
var width = bounds.x * 1.25;
|
var width = bounds.x * 1.25;
|
||||||
var scale = width / texture.getImage().width;
|
var scale = width / texture.getImage().width;
|
||||||
|
|||||||
@ -129,8 +129,8 @@ var spritesheetsDemo = function (canvas, bgColor) {
|
|||||||
|
|
||||||
renderer.begin();
|
renderer.begin();
|
||||||
var frame = frames[currFrame];
|
var frame = frames[currFrame];
|
||||||
renderer.drawSkeleton(skeleton, true);
|
renderer.drawSkeleton(skeleton);
|
||||||
renderer.drawSkeleton(skeletonSeq, true);
|
renderer.drawSkeleton(skeletonSeq);
|
||||||
renderer.end();
|
renderer.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -136,8 +136,8 @@ var stretchymanDemo = function (canvas, bgColor) {
|
|||||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
renderer.begin();
|
renderer.begin();
|
||||||
renderer.drawSkeleton(skeleton, true);
|
renderer.drawSkeleton(skeleton);
|
||||||
renderer.drawSkeletonDebug(skeleton, false, ["root"]);
|
renderer.drawSkeletonDebug(skeleton, ["root"]);
|
||||||
gl.lineWidth(2);
|
gl.lineWidth(2);
|
||||||
for (var i = 0; i < controlBones.length; i++) {
|
for (var i = 0; i < controlBones.length; i++) {
|
||||||
var bone = skeleton.findBone(controlBones[i]);
|
var bone = skeleton.findBone(controlBones[i]);
|
||||||
|
|||||||
@ -100,8 +100,8 @@ var tankDemo = function (canvas, bgColor) {
|
|||||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
renderer.begin();
|
renderer.begin();
|
||||||
renderer.drawSkeleton(skeleton, true);
|
renderer.drawSkeleton(skeleton);
|
||||||
renderer.drawSkeletonDebug(skeleton, true);
|
renderer.drawSkeletonDebug(skeleton);
|
||||||
renderer.end();
|
renderer.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -124,8 +124,8 @@ var transformsDemo = function (canvas, bgColor) {
|
|||||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
renderer.begin();
|
renderer.begin();
|
||||||
renderer.drawSkeleton(skeleton, true);
|
renderer.drawSkeleton(skeleton);
|
||||||
renderer.drawSkeletonDebug(skeleton, false, ["root", "rotate-handle"]);
|
renderer.drawSkeletonDebug(skeleton, ["root", "rotate-handle"]);
|
||||||
gl.lineWidth(2);
|
gl.lineWidth(2);
|
||||||
for (var i = 0; i < controlBones.length; i++) {
|
for (var i = 0; i < controlBones.length; i++) {
|
||||||
var bone = skeleton.findBone(controlBones[i]);
|
var bone = skeleton.findBone(controlBones[i]);
|
||||||
|
|||||||
@ -111,14 +111,14 @@ var transitionsDemo = function (canvas, loadingComplete, bgColor) {
|
|||||||
skeleton.updateWorldTransform(spine.Physics.update);
|
skeleton.updateWorldTransform(spine.Physics.update);
|
||||||
skeleton.x = -200;
|
skeleton.x = -200;
|
||||||
skeleton.y = -100;
|
skeleton.y = -100;
|
||||||
renderer.drawSkeleton(skeleton, true);
|
renderer.drawSkeleton(skeleton);
|
||||||
|
|
||||||
stateNoMix.update(delta);
|
stateNoMix.update(delta);
|
||||||
stateNoMix.apply(skeletonNoMix);
|
stateNoMix.apply(skeletonNoMix);
|
||||||
skeletonNoMix.updateWorldTransform(spine.Physics.update);
|
skeletonNoMix.updateWorldTransform(spine.Physics.update);
|
||||||
skeletonNoMix.x = size.x + 45;
|
skeletonNoMix.x = size.x + 45;
|
||||||
skeletonNoMix.y = -100;
|
skeletonNoMix.y = -100;
|
||||||
renderer.drawSkeleton(skeletonNoMix, true);
|
renderer.drawSkeleton(skeletonNoMix);
|
||||||
renderer.end();
|
renderer.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -127,7 +127,7 @@ var vineDemo = function (canvas, bgColor) {
|
|||||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
renderer.begin();
|
renderer.begin();
|
||||||
renderer.drawSkeleton(skeleton, true);
|
renderer.drawSkeleton(skeleton);
|
||||||
renderer.drawSkeletonDebug(skeleton);
|
renderer.drawSkeletonDebug(skeleton);
|
||||||
gl.lineWidth(2);
|
gl.lineWidth(2);
|
||||||
for (var i = 0; i < controlBones.length; i++) {
|
for (var i = 0; i < controlBones.length; i++) {
|
||||||
|
|||||||
@ -71,7 +71,7 @@
|
|||||||
// Begin rendering.
|
// Begin rendering.
|
||||||
renderer.begin();
|
renderer.begin();
|
||||||
// Draw the skeleton
|
// Draw the skeleton
|
||||||
renderer.drawSkeleton(this.skeleton, true);
|
renderer.drawSkeleton(this.skeleton);
|
||||||
// Complete rendering.
|
// Complete rendering.
|
||||||
renderer.end();
|
renderer.end();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -71,7 +71,7 @@
|
|||||||
// Begin rendering.
|
// Begin rendering.
|
||||||
renderer.begin();
|
renderer.begin();
|
||||||
// Draw the skeleton
|
// Draw the skeleton
|
||||||
renderer.drawSkeleton(this.skeleton, true);
|
renderer.drawSkeleton(this.skeleton);
|
||||||
// Complete rendering.
|
// Complete rendering.
|
||||||
renderer.end();
|
renderer.end();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -109,7 +109,7 @@
|
|||||||
canvas.clear(0.2, 0.2, 0.2, 1);
|
canvas.clear(0.2, 0.2, 0.2, 1);
|
||||||
|
|
||||||
renderer.begin();
|
renderer.begin();
|
||||||
renderer.drawSkeleton(this.skeleton, true);
|
renderer.drawSkeleton(this.skeleton);
|
||||||
|
|
||||||
// Draw a circle with a radius of 20 pixels around each draggable bone
|
// Draw a circle with a radius of 20 pixels around each draggable bone
|
||||||
let boneColor = new spine.Color(1, 0, 0, 0.5);
|
let boneColor = new spine.Color(1, 0, 0, 0.5);
|
||||||
|
|||||||
@ -87,7 +87,7 @@
|
|||||||
// Begin rendering.
|
// Begin rendering.
|
||||||
renderer.begin();
|
renderer.begin();
|
||||||
// Draw the skeleton
|
// Draw the skeleton
|
||||||
renderer.drawSkeleton(this.skeleton, true);
|
renderer.drawSkeleton(this.skeleton);
|
||||||
// Complete rendering.
|
// Complete rendering.
|
||||||
renderer.end();
|
renderer.end();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,8 +24,6 @@
|
|||||||
<select id="animations"></select>
|
<select id="animations"></select>
|
||||||
<label style="margin-right: 0.5em">Skins</label>
|
<label style="margin-right: 0.5em">Skins</label>
|
||||||
<select id="skins"></select>
|
<select id="skins"></select>
|
||||||
<label>PMA</label>
|
|
||||||
<input type="checkbox" id="pma" checked />
|
|
||||||
</div>
|
</div>
|
||||||
<script src="drag-and-drop.js"></script>
|
<script src="drag-and-drop.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@ -3,7 +3,6 @@ class App {
|
|||||||
this.skeleton = null;
|
this.skeleton = null;
|
||||||
this.animationState = null;
|
this.animationState = null;
|
||||||
this.canvas = null;
|
this.canvas = null;
|
||||||
this.pma = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loadAssets(canvas) {
|
loadAssets(canvas) {
|
||||||
@ -24,12 +23,6 @@ class App {
|
|||||||
this.animationState.setAnimation(0, animationSelectBox.value, true);
|
this.animationState.setAnimation(0, animationSelectBox.value, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup listener for the PMA checkbox
|
|
||||||
let pmaCheckbox = document.body.querySelector("#pma");
|
|
||||||
pmaCheckbox.onchange = () => {
|
|
||||||
this.pma = pmaCheckbox.checked;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup the drag and drop listener
|
// Setup the drag and drop listener
|
||||||
new FileDragAndDrop(canvas.htmlCanvas, (files) => this.onDrop(files))
|
new FileDragAndDrop(canvas.htmlCanvas, (files) => this.onDrop(files))
|
||||||
|
|
||||||
@ -163,7 +156,7 @@ class App {
|
|||||||
renderer.begin();
|
renderer.begin();
|
||||||
renderer.line(-10000, 0, 10000, 0, spine.Color.RED);
|
renderer.line(-10000, 0, 10000, 0, spine.Color.RED);
|
||||||
renderer.line(0, -10000, 0, 10000, spine.Color.GREEN);
|
renderer.line(0, -10000, 0, 10000, spine.Color.GREEN);
|
||||||
renderer.drawSkeleton(this.skeleton, this.pma);
|
renderer.drawSkeleton(this.skeleton);
|
||||||
renderer.end();
|
renderer.end();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -173,7 +173,7 @@
|
|||||||
// Clear the canvas and render the skeleton
|
// Clear the canvas and render the skeleton
|
||||||
canvas.clear(0.5, 0.5, 0.5, 1);
|
canvas.clear(0.5, 0.5, 0.5, 1);
|
||||||
renderer.begin();
|
renderer.begin();
|
||||||
renderer.drawSkeleton(this.skeleton, true);
|
renderer.drawSkeleton(this.skeleton);
|
||||||
renderer.end();
|
renderer.end();
|
||||||
|
|
||||||
// Get the image data and convert it to an img element
|
// Get the image data and convert it to an img element
|
||||||
@ -236,7 +236,7 @@
|
|||||||
|
|
||||||
canvas.clear(0.2, 0.2, 0.2, 1);
|
canvas.clear(0.2, 0.2, 0.2, 1);
|
||||||
renderer.begin();
|
renderer.begin();
|
||||||
renderer.drawSkeleton(this.skeleton, true);
|
renderer.drawSkeleton(this.skeleton);
|
||||||
renderer.end();
|
renderer.end();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -117,36 +117,36 @@
|
|||||||
if (assetManager.isLoadingComplete()) {
|
if (assetManager.isLoadingComplete()) {
|
||||||
skeletons = {
|
skeletons = {
|
||||||
coin: {
|
coin: {
|
||||||
Binary: loadSkeleton("coin-pro.skel", "animation", true),
|
Binary: loadSkeleton("coin-pro.skel", "animation"),
|
||||||
JSON: loadSkeleton("coin-pro.json", "animation", true)
|
JSON: loadSkeleton("coin-pro.json", "animation")
|
||||||
},
|
},
|
||||||
goblins: {
|
goblins: {
|
||||||
Binary: loadSkeleton("goblins-pro.skel", "walk", true, "goblin"),
|
Binary: loadSkeleton("goblins-pro.skel", "walk", "goblin"),
|
||||||
JSON: loadSkeleton("goblins-pro.json", "walk", true, "goblin")
|
JSON: loadSkeleton("goblins-pro.json", "walk", "goblin")
|
||||||
},
|
},
|
||||||
"mix-and-match-pro": {
|
"mix-and-match-pro": {
|
||||||
Binary: loadSkeleton("mix-and-match-pro.skel", "dance", true, "full-skins/girl-blue-cape"),
|
Binary: loadSkeleton("mix-and-match-pro.skel", "dance", "full-skins/girl-blue-cape"),
|
||||||
JSON: loadSkeleton("mix-and-match-pro.json", "dance", true, "full-skins/girl-blue-cape")
|
JSON: loadSkeleton("mix-and-match-pro.json", "dance", "full-skins/girl-blue-cape")
|
||||||
},
|
},
|
||||||
raptor: {
|
raptor: {
|
||||||
Binary: loadSkeleton("raptor-pro.skel", "walk", true),
|
Binary: loadSkeleton("raptor-pro.skel", "walk"),
|
||||||
JSON: loadSkeleton("raptor-pro.json", "walk", true)
|
JSON: loadSkeleton("raptor-pro.json", "walk")
|
||||||
},
|
},
|
||||||
spineboy: {
|
spineboy: {
|
||||||
Binary: loadSkeleton("spineboy-pro.skel", "run", true),
|
Binary: loadSkeleton("spineboy-pro.skel", "run"),
|
||||||
JSON: loadSkeleton("spineboy-pro.json", "run", true)
|
JSON: loadSkeleton("spineboy-pro.json", "run")
|
||||||
},
|
},
|
||||||
stretchyman: {
|
stretchyman: {
|
||||||
Binary: loadSkeleton("stretchyman-pro.skel", "sneak", true),
|
Binary: loadSkeleton("stretchyman-pro.skel", "sneak"),
|
||||||
JSON: loadSkeleton("stretchyman-pro.json", "sneak", true)
|
JSON: loadSkeleton("stretchyman-pro.json", "sneak")
|
||||||
},
|
},
|
||||||
tank: {
|
tank: {
|
||||||
Binary: loadSkeleton("tank-pro.skel", "drive", true),
|
Binary: loadSkeleton("tank-pro.skel", "drive"),
|
||||||
JSON: loadSkeleton("tank-pro.json", "drive", true)
|
JSON: loadSkeleton("tank-pro.json", "drive")
|
||||||
},
|
},
|
||||||
vine: {
|
vine: {
|
||||||
Binary: loadSkeleton("vine-pro.skel", "grow", true),
|
Binary: loadSkeleton("vine-pro.skel", "grow"),
|
||||||
JSON: loadSkeleton("vine-pro.json", "grow", true)
|
JSON: loadSkeleton("vine-pro.json", "grow")
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
setupUI();
|
setupUI();
|
||||||
@ -156,11 +156,11 @@
|
|||||||
requestAnimationFrame(load);
|
requestAnimationFrame(load);
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadSkeleton(name, initialAnimation, premultipliedAlpha, skin) {
|
function loadSkeleton(name, initialAnimation, skin) {
|
||||||
if (skin === undefined) skin = "default";
|
if (skin === undefined) skin = "default";
|
||||||
|
|
||||||
// Load the texture atlas using name.atlas from the AssetManager.
|
// Load the texture atlas using name.atlas from the AssetManager.
|
||||||
let atlas = assetManager.require(name.replace(/(?:-ess|-pro)\.(skel|json)/, "") + (premultipliedAlpha ? "-pma" : "") + ".atlas");
|
let atlas = assetManager.require(name.replace(/(?:-ess|-pro)\.(skel|json)/, "") + "-pma.atlas");
|
||||||
|
|
||||||
// Create an AtlasAttachmentLoader that resolves region, mesh, boundingbox and path attachments
|
// Create an AtlasAttachmentLoader that resolves region, mesh, boundingbox and path attachments
|
||||||
let atlasLoader = new spine.AtlasAttachmentLoader(atlas);
|
let atlasLoader = new spine.AtlasAttachmentLoader(atlas);
|
||||||
@ -219,7 +219,7 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Pack everything up and return to caller.
|
// Pack everything up and return to caller.
|
||||||
return { skeleton: skeleton, state: animationState, bounds: bounds, premultipliedAlpha: premultipliedAlpha };
|
return { skeleton: skeleton, state: animationState, bounds: bounds };
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculateSetupPoseBounds(skeleton) {
|
function calculateSetupPoseBounds(skeleton) {
|
||||||
@ -318,7 +318,6 @@
|
|||||||
let skeleton = skeletons[activeSkeleton][format].skeleton;
|
let skeleton = skeletons[activeSkeleton][format].skeleton;
|
||||||
let state = skeletons[activeSkeleton][format].state;
|
let state = skeletons[activeSkeleton][format].state;
|
||||||
let bounds = skeletons[activeSkeleton][format].bounds;
|
let bounds = skeletons[activeSkeleton][format].bounds;
|
||||||
let premultipliedAlpha = skeletons[activeSkeleton][format].premultipliedAlpha;
|
|
||||||
state.update(delta);
|
state.update(delta);
|
||||||
state.apply(skeleton);
|
state.apply(skeleton);
|
||||||
skeleton.updateWorldTransform(spine.Physics.update);
|
skeleton.updateWorldTransform(spine.Physics.update);
|
||||||
@ -331,7 +330,6 @@
|
|||||||
// Start the batch and tell the SkeletonRenderer to render the active skeleton.
|
// Start the batch and tell the SkeletonRenderer to render the active skeleton.
|
||||||
batcher.begin(shader);
|
batcher.begin(shader);
|
||||||
|
|
||||||
skeletonRenderer.premultipliedAlpha = premultipliedAlpha;
|
|
||||||
skeletonRenderer.draw(batcher, skeleton);
|
skeletonRenderer.draw(batcher, skeleton);
|
||||||
batcher.end();
|
batcher.end();
|
||||||
|
|
||||||
@ -342,7 +340,6 @@
|
|||||||
if (debug) {
|
if (debug) {
|
||||||
debugShader.bind();
|
debugShader.bind();
|
||||||
debugShader.setUniform4x4f(spine.Shader.MVP_MATRIX, mvp.values);
|
debugShader.setUniform4x4f(spine.Shader.MVP_MATRIX, mvp.values);
|
||||||
debugRenderer.premultipliedAlpha = premultipliedAlpha;
|
|
||||||
shapes.begin(debugShader);
|
shapes.begin(debugShader);
|
||||||
debugRenderer.draw(shapes, skeleton);
|
debugRenderer.draw(shapes, skeleton);
|
||||||
shapes.end();
|
shapes.end();
|
||||||
|
|||||||
@ -37,7 +37,7 @@
|
|||||||
|
|
||||||
// Create the skeleton
|
// Create the skeleton
|
||||||
let skeletonBinary = new spine.SkeletonBinary(atlasLoader);
|
let skeletonBinary = new spine.SkeletonBinary(atlasLoader);
|
||||||
skeletonBinary.scale = 0.5;
|
skeletonBinary.scale = 1;
|
||||||
let skeletonData = skeletonBinary.readSkeletonData(assetManager.require("mix-and-match-pro.skel"));
|
let skeletonData = skeletonBinary.readSkeletonData(assetManager.require("mix-and-match-pro.skel"));
|
||||||
this.skeleton = new spine.Skeleton(skeletonData);
|
this.skeleton = new spine.Skeleton(skeletonData);
|
||||||
|
|
||||||
@ -74,7 +74,7 @@
|
|||||||
renderer.resize(spine.ResizeMode.Expand);
|
renderer.resize(spine.ResizeMode.Expand);
|
||||||
canvas.clear(0.2, 0.2, 0.2, 1);
|
canvas.clear(0.2, 0.2, 0.2, 1);
|
||||||
renderer.begin();
|
renderer.begin();
|
||||||
renderer.drawSkeleton(this.skeleton, true);
|
renderer.drawSkeleton(this.skeleton);
|
||||||
renderer.end();
|
renderer.end();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -41,7 +41,7 @@
|
|||||||
var skeletonBinary = new spine.SkeletonBinary(atlasLoader);
|
var skeletonBinary = new spine.SkeletonBinary(atlasLoader);
|
||||||
|
|
||||||
// Set the scale to apply during parsing, parse the file, and create a new skeleton.
|
// Set the scale to apply during parsing, parse the file, and create a new skeleton.
|
||||||
skeletonBinary.scale = 0.2;
|
skeletonBinary.scale = 0.4;
|
||||||
var skeletonData = skeletonBinary.readSkeletonData(assetManager.require("/assets/sack-pro.skel"));
|
var skeletonData = skeletonBinary.readSkeletonData(assetManager.require("/assets/sack-pro.skel"));
|
||||||
this.skeleton = new spine.Skeleton(skeletonData);
|
this.skeleton = new spine.Skeleton(skeletonData);
|
||||||
|
|
||||||
@ -72,7 +72,7 @@
|
|||||||
// Begin rendering.
|
// Begin rendering.
|
||||||
renderer.begin();
|
renderer.begin();
|
||||||
// Draw the skeleton
|
// Draw the skeleton
|
||||||
renderer.drawSkeleton(this.skeleton, true);
|
renderer.drawSkeleton(this.skeleton);
|
||||||
// Complete rendering.
|
// Complete rendering.
|
||||||
renderer.end();
|
renderer.end();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -124,7 +124,7 @@
|
|||||||
// Begin rendering.
|
// Begin rendering.
|
||||||
renderer.begin();
|
renderer.begin();
|
||||||
// Draw the skeleton
|
// Draw the skeleton
|
||||||
renderer.drawSkeleton(this.skeleton, true);
|
renderer.drawSkeleton(this.skeleton);
|
||||||
// Complete rendering.
|
// Complete rendering.
|
||||||
renderer.end();
|
renderer.end();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -42,7 +42,7 @@
|
|||||||
var skeletonBinary = new spine.SkeletonBinary(atlasLoader);
|
var skeletonBinary = new spine.SkeletonBinary(atlasLoader);
|
||||||
|
|
||||||
// Set the scale to apply during parsing, parse the file, and create a new skeleton.
|
// Set the scale to apply during parsing, parse the file, and create a new skeleton.
|
||||||
skeletonBinary.scale = 0.5;
|
skeletonBinary.scale = 0.4;
|
||||||
var skeletonData = skeletonBinary.readSkeletonData(assetManager.require("/assets/snowglobe-pro.skel"));
|
var skeletonData = skeletonBinary.readSkeletonData(assetManager.require("/assets/snowglobe-pro.skel"));
|
||||||
this.skeleton = new spine.Skeleton(skeletonData);
|
this.skeleton = new spine.Skeleton(skeletonData);
|
||||||
|
|
||||||
@ -74,7 +74,7 @@
|
|||||||
// Begin rendering.
|
// Begin rendering.
|
||||||
renderer.begin();
|
renderer.begin();
|
||||||
// Draw the skeleton
|
// Draw the skeleton
|
||||||
renderer.drawSkeleton(this.skeleton, true);
|
renderer.drawSkeleton(this.skeleton);
|
||||||
// Complete rendering.
|
// Complete rendering.
|
||||||
renderer.end();
|
renderer.end();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -73,7 +73,7 @@
|
|||||||
// Begin rendering.
|
// Begin rendering.
|
||||||
renderer.begin();
|
renderer.begin();
|
||||||
// Draw the skeleton
|
// Draw the skeleton
|
||||||
renderer.drawSkeleton(this.skeleton, true);
|
renderer.drawSkeleton(this.skeleton);
|
||||||
// Complete rendering.
|
// Complete rendering.
|
||||||
renderer.end();
|
renderer.end();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,8 +33,10 @@ import type { ManagedWebGLRenderingContext } from "./WebGL.js";
|
|||||||
|
|
||||||
export class AssetManager extends AssetManagerBase {
|
export class AssetManager extends AssetManagerBase {
|
||||||
constructor (context: ManagedWebGLRenderingContext | WebGLRenderingContext, pathPrefix: string = "", downloader: Downloader = new Downloader()) {
|
constructor (context: ManagedWebGLRenderingContext | WebGLRenderingContext, pathPrefix: string = "", downloader: Downloader = new Downloader()) {
|
||||||
super((image: HTMLImageElement | ImageBitmap) => {
|
super(
|
||||||
return new GLTexture(context, image);
|
(image: HTMLImageElement | ImageBitmap, pma = false) => new GLTexture(context, image, pma),
|
||||||
}, pathPrefix, downloader);
|
pathPrefix,
|
||||||
|
downloader,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,13 +34,13 @@ export class GLTexture extends Texture implements Disposable, Restorable {
|
|||||||
context: ManagedWebGLRenderingContext;
|
context: ManagedWebGLRenderingContext;
|
||||||
private texture: WebGLTexture | null = null;
|
private texture: WebGLTexture | null = null;
|
||||||
private boundUnit = 0;
|
private boundUnit = 0;
|
||||||
private useMipMaps = false;
|
private pma: boolean;
|
||||||
|
private useMipMaps: boolean;
|
||||||
|
|
||||||
public static DISABLE_UNPACK_PREMULTIPLIED_ALPHA_WEBGL = false;
|
constructor (context: ManagedWebGLRenderingContext | WebGLRenderingContext, image: HTMLImageElement | ImageBitmap, pma: boolean, useMipMaps: boolean = false) {
|
||||||
|
|
||||||
constructor (context: ManagedWebGLRenderingContext | WebGLRenderingContext, image: HTMLImageElement | ImageBitmap, useMipMaps: boolean = false) {
|
|
||||||
super(image);
|
super(image);
|
||||||
this.context = context instanceof ManagedWebGLRenderingContext ? context : new ManagedWebGLRenderingContext(context);
|
this.context = context instanceof ManagedWebGLRenderingContext ? context : new ManagedWebGLRenderingContext(context);
|
||||||
|
this.pma = pma;
|
||||||
this.useMipMaps = useMipMaps;
|
this.useMipMaps = useMipMaps;
|
||||||
this.restore();
|
this.restore();
|
||||||
this.context.addRestorable(this);
|
this.context.addRestorable(this);
|
||||||
@ -90,13 +90,15 @@ export class GLTexture extends Texture implements Disposable, Restorable {
|
|||||||
const gl = this.context.gl;
|
const gl = this.context.gl;
|
||||||
if (!this.texture) this.texture = this.context.gl.createTexture();
|
if (!this.texture) this.texture = this.context.gl.createTexture();
|
||||||
this.bind();
|
this.bind();
|
||||||
if (GLTexture.DISABLE_UNPACK_PREMULTIPLIED_ALPHA_WEBGL) gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
|
const previousUnpackPmaValue = gl.getParameter(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL);
|
||||||
|
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, !this.pma);
|
||||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this._image);
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this._image);
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, useMipMaps ? gl.LINEAR_MIPMAP_LINEAR : gl.LINEAR);
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, useMipMaps ? gl.LINEAR_MIPMAP_LINEAR : gl.LINEAR);
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
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);
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||||
if (useMipMaps) gl.generateMipmap(gl.TEXTURE_2D);
|
if (useMipMaps) gl.generateMipmap(gl.TEXTURE_2D);
|
||||||
|
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, previousUnpackPmaValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
restore () {
|
restore () {
|
||||||
|
|||||||
@ -88,7 +88,7 @@ export class LoadingScreen implements Disposable {
|
|||||||
|
|
||||||
renderer.resize(ResizeMode.Expand);
|
renderer.resize(ResizeMode.Expand);
|
||||||
renderer.camera.position.set(canvas.width / 2, canvas.height / 2, 0);
|
renderer.camera.position.set(canvas.width / 2, canvas.height / 2, 0);
|
||||||
renderer.batcher.setBlendMode(BlendMode.Normal, true);
|
renderer.batcher.setBlendMode(BlendMode.Normal);
|
||||||
|
|
||||||
if (complete) {
|
if (complete) {
|
||||||
this.fadeOut += this.timeKeeper.delta * (this.timeKeeper.totalTime < 1 ? 2 : 1);
|
this.fadeOut += this.timeKeeper.delta * (this.timeKeeper.totalTime < 1 ? 2 : 1);
|
||||||
@ -116,8 +116,8 @@ export class LoadingScreen implements Disposable {
|
|||||||
tempColor.set(a, a, a, a);
|
tempColor.set(a, a, a, a);
|
||||||
|
|
||||||
if (!this.logo) {
|
if (!this.logo) {
|
||||||
this.logo = new GLTexture(renderer.context, logoImage);
|
this.logo = new GLTexture(renderer.context, logoImage, true);
|
||||||
this.spinner = new GLTexture(renderer.context, spinnerImage);
|
this.spinner = new GLTexture(renderer.context, spinnerImage, true);
|
||||||
}
|
}
|
||||||
renderer.camera.zoom = Math.max(1, spinnerSize / canvas.height);
|
renderer.camera.zoom = Math.max(1, spinnerSize / canvas.height);
|
||||||
renderer.begin();
|
renderer.begin();
|
||||||
@ -131,11 +131,11 @@ export class LoadingScreen implements Disposable {
|
|||||||
|
|
||||||
this.timeKeeper.update();
|
this.timeKeeper.update();
|
||||||
const renderer = this.renderer;
|
const renderer = this.renderer;
|
||||||
renderer.batcher.setBlendMode(BlendMode.Normal, true);
|
renderer.batcher.setBlendMode(BlendMode.Normal);
|
||||||
|
|
||||||
if (!this.logo) {
|
if (!this.logo) {
|
||||||
this.logo = new GLTexture(renderer.context, logoImage);
|
this.logo = new GLTexture(renderer.context, logoImage, true);
|
||||||
this.spinner = new GLTexture(renderer.context, spinnerImage);
|
this.spinner = new GLTexture(renderer.context, spinnerImage, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
const shiftedX = x - logoWidth / 2;
|
const shiftedX = x - logoWidth / 2;
|
||||||
|
|||||||
@ -35,10 +35,7 @@ import { ManagedWebGLRenderingContext } from "./WebGL.js";
|
|||||||
|
|
||||||
const GL_ONE = 1;
|
const GL_ONE = 1;
|
||||||
const GL_ONE_MINUS_SRC_COLOR = 0x0301;
|
const GL_ONE_MINUS_SRC_COLOR = 0x0301;
|
||||||
const GL_SRC_ALPHA = 0x0302;
|
|
||||||
const GL_ONE_MINUS_SRC_ALPHA = 0x0303;
|
const GL_ONE_MINUS_SRC_ALPHA = 0x0303;
|
||||||
// biome-ignore lint/correctness/noUnusedVariables: intentional
|
|
||||||
const GL_ONE_MINUS_DST_ALPHA = 0x0305;
|
|
||||||
const GL_DST_COLOR = 0x0306;
|
const GL_DST_COLOR = 0x0306;
|
||||||
|
|
||||||
export class PolygonBatcher implements Disposable {
|
export class PolygonBatcher implements Disposable {
|
||||||
@ -88,16 +85,16 @@ export class PolygonBatcher implements Disposable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static blendModesGL: { srcRgb: number, srcRgbPma: number, dstRgb: number, srcAlpha: number }[] = [
|
private static blendModesGL: { srcRgbPma: number, dstRgb: number, srcAlpha: number }[] = [
|
||||||
{ srcRgb: GL_SRC_ALPHA, srcRgbPma: GL_ONE, dstRgb: GL_ONE_MINUS_SRC_ALPHA, srcAlpha: GL_ONE },
|
{ srcRgbPma: GL_ONE, dstRgb: GL_ONE_MINUS_SRC_ALPHA, srcAlpha: GL_ONE },
|
||||||
{ srcRgb: GL_SRC_ALPHA, srcRgbPma: GL_ONE, dstRgb: GL_ONE, srcAlpha: GL_ONE },
|
{ srcRgbPma: GL_ONE, dstRgb: GL_ONE, srcAlpha: GL_ONE },
|
||||||
{ srcRgb: GL_DST_COLOR, srcRgbPma: GL_DST_COLOR, dstRgb: GL_ONE_MINUS_SRC_ALPHA, srcAlpha: GL_ONE },
|
{ srcRgbPma: GL_DST_COLOR, dstRgb: GL_ONE_MINUS_SRC_ALPHA, srcAlpha: GL_ONE },
|
||||||
{ srcRgb: GL_ONE, srcRgbPma: GL_ONE, dstRgb: GL_ONE_MINUS_SRC_COLOR, srcAlpha: GL_ONE }
|
{ srcRgbPma: GL_ONE, dstRgb: GL_ONE_MINUS_SRC_COLOR, srcAlpha: GL_ONE }
|
||||||
]
|
]
|
||||||
|
|
||||||
setBlendMode (blendMode: BlendMode, premultipliedAlpha: boolean) {
|
setBlendMode (blendMode: BlendMode) {
|
||||||
const blendModeGL = PolygonBatcher.blendModesGL[blendMode];
|
const blendModeGL = PolygonBatcher.blendModesGL[blendMode];
|
||||||
const srcColorBlend = premultipliedAlpha ? blendModeGL.srcRgbPma : blendModeGL.srcRgb;
|
const srcColorBlend = blendModeGL.srcRgbPma;
|
||||||
const srcAlphaBlend = blendModeGL.srcAlpha;
|
const srcAlphaBlend = blendModeGL.srcAlpha;
|
||||||
const dstBlend = blendModeGL.dstRgb;
|
const dstBlend = blendModeGL.dstRgb;
|
||||||
|
|
||||||
|
|||||||
@ -87,15 +87,13 @@ export class SceneRenderer implements Disposable {
|
|||||||
this.enableRenderer(this.batcher);
|
this.enableRenderer(this.batcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
drawSkeleton (skeleton: Skeleton, premultipliedAlpha = false, slotRangeStart = -1, slotRangeEnd = -1, transform: VertexTransformer | null = null) {
|
drawSkeleton (skeleton: Skeleton, slotRangeStart = -1, slotRangeEnd = -1, transform: VertexTransformer | null = null) {
|
||||||
this.enableRenderer(this.batcher);
|
this.enableRenderer(this.batcher);
|
||||||
this.skeletonRenderer.premultipliedAlpha = premultipliedAlpha;
|
|
||||||
this.skeletonRenderer.draw(this.batcher, skeleton, slotRangeStart, slotRangeEnd, transform);
|
this.skeletonRenderer.draw(this.batcher, skeleton, slotRangeStart, slotRangeEnd, transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
drawSkeletonDebug (skeleton: Skeleton, premultipliedAlpha = false, ignoredBones?: Array<string>) {
|
drawSkeletonDebug (skeleton: Skeleton, ignoredBones?: Array<string>) {
|
||||||
this.enableRenderer(this.shapes);
|
this.enableRenderer(this.shapes);
|
||||||
this.skeletonDebugRenderer.premultipliedAlpha = premultipliedAlpha;
|
|
||||||
this.skeletonDebugRenderer.draw(this.shapes, skeleton, ignoredBones);
|
this.skeletonDebugRenderer.draw(this.shapes, skeleton, ignoredBones);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -47,7 +47,6 @@ export class SkeletonDebugRenderer implements Disposable {
|
|||||||
drawPaths = true;
|
drawPaths = true;
|
||||||
drawSkeletonXY = false;
|
drawSkeletonXY = false;
|
||||||
drawClipping = true;
|
drawClipping = true;
|
||||||
premultipliedAlpha = false;
|
|
||||||
scale = 1;
|
scale = 1;
|
||||||
boneWidth = 2;
|
boneWidth = 2;
|
||||||
|
|
||||||
@ -66,8 +65,7 @@ export class SkeletonDebugRenderer implements Disposable {
|
|||||||
const skeletonX = skeleton.x;
|
const skeletonX = skeleton.x;
|
||||||
const skeletonY = skeleton.y;
|
const skeletonY = skeleton.y;
|
||||||
const gl = this.context.gl;
|
const gl = this.context.gl;
|
||||||
const srcFunc = this.premultipliedAlpha ? gl.ONE : gl.SRC_ALPHA;
|
shapes.setBlendMode(gl.ONE, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
||||||
shapes.setBlendMode(srcFunc, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
||||||
|
|
||||||
const bones = skeleton.bones;
|
const bones = skeleton.bones;
|
||||||
if (this.drawBones) {
|
if (this.drawBones) {
|
||||||
|
|||||||
@ -42,7 +42,6 @@ export type VertexTransformer = (vertices: NumberArrayLike, numVertices: number,
|
|||||||
export class SkeletonRenderer {
|
export class SkeletonRenderer {
|
||||||
static QUAD_TRIANGLES = [0, 1, 2, 2, 3, 0];
|
static QUAD_TRIANGLES = [0, 1, 2, 2, 3, 0];
|
||||||
|
|
||||||
premultipliedAlpha = false;
|
|
||||||
private tempColor = new Color();
|
private tempColor = new Color();
|
||||||
private tempColor2 = new Color();
|
private tempColor2 = new Color();
|
||||||
private vertices: NumberArrayLike;
|
private vertices: NumberArrayLike;
|
||||||
@ -64,7 +63,6 @@ export class SkeletonRenderer {
|
|||||||
|
|
||||||
draw (batcher: PolygonBatcher, skeleton: Skeleton, slotRangeStart: number = -1, slotRangeEnd: number = -1, transformer: VertexTransformer | null = null) {
|
draw (batcher: PolygonBatcher, skeleton: Skeleton, slotRangeStart: number = -1, slotRangeEnd: number = -1, transformer: VertexTransformer | null = null) {
|
||||||
const clipper = this.clipper;
|
const clipper = this.clipper;
|
||||||
const premultipliedAlpha = this.premultipliedAlpha;
|
|
||||||
const twoColorTint = this.twoColorTint;
|
const twoColorTint = this.twoColorTint;
|
||||||
let blendMode: BlendMode | null = null;
|
let blendMode: BlendMode | null = null;
|
||||||
|
|
||||||
@ -134,33 +132,25 @@ export class SkeletonRenderer {
|
|||||||
if (texture) {
|
if (texture) {
|
||||||
const slotColor = pose.color;
|
const slotColor = pose.color;
|
||||||
const finalColor = this.tempColor;
|
const finalColor = this.tempColor;
|
||||||
finalColor.r = skeletonColor.r * slotColor.r * attachmentColor.r;
|
const alpha = skeletonColor.a * slotColor.a * attachmentColor.a;
|
||||||
finalColor.g = skeletonColor.g * slotColor.g * attachmentColor.g;
|
finalColor.r = skeletonColor.r * slotColor.r * attachmentColor.r * alpha;
|
||||||
finalColor.b = skeletonColor.b * slotColor.b * attachmentColor.b;
|
finalColor.g = skeletonColor.g * slotColor.g * attachmentColor.g * alpha;
|
||||||
finalColor.a = skeletonColor.a * slotColor.a * attachmentColor.a;
|
finalColor.b = skeletonColor.b * slotColor.b * attachmentColor.b * alpha;
|
||||||
if (premultipliedAlpha) {
|
finalColor.a = alpha;
|
||||||
finalColor.r *= finalColor.a;
|
|
||||||
finalColor.g *= finalColor.a;
|
|
||||||
finalColor.b *= finalColor.a;
|
|
||||||
}
|
|
||||||
const darkColor = this.tempColor2;
|
const darkColor = this.tempColor2;
|
||||||
if (!pose.darkColor)
|
if (!pose.darkColor)
|
||||||
darkColor.set(0, 0, 0, 1.0);
|
darkColor.set(0, 0, 0, 1.0);
|
||||||
else {
|
else {
|
||||||
if (premultipliedAlpha) {
|
darkColor.r = pose.darkColor.r * alpha;
|
||||||
darkColor.r = pose.darkColor.r * finalColor.a;
|
darkColor.g = pose.darkColor.g * alpha;
|
||||||
darkColor.g = pose.darkColor.g * finalColor.a;
|
darkColor.b = pose.darkColor.b * alpha;
|
||||||
darkColor.b = pose.darkColor.b * finalColor.a;
|
darkColor.a = 1;
|
||||||
} else {
|
|
||||||
darkColor.setFromColor(pose.darkColor);
|
|
||||||
}
|
|
||||||
darkColor.a = premultipliedAlpha ? 1.0 : 0.0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const slotBlendMode = slot.data.blendMode;
|
const slotBlendMode = slot.data.blendMode;
|
||||||
if (slotBlendMode !== blendMode) {
|
if (slotBlendMode !== blendMode) {
|
||||||
blendMode = slotBlendMode;
|
blendMode = slotBlendMode;
|
||||||
batcher.setBlendMode(blendMode, premultipliedAlpha);
|
batcher.setBlendMode(blendMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clipper.isClipping() && clipper.clipTriangles(renderable.vertices, triangles, triangles.length, uvs, finalColor, darkColor, twoColorTint, vertexSize)) {
|
if (clipper.isClipping() && clipper.clipTriangles(renderable.vertices, triangles, triangles.length, uvs, finalColor, darkColor, twoColorTint, vertexSize)) {
|
||||||
@ -175,7 +165,7 @@ export class SkeletonRenderer {
|
|||||||
verts[v] = finalColor.r;
|
verts[v] = finalColor.r;
|
||||||
verts[v + 1] = finalColor.g;
|
verts[v + 1] = finalColor.g;
|
||||||
verts[v + 2] = finalColor.b;
|
verts[v + 2] = finalColor.b;
|
||||||
verts[v + 3] = finalColor.a;
|
verts[v + 3] = alpha;
|
||||||
verts[v + 4] = uvs[u];
|
verts[v + 4] = uvs[u];
|
||||||
verts[v + 5] = uvs[u + 1];
|
verts[v + 5] = uvs[u + 1];
|
||||||
}
|
}
|
||||||
@ -184,7 +174,7 @@ export class SkeletonRenderer {
|
|||||||
verts[v] = finalColor.r;
|
verts[v] = finalColor.r;
|
||||||
verts[v + 1] = finalColor.g;
|
verts[v + 1] = finalColor.g;
|
||||||
verts[v + 2] = finalColor.b;
|
verts[v + 2] = finalColor.b;
|
||||||
verts[v + 3] = finalColor.a;
|
verts[v + 3] = alpha;
|
||||||
verts[v + 4] = uvs[u];
|
verts[v + 4] = uvs[u];
|
||||||
verts[v + 5] = uvs[u + 1];
|
verts[v + 5] = uvs[u + 1];
|
||||||
verts[v + 6] = darkColor.r;
|
verts[v + 6] = darkColor.r;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user