From 75f30e297dfe8499416fe71a472d33ae5e7ffc5c Mon Sep 17 00:00:00 2001 From: Davide Tantillo Date: Mon, 18 Aug 2025 18:21:23 +0200 Subject: [PATCH] Add pma, wraps and filter management. --- .../spine-construct3-lib/src/AssetLoader.ts | 5 +- .../spine-construct3-lib/src/C3Texture.ts | 83 ++++++++++++++++--- .../src/c3runtime/instance.ts | 22 ++++- spine-ts/spine-construct3/src/instance.ts | 2 +- 4 files changed, 94 insertions(+), 18 deletions(-) diff --git a/spine-ts/spine-construct3-lib/src/AssetLoader.ts b/spine-ts/spine-construct3-lib/src/AssetLoader.ts index 6b5e36da4..474fd2ecb 100644 --- a/spine-ts/spine-construct3-lib/src/AssetLoader.ts +++ b/spine-ts/spine-construct3-lib/src/AssetLoader.ts @@ -65,7 +65,7 @@ export class AssetLoader { await Promise.all(textureAtlas.pages.map(async page => { const texture = await this.loadSpineTextureEditor(page.name, page.pma, instance); if (texture) { - const spineTexture = new C3TextureEditor(texture, renderer); + const spineTexture = new C3TextureEditor(texture, renderer, page); page.setTexture(spineTexture); } return texture; @@ -116,7 +116,7 @@ export class AssetLoader { await Promise.all(textureAtlas.pages.map(async page => { const texture = await this.loadSpineTextureRuntime(page.name, page.pma, instance); if (texture) { - const spineTexture = new C3Texture(texture, renderer); + const spineTexture = new C3Texture(texture, renderer, page); page.setTexture(spineTexture); } return texture; @@ -136,6 +136,7 @@ export class AssetLoader { static async createImageBitmapFromBlob (blob: Blob, pma: boolean): Promise { try { + // pma parameters seems to do not matter here. It matters in C3 Texture creation return createImageBitmap(blob, { premultiplyAlpha: pma ? "none" : "premultiply" }); } catch (e) { console.error("Failed to create ImageBitmap from blob:", e); diff --git a/spine-ts/spine-construct3-lib/src/C3Texture.ts b/spine-ts/spine-construct3-lib/src/C3Texture.ts index 878d8cc4f..7a6d5e4dc 100644 --- a/spine-ts/spine-construct3-lib/src/C3Texture.ts +++ b/spine-ts/spine-construct3-lib/src/C3Texture.ts @@ -27,25 +27,34 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -import { BlendMode, Texture, type TextureFilter, type TextureWrap } from "@esotericsoftware/spine-core"; +import { BlendMode, Texture, type TextureAtlasPage, TextureFilter, TextureWrap } from "@esotericsoftware/spine-core"; export class C3TextureEditor extends Texture { texture: SDK.Gfx.IWebGLTexture; renderer: SDK.Gfx.IWebGLRenderer; - constructor (image: HTMLImageElement | ImageBitmap, renderer: SDK.Gfx.IWebGLRenderer) { + constructor (image: HTMLImageElement | ImageBitmap, renderer: SDK.Gfx.IWebGLRenderer, page: TextureAtlasPage) { super(image); this.renderer = renderer; - this.texture = renderer.CreateDynamicTexture(image.width, image.height); - this.renderer.UpdateTexture(image, this.texture); + const options: TextureCreateOptions = { + wrapX: toC3TextureWrap(page.uWrap), + wrapY: toC3TextureWrap(page.vWrap), + sampling: toC3Filter(page.minFilter), + mipMap: toC3MipMap(page.minFilter), + } + this.texture = renderer.CreateDynamicTexture(image.width, image.height, options); + this.renderer.UpdateTexture(image, this.texture, { premultiplyAlpha: !page.pma }); } - setFilters (minFilter: TextureFilter, magFilter: TextureFilter) { + setFilters () { + // cannot change filter after texture creation } - setWraps (uWrap: TextureWrap, vWrap: TextureWrap) { + setWraps () { + // cannot change wraps after texture creation } + dispose () { this.renderer.DeleteTexture(this.texture); } @@ -55,17 +64,26 @@ export class C3Texture extends Texture { texture: ITexture; renderer: IRenderer; - constructor (image: HTMLImageElement | ImageBitmap, renderer: IRenderer) { + constructor (image: HTMLImageElement | ImageBitmap, renderer: IRenderer, page: TextureAtlasPage) { super(image); this.renderer = renderer; - this.texture = renderer.createDynamicTexture(image.width, image.height); - this.renderer.updateTexture(image, this.texture); + const options: TextureCreateOptions = { + wrapX: toC3TextureWrap(page.uWrap), + wrapY: toC3TextureWrap(page.vWrap), + sampling: toC3Filter(page.minFilter), + mipMap: toC3MipMap(page.minFilter), + } + this.texture = renderer.createDynamicTexture(image.width, image.height, options); + this.renderer.updateTexture(image, this.texture, { premultiplyAlpha: !page.pma }); } - setFilters (minFilter: TextureFilter, magFilter: TextureFilter) { + + setFilters () { + // cannot change filter after texture creation } - setWraps (uWrap: TextureWrap, vWrap: TextureWrap) { + setWraps () { + // cannot change wraps after texture creation } dispose () { @@ -73,6 +91,49 @@ export class C3Texture extends Texture { } } +function toC3TextureWrap (wrap: TextureWrap): TextureWrapMode { + if (wrap === TextureWrap.ClampToEdge) return "clamp-to-edge"; + else if (wrap === TextureWrap.MirroredRepeat) return "mirror-repeat"; + else if (wrap === TextureWrap.Repeat) return "repeat"; + else throw new Error(`Unknown texture wrap: ${wrap}`); +} + +function toC3MipMap (filter: TextureFilter): boolean { + switch (filter) { + case TextureFilter.MipMap: + case TextureFilter.MipMapLinearNearest: + case TextureFilter.MipMapNearestLinear: + case TextureFilter.MipMapNearestNearest: + return true; + + case TextureFilter.Linear: + case TextureFilter.Nearest: + return false; + + default: + throw new Error(`Unknown texture filter: ${filter}`); + } +} + +function toC3Filter (filter: TextureFilter): TextureSamplingMode { + switch (filter) { + case TextureFilter.Nearest: + case TextureFilter.MipMapNearestNearest: + return "nearest"; + + case TextureFilter.Linear: + case TextureFilter.MipMapLinearNearest: + case TextureFilter.MipMapNearestLinear: + return "bilinear"; + + case TextureFilter.MipMap: + case TextureFilter.MipMapLinearLinear: + return "trilinear"; + default: + throw new Error(`Unknown texture filter: ${filter}`); + } +} + export const BlendingModeSpineToC3: Record = { [BlendMode.Normal]: "normal", [BlendMode.Additive]: "additive", diff --git a/spine-ts/spine-construct3/src/c3runtime/instance.ts b/spine-ts/spine-construct3/src/c3runtime/instance.ts index 9699ebeef..812459b19 100644 --- a/spine-ts/spine-construct3/src/c3runtime/instance.ts +++ b/spine-ts/spine-construct3/src/c3runtime/instance.ts @@ -71,8 +71,10 @@ class DrawingInstance extends globalThis.ISDKWorldInstanceBase { return; } + // workaround to request a redraw: https://github.com/Scirra/Construct-feature-requests/issues/615 this.x++; this.x--; + this.update(this.dt); } @@ -224,8 +226,12 @@ class DrawingInstance extends globalThis.ISDKWorldInstanceBase { const offsetY = this.y + this.propOffsetY; const offsetAngle = this.angle + this.propOffsetAngle; - const cos = Math.cos(offsetAngle); - const sin = Math.sin(offsetAngle); + let cos = 0; + let sin = 0; + if (offsetAngle) { + cos = Math.cos(offsetAngle); + sin = Math.sin(offsetAngle); + } while (command) { const { numVertices, positions, uvs, colors, indices, numIndices, blendMode } = command; @@ -236,10 +242,17 @@ class DrawingInstance extends globalThis.ISDKWorldInstanceBase { const dstIndex = i * 3; const x = positions[srcIndex]; const y = positions[srcIndex + 1]; - vertices[dstIndex] = x * cos - y * sin + offsetX; - vertices[dstIndex + 1] = x * sin + y * cos + offsetY; + + if (offsetAngle) { + vertices[dstIndex] = x * cos - y * sin + offsetX; + vertices[dstIndex + 1] = x * sin + y * cos + offsetY; + } else { + vertices[dstIndex] = x + offsetX; + vertices[dstIndex + 1] = y + offsetY; + } vertices[dstIndex + 2] = 0; + // there's something wrong with the hand after adding the colors on spineboy portal animation const color = colors[i]; const colorDst = i * 4; @@ -254,6 +267,7 @@ class DrawingInstance extends globalThis.ISDKWorldInstanceBase { renderer.drawMesh( vertices.subarray(0, numVertices * 3), uvs.subarray(0, numVertices * 2), + // workaround for this bug: https://github.com/Scirra/Construct-bugs/issues/8746 this.padUint16ArrayForWebGPU(indices.subarray(0, numIndices)), c3colors.subarray(0, numVertices * 4), ); diff --git a/spine-ts/spine-construct3/src/instance.ts b/spine-ts/spine-construct3/src/instance.ts index 484f73c17..3096d6cc6 100644 --- a/spine-ts/spine-construct3/src/instance.ts +++ b/spine-ts/spine-construct3/src/instance.ts @@ -136,10 +136,10 @@ class MyDrawingInstance extends SDK.IWorldInstanceBase { iRenderer.SetTextureFillMode(); iRenderer.SetTexture(command.texture.texture); - const padded = this.padUint16ArrayForWebGPU(indices.subarray(0, numIndices)); iRenderer.DrawMesh( vertices.subarray(0, numVertices * 3), uvs.subarray(0, numVertices * 2), + // workaround for this bug: https://github.com/Scirra/Construct-bugs/issues/8746 this.padUint16ArrayForWebGPU(indices.subarray(0, numIndices)), );