Skip to content

Commit 2387cd2

Browse files
committed
[ts][pixi] Set blend modes. Pass alpha for dark color.
1 parent 7274ed2 commit 2387cd2

File tree

10 files changed

+184
-35
lines changed

10 files changed

+184
-35
lines changed

spine-ts/spine-pixi/example/darktint.html

+3-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929

3030
document.body.appendChild(app.view);
3131

32+
// console.log(app.renderer.state.blendModes)
33+
3234
const orange = "-pma";
3335
// const orange = "";
3436
PIXI.Assets.add("spineboyData", `./assets/orange${orange}.json`);
@@ -53,7 +55,7 @@
5355

5456
// Set animation "run" on track 0, looped.
5557
spineboy.state.setAnimation(0, "animation", true);
56-
spineboy.update(0.66);
58+
spineboy.update(0.01);
5759

5860
// Add the display object to the stage.
5961
app.stage.addChild(spineboy);

spine-ts/spine-pixi/example/manual-loading.html

+4-2
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@
2222
});
2323
document.body.appendChild(app.view);
2424

25+
const pma = true;
26+
// const pma = false;
2527
// Pre-load the skeleton data and atlas. You can also load .json skeleton data.
2628
PIXI.Assets.add("spineboyData", "./assets/spineboy-pro.skel");
27-
PIXI.Assets.add("spineboyAtlas", "./assets/spineboy-pma.atlas");
29+
PIXI.Assets.add("spineboyAtlas", `./assets/spineboy${pma ? "-pma" : ""}.atlas`);
2830
await PIXI.Assets.load(["spineboyData", "spineboyAtlas"]);
2931

3032
// Manually load the data and create a Spine display object from it using
@@ -37,7 +39,7 @@
3739
const skeletonData = binaryLoader.readSkeletonData(
3840
PIXI.Assets.get("spineboyData")
3941
);
40-
const spineboy = new spine.Spine(skeletonData);
42+
const spineboy = new spine.Spine(skeletonData, { pma });
4143

4244
// Set the default mix time to use when transitioning
4345
// from one animation to the next.

spine-ts/spine-pixi/example/typescript/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Application, Assets } from 'pixi.js';
1+
import { Application, Assets, Renderer } from 'pixi.js';
22
import { Spine } from '@esotericsoftware/spine-pixi';
33

44
/** The PixiJS app Application instance, shared across the project */

spine-ts/spine-pixi/src/DarkSlotMesh.ts

+58-3
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,22 @@
2828
*****************************************************************************/
2929

3030
import { SpineTexture } from "./SpineTexture.js";
31-
import type { BlendMode, NumberArrayLike } from "@esotericsoftware/spine-core";
31+
import type { NumberArrayLike } from "@esotericsoftware/spine-core";
32+
import { BlendMode } from "@esotericsoftware/spine-core";
3233
import { DarkTintMesh } from "./darkTintMesh/DarkTintMesh.js";
3334
import type { ISlotMesh } from "./Spine.js";
35+
import { Renderer } from "@pixi/core";
3436

3537
export class DarkSlotMesh extends DarkTintMesh implements ISlotMesh {
3638
public name: string = "";
39+
public pma: boolean = false;
40+
private blendModeInternal: BlendMode = BlendMode.Normal;
3741

3842
private static auxColor = [0, 0, 0, 0];
3943

40-
constructor () {
44+
constructor (pma = false) {
4145
super();
46+
this.pma = pma;
4247
}
4348
public updateFromSpineData (
4449
slotTexture: SpineTexture,
@@ -99,7 +104,8 @@ export class DarkSlotMesh extends DarkTintMesh implements ISlotMesh {
99104
this.tint = DarkSlotMesh.auxColor;
100105
}
101106
this.alpha = DarkSlotMesh.auxColor[3];
102-
this.blendMode = SpineTexture.toPixiBlending(slotBlendMode);
107+
this.blendModeInternal = slotBlendMode;
108+
// this.blendModeInternal = 0;
103109

104110
if (this.geometry.indexBuffer.data.length !== finalIndices.length) {
105111
this.geometry.indexBuffer.data = new Uint32Array(finalIndices);
@@ -115,4 +121,53 @@ export class DarkSlotMesh extends DarkTintMesh implements ISlotMesh {
115121
this.geometry.getBuffer("aTextureCoord").update();
116122
this.geometry.indexBuffer.update();
117123
}
124+
125+
// 1,2 -> 1
126+
// 3 -> 2,4
127+
// 4 -> 3
128+
129+
// { srcRgb: GL_SRC_ALPHA, srcRgbPma: GL_ONE, dstRgb: GL_ONE_MINUS_SRC_ALPHA, srcAlpha: GL_ONE },
130+
// { srcRgb: GL_SRC_ALPHA, srcRgbPma: GL_ONE, dstRgb: GL_ONE, srcAlpha: GL_ONE },
131+
// { srcRgb: GL_DST_COLOR, srcRgbPma: GL_DST_COLOR, dstRgb: GL_ONE_MINUS_SRC_ALPHA, srcAlpha: GL_ONE },
132+
// { srcRgb: GL_ONE, srcRgbPma: GL_ONE, dstRgb: GL_ONE_MINUS_SRC_COLOR, srcAlpha: GL_ONE }
133+
134+
protected override _render(renderer: Renderer): void {
135+
const pma_normal = [renderer.gl.ONE, renderer.gl.ONE_MINUS_SRC_ALPHA, renderer.gl.ONE, renderer.gl.ONE_MINUS_SRC_ALPHA];
136+
const pma_additive = [renderer.gl.ONE, renderer.gl.ONE, renderer.gl.ONE, renderer.gl.ONE];
137+
const pma_multiply = [renderer.gl.DST_COLOR, renderer.gl.ONE_MINUS_SRC_ALPHA, renderer.gl.ONE, renderer.gl.ONE_MINUS_SRC_ALPHA];
138+
const pma_screen = [renderer.gl.ONE, renderer.gl.ONE_MINUS_SRC_COLOR, renderer.gl.ONE, renderer.gl.ONE_MINUS_SRC_COLOR];
139+
140+
const normal = [renderer.gl.SRC_ALPHA, renderer.gl.ONE_MINUS_SRC_ALPHA, renderer.gl.ONE, renderer.gl.ONE_MINUS_SRC_ALPHA];
141+
const additive = [renderer.gl.SRC_ALPHA, renderer.gl.ONE, renderer.gl.ONE, renderer.gl.ONE];
142+
const multiply = [renderer.gl.DST_COLOR, renderer.gl.ONE_MINUS_SRC_ALPHA, renderer.gl.ONE, renderer.gl.ONE_MINUS_SRC_ALPHA];
143+
const screen = [renderer.gl.ONE, renderer.gl.ONE_MINUS_SRC_COLOR, renderer.gl.ONE, renderer.gl.ONE_MINUS_SRC_COLOR];
144+
let blend;
145+
146+
switch (this.blendModeInternal) {
147+
case BlendMode.Normal:
148+
blend = this.pma ? pma_normal : normal;
149+
break;
150+
case BlendMode.Additive:
151+
blend = this.pma ? pma_additive : additive;
152+
break;
153+
case BlendMode.Multiply:
154+
blend = this.pma ? pma_multiply : multiply;
155+
break;
156+
case BlendMode.Screen:
157+
blend = this.pma ? pma_screen : screen;
158+
}
159+
160+
if (
161+
renderer.gl.getParameter(renderer.gl.BLEND_SRC_RGB) !== blend[0] ||
162+
renderer.gl.getParameter(renderer.gl.BLEND_DST_RGB) !== blend[1] ||
163+
renderer.gl.getParameter(renderer.gl.BLEND_SRC_ALPHA) !== blend[2] ||
164+
renderer.gl.getParameter(renderer.gl.BLEND_DST_ALPHA) !== blend[3]
165+
) {
166+
// how can I know if batch is drawing?
167+
renderer.batch.flush();
168+
renderer.gl.blendFuncSeparate(blend[0], blend[1], blend[2], blend[3]);
169+
}
170+
171+
super._render(renderer);
172+
}
118173
}

spine-ts/spine-pixi/src/SlotMesh.ts

+51-5
Original file line numberDiff line numberDiff line change
@@ -28,26 +28,32 @@
2828
*****************************************************************************/
2929

3030
import { SpineTexture } from "./SpineTexture.js";
31-
import type { BlendMode, NumberArrayLike } from "@esotericsoftware/spine-core";
31+
import type { NumberArrayLike } from "@esotericsoftware/spine-core";
32+
import { BlendMode } from "@esotericsoftware/spine-core";
3233
import type { ISlotMesh } from "./Spine.js";
3334
import { Mesh, MeshGeometry, MeshMaterial } from "@pixi/mesh";
34-
import { Texture } from "@pixi/core";
35+
import { Texture, BLEND_MODES, Renderer } from "@pixi/core";
3536

3637
export class SlotMesh extends Mesh implements ISlotMesh {
3738
public name: string = "";
3839

3940
private static readonly auxColor = [0, 0, 0, 0];
4041
private warnedTwoTint: boolean = false;
42+
public pma: boolean = false;
43+
private blendModeInternal: BlendMode = BlendMode.Normal;
4144

42-
constructor () {
45+
constructor (pma = false) {
4346
const geometry = new MeshGeometry();
4447

4548
geometry.getBuffer("aVertexPosition").static = false;
4649
geometry.getBuffer("aTextureCoord").static = false;
4750

4851
const meshMaterial = new MeshMaterial(Texture.EMPTY);
4952
super(geometry, meshMaterial);
53+
54+
this.pma = pma;
5055
}
56+
5157
public updateFromSpineData (
5258
slotTexture: SpineTexture,
5359
slotBlendMode: BlendMode,
@@ -56,7 +62,7 @@ export class SlotMesh extends Mesh implements ISlotMesh {
5662
finalVerticesLength: number,
5763
finalIndices: NumberArrayLike,
5864
finalIndicesLength: number,
59-
darkTint: boolean
65+
darkTint: boolean,
6066
): void {
6167
this.texture = slotTexture.texture;
6268

@@ -100,7 +106,7 @@ export class SlotMesh extends Mesh implements ISlotMesh {
100106

101107
this.tint = SlotMesh.auxColor;
102108
this.alpha = SlotMesh.auxColor[3];
103-
this.blendMode = SpineTexture.toPixiBlending(slotBlendMode);
109+
this.blendModeInternal = slotBlendMode;
104110

105111
if (this.geometry.indexBuffer.data.length !== finalIndices.length) {
106112
this.geometry.indexBuffer.data = new Uint32Array(finalIndices);
@@ -116,4 +122,44 @@ export class SlotMesh extends Mesh implements ISlotMesh {
116122
this.geometry.getBuffer("aTextureCoord").update();
117123
this.geometry.indexBuffer.update();
118124
}
125+
126+
protected override _render(renderer: Renderer): void {
127+
const pma_normal = [renderer.gl.ONE, renderer.gl.ONE_MINUS_SRC_ALPHA, renderer.gl.ONE, renderer.gl.ONE_MINUS_SRC_ALPHA];
128+
const pma_additive = [renderer.gl.ONE, renderer.gl.ONE, renderer.gl.ONE, renderer.gl.ONE];
129+
const pma_multiply = [renderer.gl.DST_COLOR, renderer.gl.ONE_MINUS_SRC_ALPHA, renderer.gl.ONE, renderer.gl.ONE_MINUS_SRC_ALPHA];
130+
const pma_screen = [renderer.gl.ONE, renderer.gl.ONE_MINUS_SRC_COLOR, renderer.gl.ONE, renderer.gl.ONE_MINUS_SRC_COLOR];
131+
132+
const normal = [renderer.gl.SRC_ALPHA, renderer.gl.ONE_MINUS_SRC_ALPHA, renderer.gl.ONE, renderer.gl.ONE_MINUS_SRC_ALPHA];
133+
const additive = [renderer.gl.SRC_ALPHA, renderer.gl.ONE, renderer.gl.ONE, renderer.gl.ONE];
134+
const multiply = [renderer.gl.DST_COLOR, renderer.gl.ONE_MINUS_SRC_ALPHA, renderer.gl.ONE, renderer.gl.ONE_MINUS_SRC_ALPHA];
135+
const screen = [renderer.gl.ONE, renderer.gl.ONE_MINUS_SRC_COLOR, renderer.gl.ONE, renderer.gl.ONE_MINUS_SRC_COLOR];
136+
let blend;
137+
138+
switch (this.blendModeInternal) {
139+
case BlendMode.Normal:
140+
blend = this.pma ? pma_normal : normal;
141+
break;
142+
case BlendMode.Additive:
143+
blend = this.pma ? pma_additive : additive;
144+
break;
145+
case BlendMode.Multiply:
146+
blend = this.pma ? pma_multiply : multiply;
147+
break;
148+
case BlendMode.Screen:
149+
blend = this.pma ? pma_screen : screen;
150+
}
151+
152+
if (
153+
renderer.gl.getParameter(renderer.gl.BLEND_SRC_RGB) !== blend[0] ||
154+
renderer.gl.getParameter(renderer.gl.BLEND_DST_RGB) !== blend[1] ||
155+
renderer.gl.getParameter(renderer.gl.BLEND_SRC_ALPHA) !== blend[2] ||
156+
renderer.gl.getParameter(renderer.gl.BLEND_DST_ALPHA) !== blend[3]
157+
) {
158+
// how can I know if batch is drawing?
159+
renderer.batch.flush();
160+
renderer.gl.blendFuncSeparate(blend[0], blend[1], blend[2], blend[3]);
161+
}
162+
163+
super._render(renderer);
164+
}
119165
}

spine-ts/spine-pixi/src/Spine.ts

+51-20
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ export interface ISpineOptions {
7070
* a dark tint mesh in the skeleton.
7171
* */
7272
slotMeshFactory?: () => ISlotMesh;
73+
/** True, if the atlas images are premultiplied alpha. If not passed, the value is read from the atlas file. */
74+
pma?: boolean;
7375
}
7476

7577
/**
@@ -111,7 +113,7 @@ export class Spine extends Container {
111113
this._debug = value;
112114
}
113115

114-
protected slotMeshFactory: () => ISlotMesh = () => new SlotMesh();
116+
protected slotMeshFactory: (pma: boolean) => ISlotMesh = (pma: boolean) => new SlotMesh(pma);
115117

116118
beforeUpdateWorldTransforms: (object: Spine) => void = () => { };
117119
afterUpdateWorldTransforms: (object: Spine) => void = () => { };
@@ -143,6 +145,8 @@ export class Spine extends Container {
143145

144146
private lightColor = new Color();
145147
private darkColor = new Color();
148+
private twoColorTint = false;
149+
private pma = false;
146150

147151
constructor (skeletonData: SkeletonData, options?: ISpineOptions) {
148152
super();
@@ -151,6 +155,7 @@ export class Spine extends Container {
151155
const animData = new AnimationStateData(skeletonData);
152156
this.state = new AnimationState(animData);
153157
this.autoUpdate = options?.autoUpdate ?? true;
158+
this.pma = options?.pma ?? false;
154159
this.initializeMeshFactory(options);
155160
this.skeleton.setToSetupPose();
156161
this.skeleton.updateWorldTransform(Physics.update);
@@ -162,7 +167,8 @@ export class Spine extends Container {
162167
} else {
163168
for (let i = 0; i < this.skeleton.slots.length; i++) {
164169
if (this.skeleton.slots[i].data.darkColor) {
165-
this.slotMeshFactory = options?.slotMeshFactory ?? (() => new DarkSlotMesh());
170+
this.twoColorTint = true;
171+
this.slotMeshFactory = options?.slotMeshFactory ?? ((pma: boolean) => new DarkSlotMesh(pma));
166172
break;
167173
}
168174
}
@@ -228,7 +234,7 @@ export class Spine extends Container {
228234
*/
229235
protected getMeshForSlot (slot: Slot): ISlotMesh {
230236
if (!this.meshesCache.has(slot)) {
231-
let mesh = this.slotMeshFactory();
237+
let mesh = this.slotMeshFactory(this.pma);
232238
this.addChild(mesh);
233239
this.meshesCache.set(slot, mesh);
234240
return mesh;
@@ -412,27 +418,47 @@ export class Spine extends Container {
412418
skeletonColor.a * slotColor.a * attachmentColor.a,
413419
);
414420

415-
if (slot.darkColor != null) {
416-
const premultipliedAlpha = attachment.region?.texture.texture.baseTexture.alphaMode == 2;
417-
// const premultipliedAlpha = false;
421+
const mesh = this.getMeshForSlot(slot);
422+
const premultipliedAlpha = mesh.pma;
423+
if (premultipliedAlpha) {
424+
this.lightColor.r *= this.lightColor.a;
425+
this.lightColor.g *= this.lightColor.a;
426+
this.lightColor.b *= this.lightColor.a;
427+
}
428+
429+
if (!slot.darkColor)
430+
this.darkColor.set(0, 0, 0, 1.0);
431+
else {
418432
if (premultipliedAlpha) {
419-
this.darkColor.setFromColor(slot.darkColor);
420-
this.darkColor.a = 0.0;
421-
// this.darkColor.r = slot.darkColor.r * this.lightColor.a;
422-
// this.darkColor.g = slot.darkColor.g * this.lightColor.a;
423-
// this.darkColor.b = slot.darkColor.b * this.lightColor.a;
424-
// this.darkColor.a = 1.0;
433+
this.darkColor.r = slot.darkColor.r * this.lightColor.a;
434+
this.darkColor.g = slot.darkColor.g * this.lightColor.a;
435+
this.darkColor.b = slot.darkColor.b * this.lightColor.a;
425436
} else {
426437
this.darkColor.setFromColor(slot.darkColor);
427-
// this.darkColor.r = slot.darkColor.r * this.lightColor.a;
428-
// this.darkColor.g = slot.darkColor.g * this.lightColor.a;
429-
// this.darkColor.b = slot.darkColor.b * this.lightColor.a;
430-
this.darkColor.a = 0.0;
431438
}
432-
} else {
433-
this.darkColor.set(0, 0, 0, 0);
439+
this.darkColor.a = premultipliedAlpha ? 1.0 : 0.0;
434440
}
435441

442+
// if (slot.darkColor != null) {
443+
// // const premultipliedAlpha = false;
444+
// if (premultipliedAlpha) {
445+
// this.darkColor.setFromColor(slot.darkColor);
446+
// this.darkColor.a = 0.0;
447+
// this.darkColor.r = slot.darkColor.r * this.lightColor.a;
448+
// this.darkColor.g = slot.darkColor.g * this.lightColor.a;
449+
// this.darkColor.b = slot.darkColor.b * this.lightColor.a;
450+
// this.darkColor.a = 1.0;
451+
// } else {
452+
// this.darkColor.setFromColor(slot.darkColor);
453+
// // this.darkColor.r = slot.darkColor.r * this.lightColor.a;
454+
// // this.darkColor.g = slot.darkColor.g * this.lightColor.a;
455+
// // this.darkColor.b = slot.darkColor.b * this.lightColor.a;
456+
// this.darkColor.a = 0.0;
457+
// }
458+
// } else {
459+
// this.darkColor.set(0, 0, 0, 0);
460+
// }
461+
436462
let finalVertices: NumberArrayLike;
437463
let finalVerticesLength: number;
438464
let finalIndices: NumberArrayLike;
@@ -463,6 +489,9 @@ export class Spine extends Container {
463489
verts[tempV++] = this.darkColor.g;
464490
verts[tempV++] = this.darkColor.b;
465491
verts[tempV++] = this.darkColor.a;
492+
if (slot.data.name == "orangeball3") {
493+
// console.log(this.darkColor)
494+
}
466495
}
467496
}
468497
finalVertices = this.verticesCache;
@@ -476,7 +505,6 @@ export class Spine extends Container {
476505
continue;
477506
}
478507

479-
const mesh = this.getMeshForSlot(slot);
480508
mesh.zIndex = zIndex;
481509
mesh.updateFromSpineData(texture, slot.data.blendMode, slot.data.name, finalVertices, finalVerticesLength, finalIndices, finalIndicesLength, useDarkColor);
482510
}
@@ -579,19 +607,21 @@ export class Spine extends Container {
579607
* @returns {Spine} The Spine game object instantiated
580608
*/
581609
public static from (skeletonAssetName: string, atlasAssetName: string, options?: ISpineOptions): Spine {
610+
let opt = { ...options };
582611
const cacheKey = `${skeletonAssetName}-${atlasAssetName}-${options?.scale ?? 1}`;
583612
let skeletonData = Spine.skeletonCache[cacheKey];
584613
if (skeletonData) {
585614
return new Spine(skeletonData, options);
586615
}
587616
const skeletonAsset = Assets.get<any | Uint8Array>(skeletonAssetName);
588617
const atlasAsset = Assets.get<TextureAtlas>(atlasAssetName);
618+
opt.pma = options?.pma ?? atlasAsset.pages.findIndex(({ pma }) => pma) > -1;
589619
const attachmentLoader = new AtlasAttachmentLoader(atlasAsset);
590620
let parser = skeletonAsset instanceof Uint8Array ? new SkeletonBinary(attachmentLoader) : new SkeletonJson(attachmentLoader);
591621
parser.scale = options?.scale ?? 1;
592622
skeletonData = parser.readSkeletonData(skeletonAsset);
593623
Spine.skeletonCache[cacheKey] = skeletonData;
594-
return new this(skeletonData, options);
624+
return new this(skeletonData, opt);
595625
}
596626

597627
public get tint (): number {
@@ -614,6 +644,7 @@ Skeleton.yDown = true;
614644
*/
615645
export interface ISlotMesh extends DisplayObject {
616646
name: string;
647+
pma: boolean;
617648
updateFromSpineData (
618649
slotTexture: SpineTexture,
619650
slotBlendMode: BlendMode,

0 commit comments

Comments
 (0)