@@ -54,6 +54,7 @@ import type { IPointData } from "@pixi/core";
54
54
import { Ticker } from "@pixi/core" ;
55
55
import type { IDestroyOptions , DisplayObject } from "@pixi/display" ;
56
56
import { Container } from "@pixi/display" ;
57
+ import { Graphics } from "@pixi/graphics" ;
57
58
58
59
/**
59
60
* Options to configure a {@link Spine} game object.
@@ -205,6 +206,13 @@ export class Spine extends Container {
205
206
this . debug = undefined ;
206
207
this . meshesCache . clear ( ) ;
207
208
this . slotsObject . clear ( ) ;
209
+
210
+ for ( let maskKey in this . clippingSlotToPixiMasks ) {
211
+ const mask = this . clippingSlotToPixiMasks [ maskKey ] ;
212
+ mask . destroy ( ) ;
213
+ delete this . clippingSlotToPixiMasks [ maskKey ] ;
214
+ }
215
+
208
216
super . destroy ( options ) ;
209
217
}
210
218
@@ -231,7 +239,7 @@ export class Spine extends Container {
231
239
}
232
240
}
233
241
234
- private slotsObject = new Map < Slot , DisplayObject > ( ) ;
242
+ private slotsObject = new Map < Slot , Container > ( ) ;
235
243
private getSlotFromRef ( slotRef : number | string | Slot ) : Slot {
236
244
let slot : Slot | null ;
237
245
if ( typeof slotRef === 'number' ) slot = this . skeleton . slots [ slotRef ] ;
@@ -243,54 +251,52 @@ export class Spine extends Container {
243
251
return slot ;
244
252
}
245
253
/**
246
- * Add a pixi DisplayObject as a child of the Spine object.
247
- * The DisplayObject will be rendered coherently with the draw order of the slot.
248
- * If an attachment is active on the slot, the pixi DisplayObject will be rendered on top of it.
249
- * If the DisplayObject is already attached to the given slot, nothing will happen.
250
- * If the DisplayObject is already attached to another slot, it will be removed from that slot
254
+ * Add a pixi Container as a child of the Spine object.
255
+ * The Container will be rendered coherently with the draw order of the slot.
256
+ * If an attachment is active on the slot, the pixi Container will be rendered on top of it.
257
+ * If the Container is already attached to the given slot, nothing will happen.
258
+ * If the Container is already attached to another slot, it will be removed from that slot
251
259
* before adding it to the given one.
252
- * If another DisplayObject is already attached to this slot, the old one will be removed from this
260
+ * If another Container is already attached to this slot, the old one will be removed from this
253
261
* slot before adding it to the current one.
254
262
* @param slotRef - The slot index, or the slot name, or the Slot where the pixi object will be added to.
255
- * @param pixiObject - The pixi DisplayObject to add.
263
+ * @param pixiObject - The pixi Container to add.
256
264
*/
257
- addSlotObject ( slotRef : number | string | Slot , pixiObject : DisplayObject ) : void {
265
+ addSlotObject ( slotRef : number | string | Slot , pixiObject : Container ) : void {
258
266
let slot = this . getSlotFromRef ( slotRef ) ;
259
267
let oldPixiObject = this . slotsObject . get ( slot ) ;
268
+ if ( oldPixiObject === pixiObject ) return ;
260
269
261
270
// search if the pixiObject was already in another slotObject
262
- if ( ! oldPixiObject ) {
263
- for ( const [ slot , oldPixiObjectAnotherSlot ] of this . slotsObject ) {
264
- if ( oldPixiObjectAnotherSlot === pixiObject ) {
265
- this . removeSlotObject ( slot , pixiObject ) ;
266
- break ;
267
- }
271
+ for ( const [ otherSlot , oldPixiObjectAnotherSlot ] of this . slotsObject ) {
272
+ if ( otherSlot !== slot && oldPixiObjectAnotherSlot === pixiObject ) {
273
+ this . removeSlotObject ( otherSlot , pixiObject ) ;
274
+ break ;
268
275
}
269
276
}
270
-
271
- if ( oldPixiObject === pixiObject ) return ;
277
+
272
278
if ( oldPixiObject ) this . removeChild ( oldPixiObject ) ;
273
279
274
280
this . slotsObject . set ( slot , pixiObject ) ;
275
281
this . addChild ( pixiObject ) ;
276
282
}
277
283
/**
278
- * Return the DisplayObject connected to the given slot, if any.
284
+ * Return the Container connected to the given slot, if any.
279
285
* Otherwise return undefined
280
- * @param pixiObject - The slot index, or the slot name, or the Slot to get the DisplayObject from.
281
- * @returns a DisplayObject if any, undefined otherwise.
286
+ * @param pixiObject - The slot index, or the slot name, or the Slot to get the Container from.
287
+ * @returns a Container if any, undefined otherwise.
282
288
*/
283
- getSlotObject ( slotRef : number | string | Slot ) : DisplayObject | undefined {
289
+ getSlotObject ( slotRef : number | string | Slot ) : Container | undefined {
284
290
return this . slotsObject . get ( this . getSlotFromRef ( slotRef ) ) ;
285
291
}
286
292
/**
287
293
* Remove a slot object from the given slot.
288
294
* If `pixiObject` is passed and attached to the given slot, remove it from the slot.
289
- * If `pixiObject` is not passed and the given slot has an attached DisplayObject , remove it from the slot.
295
+ * If `pixiObject` is not passed and the given slot has an attached Container , remove it from the slot.
290
296
* @param slotRef - The slot index, or the slot name, or the Slot where the pixi object will be remove from.
291
- * @param pixiObject - Optional, The pixi DisplayObject to remove.
297
+ * @param pixiObject - Optional, The pixi Container to remove.
292
298
*/
293
- removeSlotObject ( slotRef : number | string | Slot , pixiObject ?: DisplayObject ) : void {
299
+ removeSlotObject ( slotRef : number | string | Slot , pixiObject ?: Container ) : void {
294
300
let slot = this . getSlotFromRef ( slotRef ) ;
295
301
let slotObject = this . slotsObject . get ( slot ) ;
296
302
if ( ! slotObject ) return ;
@@ -303,29 +309,64 @@ export class Spine extends Container {
303
309
}
304
310
305
311
private verticesCache : NumberArrayLike = Utils . newFloatArray ( 1024 ) ;
312
+ private clippingSlotToPixiMasks : Record < string , Graphics > = { } ;
313
+ private pixiMaskCleanup ( slot : Slot ) {
314
+ let mask = this . clippingSlotToPixiMasks [ slot . data . name ] ;
315
+ if ( mask ) {
316
+ delete this . clippingSlotToPixiMasks [ slot . data . name ] ;
317
+ mask . destroy ( ) ;
318
+ }
319
+ }
320
+ private updatePixiObject ( pixiObject : Container , slot : Slot , zIndex : number ) {
321
+ pixiObject . setTransform ( slot . bone . worldX , slot . bone . worldY , slot . bone . getWorldScaleX ( ) , slot . bone . getWorldScaleX ( ) , slot . bone . getWorldRotationX ( ) * MathUtils . degRad ) ;
322
+ pixiObject . zIndex = zIndex + 1 ;
323
+ pixiObject . alpha = this . skeleton . color . a * slot . color . a ;
324
+ }
325
+ private updateAndSetPixiMask ( pixiMaskSource : PixiMaskSource | null , pixiObject : Container ) {
326
+ if ( Spine . clipper . isClipping ( ) && pixiMaskSource ) {
327
+ let mask = this . clippingSlotToPixiMasks [ pixiMaskSource . slot . data . name ] as Graphics ;
328
+ if ( ! mask ) {
329
+ mask = new Graphics ( ) ;
330
+ this . clippingSlotToPixiMasks [ pixiMaskSource . slot . data . name ] = mask ;
331
+ this . addChild ( mask ) ;
332
+ }
333
+ if ( ! pixiMaskSource . computed ) {
334
+ pixiMaskSource . computed = true ;
335
+ const clippingAttachment = pixiMaskSource . slot . attachment as ClippingAttachment ;
336
+ const world = Array . from ( clippingAttachment . vertices ) ;
337
+ clippingAttachment . computeWorldVertices ( pixiMaskSource . slot , 0 , clippingAttachment . worldVerticesLength , world , 0 , 2 ) ;
338
+ mask . clear ( ) . lineStyle ( 0 ) . beginFill ( 0x000000 ) . drawPolygon ( world ) ;
339
+ }
340
+ pixiObject . mask = mask ;
341
+ } else if ( pixiObject . mask ) {
342
+ pixiObject . mask = null ;
343
+ }
344
+ }
306
345
private renderMeshes ( ) : void {
307
346
this . resetMeshes ( ) ;
308
347
309
348
let triangles : Array < number > | null = null ;
310
349
let uvs : NumberArrayLike | null = null ;
350
+ let pixiMaskSource : PixiMaskSource | null = null ;
311
351
const drawOrder = this . skeleton . drawOrder ;
312
352
313
353
for ( let i = 0 , n = drawOrder . length , slotObjectsCounter = 0 ; i < n ; i ++ ) {
314
354
const slot = drawOrder [ i ] ;
315
-
355
+
316
356
// render pixi object on the current slot on top of the slot attachment
317
357
let pixiObject = this . slotsObject . get ( slot ) ;
318
358
let zIndex = i + slotObjectsCounter ;
319
359
if ( pixiObject ) {
320
- pixiObject . setTransform ( slot . bone . worldX , slot . bone . worldY , slot . bone . getWorldScaleX ( ) , slot . bone . getWorldScaleX ( ) , slot . bone . getWorldRotationX ( ) * MathUtils . degRad ) ;
321
- pixiObject . zIndex = zIndex + 1 ;
360
+ this . updatePixiObject ( pixiObject , slot , zIndex + 1 ) ;
322
361
slotObjectsCounter ++ ;
362
+ this . updateAndSetPixiMask ( pixiMaskSource , pixiObject ) ;
323
363
}
324
364
325
365
const useDarkColor = slot . darkColor != null ;
326
366
const vertexSize = Spine . clipper . isClipping ( ) ? 2 : useDarkColor ? Spine . DARK_VERTEX_SIZE : Spine . VERTEX_SIZE ;
327
367
if ( ! slot . bone . active ) {
328
368
Spine . clipper . clipEndWithSlot ( slot ) ;
369
+ this . pixiMaskCleanup ( slot ) ;
329
370
continue ;
330
371
}
331
372
const attachment = slot . getAttachment ( ) ;
@@ -353,9 +394,11 @@ export class Spine extends Container {
353
394
texture = < SpineTexture > mesh . region ?. texture ;
354
395
} else if ( attachment instanceof ClippingAttachment ) {
355
396
Spine . clipper . clipStart ( slot , attachment ) ;
397
+ pixiMaskSource = { slot, computed : false } ;
356
398
continue ;
357
399
} else {
358
400
Spine . clipper . clipEndWithSlot ( slot ) ;
401
+ this . pixiMaskCleanup ( slot ) ;
359
402
continue ;
360
403
}
361
404
if ( texture != null ) {
@@ -423,6 +466,7 @@ export class Spine extends Container {
423
466
}
424
467
425
468
Spine . clipper . clipEndWithSlot ( slot ) ;
469
+ this . pixiMaskCleanup ( slot ) ;
426
470
}
427
471
Spine . clipper . clipEnd ( ) ;
428
472
}
@@ -542,6 +586,11 @@ export class Spine extends Container {
542
586
}
543
587
}
544
588
589
+ type PixiMaskSource = {
590
+ slot : Slot ,
591
+ computed : boolean , // prevent to reculaculate vertices for a mask clipping multiple pixi objects
592
+ }
593
+
545
594
Skeleton . yDown = true ;
546
595
547
596
/**
0 commit comments