瀏覽代碼

[ts][pixi-v7] Aligned slot object mask to spine-pixi-v8 to fix non removed masks. See #2872.

Davide Tantillo 1 月之前
父節點
當前提交
946868a04e

+ 1 - 1
spine-ts/spine-pixi-v7/example/slot-objects.html

@@ -127,7 +127,7 @@
         // for clipping attachments having slot objects
         setTimeout(() => {
           spineboy.state.setAnimation(0, "portal", true)
-          const tooth3 = PIXI.Sprite.from('assets/raptor-jaw-tooth.png');
+          const tooth3 = PIXI.Sprite.from('/assets/raptor-jaw-tooth.png');
           tooth3.scale.set(2);
           tooth3.x = -60;
           tooth3.y = 120;

+ 63 - 38
spine-ts/spine-pixi-v7/src/Spine.ts

@@ -36,6 +36,7 @@ import {
 	Color,
 	MeshAttachment,
 	Physics,
+	Pool,
 	RegionAttachment,
 	Skeleton,
 	SkeletonBinary,
@@ -323,7 +324,6 @@ export class Spine extends Container {
 
 	private lightColor = new Color();
 	private darkColor = new Color();
-	private clippingVertAux = new Float32Array(6);
 
 	private _boundsProvider?: SpineBoundsProvider;
 	/** The bounds provider to use. If undefined the bounds will be dynamic, calculated when requested and based on the current frame. */
@@ -441,8 +441,8 @@ export class Spine extends Container {
 		this.slotsObject.clear();
 
 		for (let maskKey in this.clippingSlotToPixiMasks) {
-			const mask = this.clippingSlotToPixiMasks[maskKey];
-			mask.destroy();
+			const maskObj = this.clippingSlotToPixiMasks[maskKey];
+			maskObj.mask?.destroy();
 			delete this.clippingSlotToPixiMasks[maskKey];
 		}
 
@@ -575,14 +575,7 @@ export class Spine extends Container {
 	}
 
 	private verticesCache: NumberArrayLike = Utils.newFloatArray(1024);
-	private clippingSlotToPixiMasks: Record<string, Graphics> = {};
-	private pixiMaskCleanup (slot: Slot) {
-		let mask = this.clippingSlotToPixiMasks[slot.data.name];
-		if (mask) {
-			delete this.clippingSlotToPixiMasks[slot.data.name];
-			mask.destroy();
-		}
-	}
+	private clippingSlotToPixiMasks: Record<string, SlotsToClipping> = {};
 
 	private updateSlotObject (element: { container: Container, followAttachmentTimeline: boolean }, slot: Slot, zIndex: number) {
 		const { container: slotObject, followAttachmentTimeline } = element
@@ -615,30 +608,62 @@ export class Spine extends Container {
 		}
 	}
 
-	private updateAndSetPixiMask (pixiMaskSource: PixiMaskSource | null, pixiObject: Container) {
-		if (Spine.clipper.isClipping() && pixiMaskSource) {
-			let mask = this.clippingSlotToPixiMasks[pixiMaskSource.slot.data.name] as Graphics;
+	private currentClippingSlot: SlotsToClipping | undefined;
+	private updateAndSetPixiMask (slot: Slot, last: boolean) {
+		// assign/create the currentClippingSlot
+		const attachment = slot.attachment;
+		if (attachment && attachment instanceof ClippingAttachment) {
+			const clip = (this.clippingSlotToPixiMasks[slot.data.name] ||= { slot, vertices: new Array<number>() });
+			clip.maskComputed = false;
+			this.currentClippingSlot = clip;
+			return;
+		}
+
+		// assign the currentClippingSlot mask to the slot object
+		let currentClippingSlot = this.currentClippingSlot;
+		const slotObject = this.slotsObject.get(slot);
+		if (currentClippingSlot && slotObject) {
+			// create the pixi mask, only the first time and if the clipped slot is the first one clipped by this currentClippingSlot
+			let mask = currentClippingSlot.mask;
 			if (!mask) {
-				mask = new Graphics();
-				this.clippingSlotToPixiMasks[pixiMaskSource.slot.data.name] = mask;
+				mask = maskPool.obtain();
+				currentClippingSlot.mask = mask;
 				this.addChild(mask);
 			}
-			if (!pixiMaskSource.computed) {
-				pixiMaskSource.computed = true;
-				const clippingAttachment = pixiMaskSource.slot.attachment as ClippingAttachment;
+
+			// compute the pixi mask polygon, if the clipped slot is the first one clipped by this currentClippingSlot
+			if (!currentClippingSlot.maskComputed) {
+				let slotClipping = currentClippingSlot.slot;
+				let clippingAttachment = slotClipping.attachment as ClippingAttachment;
+				currentClippingSlot.maskComputed = true;
 				const worldVerticesLength = clippingAttachment.worldVerticesLength;
-				if (this.clippingVertAux.length < worldVerticesLength) this.clippingVertAux = new Float32Array(worldVerticesLength);
-				clippingAttachment.computeWorldVertices(pixiMaskSource.slot, 0, worldVerticesLength, this.clippingVertAux, 0, 2);
-				mask.clear().lineStyle(0).beginFill(0x000000);
-				mask.moveTo(this.clippingVertAux[0], this.clippingVertAux[1]);
-				for (let i = 2; i < worldVerticesLength; i += 2) {
-					mask.lineTo(this.clippingVertAux[i], this.clippingVertAux[i + 1]);
+				const vertices = currentClippingSlot.vertices;
+				clippingAttachment.computeWorldVertices(slotClipping, 0, worldVerticesLength, vertices, 0, 2);
+				mask.clear().lineStyle(0).beginFill(0x000000).drawPolygon(vertices).endFill();
+			}
+
+			slotObject.container.mask = mask;
+		} else if (slotObject?.container.mask) {
+			// remove the mask, if slot object has a mask, but currentClippingSlot is undefined
+			slotObject.container.mask = null;
+		}
+
+		// if current slot is the ending one of the currentClippingSlot mask, set currentClippingSlot to undefined
+		if (currentClippingSlot && (currentClippingSlot.slot.attachment as ClippingAttachment).endSlot == slot.data) {
+			this.currentClippingSlot = undefined;
+		}
+
+		// clean up unused masks
+		if (last) {
+			for (const key in this.clippingSlotToPixiMasks) {
+				const clippingSlotToPixiMask = this.clippingSlotToPixiMasks[key];
+				if ((!(clippingSlotToPixiMask.slot.attachment instanceof ClippingAttachment) || !clippingSlotToPixiMask.maskComputed) && clippingSlotToPixiMask.mask) {
+					this.removeChild(clippingSlotToPixiMask.mask);
+					maskPool.free(clippingSlotToPixiMask.mask);
+					clippingSlotToPixiMask.mask = undefined;
 				}
-				mask.finishPoly();
 			}
-			pixiObject.mask = mask;
-		} else if (pixiObject.mask) {
-			pixiObject.mask = null;
+			this.currentClippingSlot = undefined;
 		}
 	}
 
@@ -658,7 +683,7 @@ export class Spine extends Container {
 
 		let triangles: Array<number> | null = null;
 		let uvs: NumberArrayLike | null = null;
-		let pixiMaskSource: PixiMaskSource | null = null;
+
 		const drawOrder = this.skeleton.drawOrder;
 
 		for (let i = 0, n = drawOrder.length, slotObjectsCounter = 0; i < n; i++) {
@@ -670,14 +695,13 @@ export class Spine extends Container {
 			if (pixiObject) {
 				this.updateSlotObject(pixiObject, slot, zIndex + 1);
 				slotObjectsCounter++;
-				this.updateAndSetPixiMask(pixiMaskSource, pixiObject.container);
 			}
+			this.updateAndSetPixiMask(slot, i === drawOrder.length - 1);
 
 			const useDarkColor = slot.darkColor != null;
 			const vertexSize = Spine.clipper.isClipping() ? 2 : useDarkColor ? Spine.DARK_VERTEX_SIZE : Spine.VERTEX_SIZE;
 			if (!slot.bone.active) {
 				Spine.clipper.clipEndWithSlot(slot);
-				this.pixiMaskCleanup(slot);
 				continue;
 			}
 			const attachment = slot.getAttachment();
@@ -705,14 +729,12 @@ export class Spine extends Container {
 				texture = <SpineTexture>mesh.region?.texture;
 			} else if (attachment instanceof ClippingAttachment) {
 				Spine.clipper.clipStart(slot, attachment);
-				pixiMaskSource = { slot, computed: false };
 				continue;
 			} else {
 				if (this.hasMeshForSlot(slot)) {
 					this.getMeshForSlot(slot).visible = false;
 				}
 				Spine.clipper.clipEndWithSlot(slot);
-				this.pixiMaskCleanup(slot);
 				continue;
 			}
 			if (texture != null) {
@@ -788,7 +810,6 @@ export class Spine extends Container {
 			}
 
 			Spine.clipper.clipEndWithSlot(slot);
-			this.pixiMaskCleanup(slot);
 		}
 		Spine.clipper.clipEnd();
 	}
@@ -997,10 +1018,14 @@ export class Spine extends Container {
 	}
 }
 
-type PixiMaskSource = {
+interface SlotsToClipping {
 	slot: Slot,
-	computed: boolean, // prevent to reculaculate vertices for a mask clipping multiple pixi objects
-}
+	mask?: Graphics,
+	maskComputed?: boolean,
+	vertices: Array<number>,
+};
+
+const maskPool = new Pool<Graphics>(() => new Graphics);
 
 Skeleton.yDown = true;
 

+ 4 - 6
spine-ts/spine-pixi-v8/src/Spine.ts

@@ -568,7 +568,7 @@ export class Spine extends ViewContainer {
 		if (attachment && attachment instanceof ClippingAttachment) {
 			const clip = (this.clippingSlotToPixiMasks[slot.data.name] ||= { slot, vertices: new Array<number>() });
 			clip.maskComputed = false;
-			this.currentClippingSlot = this.clippingSlotToPixiMasks[slot.data.name];
+			this.currentClippingSlot = clip;
 			return;
 		}
 
@@ -576,11 +576,8 @@ export class Spine extends ViewContainer {
 		let currentClippingSlot = this.currentClippingSlot;
 		let slotObject = this._slotsObject[slot.data.name];
 		if (currentClippingSlot && slotObject) {
-			let slotClipping = currentClippingSlot.slot;
-			let clippingAttachment = slotClipping.attachment as ClippingAttachment;
-
 			// create the pixi mask, only the first time and if the clipped slot is the first one clipped by this currentClippingSlot
-			let mask = currentClippingSlot.mask as Graphics;
+			let mask = currentClippingSlot.mask;
 			if (!mask) {
 				mask = maskPool.obtain();
 				currentClippingSlot.mask = mask;
@@ -589,6 +586,8 @@ export class Spine extends ViewContainer {
 
 			// compute the pixi mask polygon, if the clipped slot is the first one clipped by this currentClippingSlot
 			if (!currentClippingSlot.maskComputed) {
+				let slotClipping = currentClippingSlot.slot;
+				let clippingAttachment = slotClipping.attachment as ClippingAttachment;
 				currentClippingSlot.maskComputed = true;
 				const worldVerticesLength = clippingAttachment.worldVerticesLength;
 				const vertices = currentClippingSlot.vertices;
@@ -896,7 +895,6 @@ export class Spine extends ViewContainer {
 
 		container.includeInBuild = false;
 
-		// TODO only add once??
 		this.addChild(container);
 
 		const slotObject = {