Add pma, wraps and filter management.

This commit is contained in:
Davide Tantillo 2025-08-18 18:21:23 +02:00
parent 235a96ca5c
commit 75f30e297d
4 changed files with 94 additions and 18 deletions

View File

@ -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<ImageBitmap | null> {
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);

View File

@ -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, BlendModeParameter> = {
[BlendMode.Normal]: "normal",
[BlendMode.Additive]: "additive",

View File

@ -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),
);

View File

@ -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)),
);