diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5332a7195..9fa74cae7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -154,6 +154,8 @@
* Added `stride` parameter to `VertexAttachment.computeWorldVertices`.
* Removed `RegionAttachment.vertices` field. The vertices array is provided to `RegionAttachment.computeWorldVertices` by the API user now.
* Removed `RegionAttachment.updateWorldVertices`, added `RegionAttachment.computeWorldVertices`. The new method now computes the x/y positions of the 4 vertices of the corner and places them in the provided `worldVertices` array, starting at `offset`, then moving by `stride` array elements when advancing to the next vertex. This allows to directly compose the vertex buffer and avoids a copy. The computation of the full vertices, including vertex colors and texture coordinates, is now done by the backend's respective renderer.
+ * Skeleton attachments: Moved update of attached skeleton out of libGDX `SkeletonRenderer`, added overloaded method `Skeleton#updateWorldTransform(Bone), used for `SkeletonAttachment`. You now MUST call this new method
+ with the bone of the parent skeleton to which the child skeleton is attached. See `SkeletonAttachmentTest` for and example.
* **Additions**
* Added support for local and relative transform constraint calculation, including additional fields in `TransformConstraintData`
* Added `Bone.localToWorldRotation`(rotation given relative to x-axis, counter-clockwise, in degrees).
@@ -162,7 +164,7 @@
* Added `ClippingAttachment`, additional method `newClippingAttachment` in `AttachmentLoader` interface.
* Added `SkeletonClipper` and `Triangulator`, used to implement software clipping of attachments.
* `AnimationState#apply` returns boolean indicating if any timeline was applied or not.
- * `Animation#apply` and `Timeline#apply`` now take enums `MixPose` and `MixDirection` instead of booleans
+ * `Animation#apply` and `Timeline#apply`` now take enums `MixPose` and `MixDirection` instead of booleans
### libGDX
* Fixed renderer to work with 3.6 changes
diff --git a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/SkeletonAttachmentTest.java b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/SkeletonAttachmentTest.java
index 209c35a66..3a00b01b7 100644
--- a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/SkeletonAttachmentTest.java
+++ b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/SkeletonAttachmentTest.java
@@ -46,6 +46,7 @@ public class SkeletonAttachmentTest extends ApplicationAdapter {
Skeleton spineboy, goblin;
AnimationState spineboyState, goblinState;
+ Bone attachmentBone;
public void create () {
camera = new OrthographicCamera();
@@ -82,7 +83,9 @@ public class SkeletonAttachmentTest extends ApplicationAdapter {
// Instead of a right shoulder, spineboy will have a goblin!
SkeletonAttachment skeletonAttachment = new SkeletonAttachment("goblin");
skeletonAttachment.setSkeleton(goblin);
- spineboy.findSlot("front-upper-arm").setAttachment(skeletonAttachment);
+ Slot slot = spineboy.findSlot("front-upper-arm");
+ slot.setAttachment(skeletonAttachment);
+ attachmentBone = slot.getBone();
}
}
@@ -93,7 +96,7 @@ public class SkeletonAttachmentTest extends ApplicationAdapter {
goblinState.update(Gdx.graphics.getDeltaTime());
goblinState.apply(goblin);
- goblin.updateWorldTransform();
+ goblin.updateWorldTransform(attachmentBone);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java
index b48ecb390..9bc28c918 100644
--- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java
+++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java
@@ -30,6 +30,9 @@
package com.esotericsoftware.spine;
+import static com.esotericsoftware.spine.utils.SpineUtils.cosDeg;
+import static com.esotericsoftware.spine.utils.SpineUtils.sinDeg;
+
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array;
@@ -328,6 +331,62 @@ public class Skeleton {
for (int i = 0, n = updateCache.size; i < n; i++)
updateCache.get(i).update();
}
+
+ /** Updates the world transform for each bone and applies all constraints. The
+ * root bone will be temporarily parented to the specified bone.
+ *
+ * See World transforms in the Spine
+ * Runtimes Guide. */
+ public void updateWorldTransform (Bone parent) {
+ // This partial update avoids computing the world transform for constrained bones when 1) the bone is not updated
+ // before the constraint, 2) the constraint only needs to access the applied local transform, and 3) the constraint calls
+ // updateWorldTransform.
+ Array updateCacheReset = this.updateCacheReset;
+ for (int i = 0, n = updateCacheReset.size; i < n; i++) {
+ Bone bone = updateCacheReset.get(i);
+ bone.ax = bone.x;
+ bone.ay = bone.y;
+ bone.arotation = bone.rotation;
+ bone.ascaleX = bone.scaleX;
+ bone.ascaleY = bone.scaleY;
+ bone.ashearX = bone.shearX;
+ bone.ashearY = bone.shearY;
+ bone.appliedValid = true;
+ }
+
+ // Apply the parent bone transform to the root bone. The root bone
+ // always inherits scale, rotation and reflection.
+ Bone rootBone = getRootBone();
+ float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
+ rootBone.worldX = pa * x + pb * y + parent.worldX;
+ rootBone.worldY = pc * x + pd * y + parent.worldY;
+
+ float rotationY = rootBone.rotation + 90 + rootBone.shearY;
+ float la = cosDeg(rootBone.rotation + rootBone.shearX) * rootBone.scaleX;
+ float lb = cosDeg(rotationY) * rootBone.scaleY;
+ float lc = sinDeg(rootBone.rotation + rootBone.shearX) * rootBone.scaleX;
+ float ld = sinDeg(rotationY) * rootBone.scaleY;
+ rootBone.a = pa * la + pb * lc;
+ rootBone.b = pa * lb + pb * ld;
+ rootBone.c = pc * la + pd * lc;
+ rootBone.d = pc * lb + pd * ld;
+
+ if (flipY) {
+ rootBone.a = -rootBone.a;
+ rootBone.b = -rootBone.b;
+ }
+ if (flipX) {
+ rootBone.c = -rootBone.c;
+ rootBone.d = -rootBone.d;
+ }
+
+ // Update everything except root bone.
+ Array updateCache = this.updateCache;
+ for (int i = 0, n = updateCache.size; i < n; i++) {
+ Updatable updatable = updateCache.get(i);
+ if (updatable != rootBone) updatable.update();
+ }
+ }
/** Sets the bones, constraints, slots, and draw order to their setup pose values. */
public void setToSetupPose () {
diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java
index 32e033b1a..aae78ade2 100644
--- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java
+++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java
@@ -106,29 +106,7 @@ public class SkeletonRenderer {
} else if (attachment instanceof SkeletonAttachment) {
Skeleton attachmentSkeleton = ((SkeletonAttachment)attachment).getSkeleton();
- if (attachmentSkeleton != null) {
- Bone bone = slot.getBone();
- Bone rootBone = attachmentSkeleton.getRootBone();
- float oldScaleX = rootBone.getScaleX();
- float oldScaleY = rootBone.getScaleY();
- float oldRotation = rootBone.getRotation();
- attachmentSkeleton.setPosition(bone.getWorldX(), bone.getWorldY());
- // rootBone.setScaleX(1 + bone.getWorldScaleX() -
- // oldScaleX);
- // rootBone.setScaleY(1 + bone.getWorldScaleY() -
- // oldScaleY);
- // Set shear.
- rootBone.setRotation(oldRotation + bone.getWorldRotationX());
- attachmentSkeleton.updateWorldTransform();
-
- draw(batch, attachmentSkeleton);
-
- attachmentSkeleton.setX(0);
- attachmentSkeleton.setY(0);
- rootBone.setScaleX(oldScaleX);
- rootBone.setScaleY(oldScaleY);
- rootBone.setRotation(oldRotation);
- }
+ if (attachmentSkeleton != null) draw(batch, attachmentSkeleton);
}
clipper.clipEnd(slot);
@@ -189,28 +167,7 @@ public class SkeletonRenderer {
} else if (attachment instanceof SkeletonAttachment) {
Skeleton attachmentSkeleton = ((SkeletonAttachment)attachment).getSkeleton();
- if (attachmentSkeleton != null) {
- Bone bone = slot.getBone();
- Bone rootBone = attachmentSkeleton.getRootBone();
- float oldScaleX = rootBone.getScaleX();
- float oldScaleY = rootBone.getScaleY();
- float oldRotation = rootBone.getRotation();
- attachmentSkeleton.setPosition(bone.getWorldX(), bone.getWorldY());
- // rootBone.setScaleX(1 + bone.getWorldScaleX() -
- // oldScaleX);
- // rootBone.setScaleY(1 + bone.getWorldScaleY() -
- // oldScaleY);
- // Also set shear.
- rootBone.setRotation(oldRotation + bone.getWorldRotationX());
- attachmentSkeleton.updateWorldTransform();
-
- draw(batch, attachmentSkeleton);
-
- attachmentSkeleton.setPosition(0, 0);
- rootBone.setScaleX(oldScaleX);
- rootBone.setScaleY(oldScaleY);
- rootBone.setRotation(oldRotation);
- }
+ if (attachmentSkeleton != null) draw(batch, attachmentSkeleton);
}
if (texture != null) {
@@ -321,26 +278,7 @@ public class SkeletonRenderer {
} else if (attachment instanceof SkeletonAttachment) {
Skeleton attachmentSkeleton = ((SkeletonAttachment)attachment).getSkeleton();
- if (attachmentSkeleton != null) {
- Bone bone = slot.getBone();
- Bone rootBone = attachmentSkeleton.getRootBone();
- float oldScaleX = rootBone.getScaleX();
- float oldScaleY = rootBone.getScaleY();
- float oldRotation = rootBone.getRotation();
- attachmentSkeleton.setPosition(bone.getWorldX(), bone.getWorldY());
- // rootBone.setScaleX(1 + bone.getWorldScaleX() - oldScaleX);
- // rootBone.setScaleY(1 + bone.getWorldScaleY() - oldScaleY);
- // Also set shear.
- rootBone.setRotation(oldRotation + bone.getWorldRotationX());
- attachmentSkeleton.updateWorldTransform();
-
- draw(batch, attachmentSkeleton);
-
- attachmentSkeleton.setPosition(0, 0);
- rootBone.setScaleX(oldScaleX);
- rootBone.setScaleY(oldScaleY);
- rootBone.setRotation(oldRotation);
- }
+ if (attachmentSkeleton != null) draw(batch, attachmentSkeleton);
}
if (texture != null) {