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) {