[ts][pixi-v7][pixi-v8] Slot objects follow bone using local matrix making its transform equivalent to the one of the slot. See #3015.

This commit is contained in:
Davide Tantillo 2026-01-27 15:59:36 +01:00
parent cd47971c95
commit 0b3dba8998
4 changed files with 43 additions and 42 deletions

View File

@ -55,25 +55,34 @@
const bone = spineGO.skeleton.findBone("replaceMe"); const bone = spineGO.skeleton.findBone("replaceMe");
const myObject = { const myObject = {
parentScaleX: 1, parentScaleY: 1, parentRotation: 0, parentScaleX: 1, parentScaleY: 1, parentRotation: 0, parentShearX: 0, parentShearY: 0,
parent2ScaleX: 1, parent2ScaleY: 1, parent2Rotation: 0, parent2ScaleX: 1, parent2ScaleY: 1, parent2Rotation: 0, parent2ShearX: 0, parent2ShearY: 0,
scaleX: 1, scaleY: 1, rotation: 0, scaleX: 1, scaleY: 1, rotation: 0,
shearX: 0, shearY: 0,
spriteAlpha: 1,
}; };
const gui = new lil.GUI({}); const gui = new lil.GUI({});
gui.add(myObject, 'parentScaleX').min(-3).max(3).step(0.1).name('parentScaleX').onChange(value => parentBone.scaleX = value); gui.add(myObject, 'parentScaleX').min(-3).max(3).step(0.1).name('parentScaleX').onChange(value => parentBone.scaleX = value);
gui.add(myObject, 'parentScaleY').min(-3).max(3).step(0.1).name('parentScaleY').onChange(value => parentBone.scaleY = value); gui.add(myObject, 'parentScaleY').min(-3).max(3).step(0.1).name('parentScaleY').onChange(value => parentBone.scaleY = value);
gui.add(myObject, 'parentRotation').min(-180).max(180).step(1).name('parentRotation').onChange(value => parentBone.rotation = value); gui.add(myObject, 'parentRotation').min(-180).max(180).step(1).name('parentRotation').onChange(value => parentBone.rotation = value);
gui.add(myObject, 'parentShearX').min(-180).max(180).step(1).name('parentShearX').onChange(value => parentBone.shearX = value);
gui.add(myObject, 'parentShearY').min(-180).max(180).step(1).name('parentShearY').onChange(value => parentBone.shearY = value);
gui.add(myObject, 'parent2ScaleX').min(-3).max(3).step(0.1).name('parent2ScaleX').onChange(value => parent2Bone.scaleX = value); gui.add(myObject, 'parent2ScaleX').min(-3).max(3).step(0.1).name('parent2ScaleX').onChange(value => parent2Bone.scaleX = value);
gui.add(myObject, 'parent2ScaleY').min(-3).max(3).step(0.1).name('parent2ScaleY').onChange(value => parent2Bone.scaleY = value); gui.add(myObject, 'parent2ScaleY').min(-3).max(3).step(0.1).name('parent2ScaleY').onChange(value => parent2Bone.scaleY = value);
gui.add(myObject, 'parent2Rotation').min(-180).max(180).step(1).name('parent2Rotation').onChange(value => parent2Bone.rotation = value); gui.add(myObject, 'parent2Rotation').min(-180).max(180).step(1).name('parent2Rotation').onChange(value => parent2Bone.rotation = value);
gui.add(myObject, 'parent2ShearX').min(-180).max(180).step(1).name('parent2ShearX').onChange(value => parent2Bone.shearX = value);
gui.add(myObject, 'parent2ShearY').min(-180).max(180).step(1).name('parent2ShearY').onChange(value => parent2Bone.shearY = value);
gui.add(myObject, 'scaleX').min(-3).max(3).step(0.1).name('scaleX').onChange(value => bone.scaleX = value); gui.add(myObject, 'scaleX').min(-3).max(3).step(0.1).name('scaleX').onChange(value => bone.scaleX = value);
gui.add(myObject, 'scaleY').min(-3).max(3).step(0.1).name('scaleY').onChange(value => bone.scaleY = value); gui.add(myObject, 'scaleY').min(-3).max(3).step(0.1).name('scaleY').onChange(value => bone.scaleY = value);
gui.add(myObject, 'rotation').min(-180).max(180).step(1).name('rotation').onChange(value => bone.rotation = value); gui.add(myObject, 'rotation').min(-180).max(180).step(1).name('rotation').onChange(value => bone.rotation = value);
gui.add(myObject, 'shearX').min(-180).max(180).step(1).name('shearX').onChange(value => bone.shearX = value);
gui.add(myObject, 'shearY').min(-180).max(180).step(1).name('shearY').onChange(value => bone.shearY = value);
gui.add(myObject, 'spriteAlpha').min(0).max(1).step(0.01).name('spriteAlpha').onChange(value => sprite.alpha = value);
// add instructions // add instructions
const basicText = new PIXI.Text( const basicText = new PIXI.Text(
"This example shows that slot objects follow scale and rotation from ancestors too, but if a rotation on a bone occurs scale won't work anymore on ancestors.", "This example shows that slot objects can follow scale, shear and rotation from ancestors too.",
{ fontSize: 20, fill: "white", wordWrap: true, wordWrapWidth: 300 } { fontSize: 20, fill: "white", wordWrap: true, wordWrapWidth: 300 }
); );
basicText.position.set(10, 10); basicText.position.set(10, 10);

View File

@ -578,24 +578,16 @@ export class Spine extends Container {
slotObject.visible = this.skeleton.drawOrder.includes(slot) && followAttachmentValue; slotObject.visible = this.skeleton.drawOrder.includes(slot) && followAttachmentValue;
if (slotObject.visible) { if (slotObject.visible) {
slotObject.position.set(slot.bone.worldX, slot.bone.worldY); let bone = slot.bone;
slotObject.angle = slot.bone.getWorldRotationX();
let bone: Bone | null = slot.bone; const matrix = slotObject.localTransform;
let cumulativeScaleX = 1; matrix.a = bone.a;
let cumulativeScaleY = 1; matrix.b = bone.c;
while (bone) { matrix.c = -bone.b;
cumulativeScaleX *= bone.scaleX; matrix.d = -bone.d;
cumulativeScaleY *= bone.scaleY; matrix.tx = bone.worldX;
bone = bone.parent; matrix.ty = bone.worldY;
}; slotObject.transform.setFromMatrix(matrix);
if (cumulativeScaleX < 0) slotObject.angle -= 180;
slotObject.scale.set(
slot.bone.getWorldScaleX() * Math.sign(cumulativeScaleX),
slot.bone.getWorldScaleY() * Math.sign(cumulativeScaleY),
);
slotObject.zIndex = zIndex + 1; slotObject.zIndex = zIndex + 1;
slotObject.alpha = this.skeleton.color.a * slot.color.a; slotObject.alpha = this.skeleton.color.a * slot.color.a;

View File

@ -2,7 +2,7 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<title>spine-pixi</title> <title>spine-pixi</title>
<script src="https://cdn.jsdelivr.net/npm/pixi.js@8.4.1/dist/pixi.js"></script> <script src="https://cdn.jsdelivr.net/npm/pixi.js@8.7/dist/pixi.js"></script>
<script src="../dist/iife/spine-pixi-v8.js"></script> <script src="../dist/iife/spine-pixi-v8.js"></script>
<script src="https://cdn.jsdelivr.net/npm/tweakpane@3.1.9/dist/tweakpane.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/tweakpane@3.1.9/dist/tweakpane.min.js"></script>
<link rel="stylesheet" href="../../index.css"> <link rel="stylesheet" href="../../index.css">
@ -54,25 +54,34 @@
const bone = spineGO.skeleton.findBone("replaceMe"); const bone = spineGO.skeleton.findBone("replaceMe");
const myObject = { const myObject = {
parentScaleX: 1, parentScaleY: 1, parentRotation: 0, parentScaleX: 1, parentScaleY: 1, parentRotation: 0, parentShearX: 0, parentShearY: 0,
parent2ScaleX: 1, parent2ScaleY: 1, parent2Rotation: 0, parent2ScaleX: 1, parent2ScaleY: 1, parent2Rotation: 0, parent2ShearX: 0, parent2ShearY: 0,
scaleX: 1, scaleY: 1, rotation: 0, scaleX: 1, scaleY: 1, rotation: 0,
shearX: 0, shearY: 0,
spriteAlpha: 1,
}; };
const gui = new lil.GUI({}); const gui = new lil.GUI({});
gui.add(myObject, 'parentScaleX').min(-3).max(3).step(0.1).name('parentScaleX').onChange(value => parentBone.scaleX = value); gui.add(myObject, 'parentScaleX').min(-3).max(3).step(0.1).name('parentScaleX').onChange(value => parentBone.scaleX = value);
gui.add(myObject, 'parentScaleY').min(-3).max(3).step(0.1).name('parentScaleY').onChange(value => parentBone.scaleY = value); gui.add(myObject, 'parentScaleY').min(-3).max(3).step(0.1).name('parentScaleY').onChange(value => parentBone.scaleY = value);
gui.add(myObject, 'parentRotation').min(-180).max(180).step(1).name('parentRotation').onChange(value => parentBone.rotation = value); gui.add(myObject, 'parentRotation').min(-180).max(180).step(1).name('parentRotation').onChange(value => parentBone.rotation = value);
gui.add(myObject, 'parentShearX').min(-180).max(180).step(1).name('parentShearX').onChange(value => parentBone.shearX = value);
gui.add(myObject, 'parentShearY').min(-180).max(180).step(1).name('parentShearY').onChange(value => parentBone.shearY = value);
gui.add(myObject, 'parent2ScaleX').min(-3).max(3).step(0.1).name('parent2ScaleX').onChange(value => parent2Bone.scaleX = value); gui.add(myObject, 'parent2ScaleX').min(-3).max(3).step(0.1).name('parent2ScaleX').onChange(value => parent2Bone.scaleX = value);
gui.add(myObject, 'parent2ScaleY').min(-3).max(3).step(0.1).name('parent2ScaleY').onChange(value => parent2Bone.scaleY = value); gui.add(myObject, 'parent2ScaleY').min(-3).max(3).step(0.1).name('parent2ScaleY').onChange(value => parent2Bone.scaleY = value);
gui.add(myObject, 'parent2Rotation').min(-180).max(180).step(1).name('parent2Rotation').onChange(value => parent2Bone.rotation = value); gui.add(myObject, 'parent2Rotation').min(-180).max(180).step(1).name('parent2Rotation').onChange(value => parent2Bone.rotation = value);
gui.add(myObject, 'parent2ShearX').min(-180).max(180).step(1).name('parent2ShearX').onChange(value => parent2Bone.shearX = value);
gui.add(myObject, 'parent2ShearY').min(-180).max(180).step(1).name('parent2ShearY').onChange(value => parent2Bone.shearY = value);
gui.add(myObject, 'scaleX').min(-3).max(3).step(0.1).name('scaleX').onChange(value => bone.scaleX = value); gui.add(myObject, 'scaleX').min(-3).max(3).step(0.1).name('scaleX').onChange(value => bone.scaleX = value);
gui.add(myObject, 'scaleY').min(-3).max(3).step(0.1).name('scaleY').onChange(value => bone.scaleY = value); gui.add(myObject, 'scaleY').min(-3).max(3).step(0.1).name('scaleY').onChange(value => bone.scaleY = value);
gui.add(myObject, 'rotation').min(-180).max(180).step(1).name('rotation').onChange(value => bone.rotation = value); gui.add(myObject, 'rotation').min(-180).max(180).step(1).name('rotation').onChange(value => bone.rotation = value);
gui.add(myObject, 'shearX').min(-180).max(180).step(1).name('shearX').onChange(value => bone.shearX = value);
gui.add(myObject, 'shearY').min(-180).max(180).step(1).name('shearY').onChange(value => bone.shearY = value);
gui.add(myObject, 'spriteAlpha').min(0).max(1).step(0.01).name('spriteAlpha').onChange(value => sprite.alpha = value);
// add instructions // add instructions
const basicText = new PIXI.Text({ const basicText = new PIXI.Text({
text: "This example shows that slot objects follow scale and rotation from ancestors too, but if a rotation on a bone occurs scale won't work anymore on ancestors.", text: "This example shows that slot objects can follow scale, shear and rotation from ancestors too.",
style: { fontSize: 20, fill: "white", wordWrap: true, wordWrapWidth: 300 } style: { fontSize: 20, fill: "white", wordWrap: true, wordWrapWidth: 300 }
}); });
basicText.position.set(10, 10); basicText.position.set(10, 10);

View File

@ -792,25 +792,16 @@ export class Spine extends ViewContainer {
container.visible = this.skeleton.drawOrder.includes(slot) && followAttachmentValue; container.visible = this.skeleton.drawOrder.includes(slot) && followAttachmentValue;
if (container.visible) { if (container.visible) {
let bone: Bone | null = slot.bone; let bone = slot.bone;
container.position.set(bone.worldX, bone.worldY); const matrix = container.localTransform;
container.angle = bone.getWorldRotationX(); matrix.a = bone.a;
matrix.b = bone.c;
let cumulativeScaleX = 1; matrix.c = -bone.b;
let cumulativeScaleY = 1; matrix.d = -bone.d;
while (bone) { matrix.tx = bone.worldX;
cumulativeScaleX *= bone.scaleX; matrix.ty = bone.worldY;
cumulativeScaleY *= bone.scaleY; container.setFromMatrix(matrix);
bone = bone.parent;
};
if (cumulativeScaleX < 0) container.angle -= 180;
container.scale.set(
slot.bone.getWorldScaleX() * Math.sign(cumulativeScaleX),
slot.bone.getWorldScaleY() * Math.sign(cumulativeScaleY),
);
container.alpha = this.skeleton.color.a * slot.color.a; container.alpha = this.skeleton.color.a * slot.color.a;
} }