diff --git a/CHANGELOG.md b/CHANGELOG.md index cecfb9dd4..d0a01a5c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -156,6 +156,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). @@ -164,7 +166,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-c/spine-c/src/spine/IkConstraint.c b/spine-c/spine-c/src/spine/IkConstraint.c index 0deffeb2d..1caab6379 100644 --- a/spine-c/spine-c/src/spine/IkConstraint.c +++ b/spine-c/spine-c/src/spine/IkConstraint.c @@ -160,7 +160,7 @@ void spIkConstraint_apply2 (spBone* parent, spBone* child, float targetX, float y = SQRT(dd - r * r) * bendDir; a1 = ta - ATAN2(y, r); a2 = ATAN2(y / psy, (r - l1) / psx); - goto outer; + goto break_outer; } } { @@ -194,7 +194,7 @@ void spIkConstraint_apply2 (spBone* parent, spBone* child, float targetX, float } } } - outer: { + break_outer: { float os = ATAN2(cy, cx) * s2; a1 = (a1 - os) * RAD_DEG + o1 - parent->arotation; if (a1 > 180) a1 -= 360; diff --git a/spine-c/spine-c/src/spine/Skeleton.c b/spine-c/spine-c/src/spine/Skeleton.c index d73f34f01..7383b0c1d 100644 --- a/spine-c/spine-c/src/spine/Skeleton.c +++ b/spine-c/spine-c/src/spine/Skeleton.c @@ -351,14 +351,14 @@ void spSkeleton_updateCache (spSkeleton* self) { constraintCount = ikCount + transformCount + pathCount; i = 0; - outer: + continue_outer: for (; i < constraintCount; i++) { for (ii = 0; ii < ikCount; ii++) { spIkConstraint* ikConstraint = ikConstraints[ii]; if (ikConstraint->data->order == i) { _sortIkConstraint(internal, ikConstraint); i++; - goto outer; + goto continue_outer; } } @@ -367,7 +367,7 @@ void spSkeleton_updateCache (spSkeleton* self) { if (transformConstraint->data->order == i) { _sortTransformConstraint(internal, transformConstraint); i++; - goto outer; + goto continue_outer; } } @@ -376,7 +376,7 @@ void spSkeleton_updateCache (spSkeleton* self) { if (pathConstraint->data->order == i) { _sortPathConstraint(internal, pathConstraint); i++; - goto outer; + goto continue_outer; } } } diff --git a/spine-c/spine-c/src/spine/SkeletonClipping.c b/spine-c/spine-c/src/spine/SkeletonClipping.c index 8d2c7803c..a46dea73e 100644 --- a/spine-c/spine-c/src/spine/SkeletonClipping.c +++ b/spine-c/spine-c/src/spine/SkeletonClipping.c @@ -220,7 +220,7 @@ void spSkeletonClipping_clipTriangles(spSkeletonClipping* self, float* vertices, spFloatArray_clear(clippedUVs); spUnsignedShortArray_clear(clippedTriangles); i = 0; - outer: + continue_outer: for (; i < trianglesLength; i += 3) { int p; int vertexOffset = triangles[i] * stride; @@ -306,7 +306,7 @@ void spSkeletonClipping_clipTriangles(spSkeletonClipping* self, float* vertices, clippedTrianglesItems[s + 2] = (unsigned short)(index + 2); index += 3; i += 3; - goto outer; + goto continue_outer; } } } diff --git a/spine-c/spine-c/src/spine/Triangulator.c b/spine-c/spine-c/src/spine/Triangulator.c index c09976546..16b69bc93 100644 --- a/spine-c/spine-c/src/spine/Triangulator.c +++ b/spine-c/spine-c/src/spine/Triangulator.c @@ -168,13 +168,13 @@ spShortArray* spTriangulator_triangulate(spTriangulator* self, spFloatArray* ver vx = vertices[v]; vy = vertices[v + 1]; if (_positiveArea(p3x, p3y, p1x, p1y, vx, vy)) { if (_positiveArea(p1x, p1y, p2x, p2y, vx, vy)) { - if (_positiveArea(p2x, p2y, p3x, p3y, vx, vy)) goto outer; + if (_positiveArea(p2x, p2y, p3x, p3y, vx, vy)) goto break_outer; } } } break; } - outer: + break_outer: if (next == 0) { do { diff --git a/spine-csharp/src/AnimationState.cs b/spine-csharp/src/AnimationState.cs index 772ecc08b..f992efe0c 100644 --- a/spine-csharp/src/AnimationState.cs +++ b/spine-csharp/src/AnimationState.cs @@ -712,14 +712,14 @@ namespace Spine { if (entry.mixDuration > 0) { timelineDataItems[i] = AnimationState.DipMix; timelineDipMixItems[i] = entry; - goto outer; // continue outer; + goto continue_outer; // continue outer; } break; } } timelineDataItems[i] = AnimationState.Dip; } - outer: {} + continue_outer: {} } return lastEntry; } diff --git a/spine-csharp/src/IkConstraint.cs b/spine-csharp/src/IkConstraint.cs index 900b84870..9d188a69e 100644 --- a/spine-csharp/src/IkConstraint.cs +++ b/spine-csharp/src/IkConstraint.cs @@ -176,7 +176,7 @@ namespace Spine { y = (float)Math.Sqrt(dd - r * r) * bendDir; a1 = ta - (float)Math.Atan2(y, r); a2 = (float)Math.Atan2(y / psy, (r - l1) / psx); - goto outer; // break outer; + goto break_outer; // break outer; } } float minAngle = MathUtils.PI, minX = l1 - a, minDist = minX * minX, minY = 0; @@ -208,7 +208,7 @@ namespace Spine { a2 = maxAngle * bendDir; } } - outer: + break_outer: float os = (float)Math.Atan2(cy, cx) * s2; float rotation = parent.arotation; a1 = (a1 - os) * MathUtils.RadDeg + os1 - rotation; diff --git a/spine-csharp/src/Skeleton.cs b/spine-csharp/src/Skeleton.cs index a244544a2..ed4897ae6 100644 --- a/spine-csharp/src/Skeleton.cs +++ b/spine-csharp/src/Skeleton.cs @@ -135,24 +135,24 @@ namespace Spine { IkConstraint constraint = ikConstraints.Items[ii]; if (constraint.data.order == i) { SortIkConstraint(constraint); - goto outer; //continue outer; + goto continue_outer; //continue outer; } } for (int ii = 0; ii < transformCount; ii++) { TransformConstraint constraint = transformConstraints.Items[ii]; if (constraint.data.order == i) { SortTransformConstraint(constraint); - goto outer; //continue outer; + goto continue_outer; //continue outer; } } for (int ii = 0; ii < pathCount; ii++) { PathConstraint constraint = pathConstraints.Items[ii]; if (constraint.data.order == i) { SortPathConstraint(constraint); - goto outer; //continue outer; + goto continue_outer; //continue outer; } } - outer: {} + continue_outer: {} } for (int i = 0, n = bones.Count; i < n; i++) diff --git a/spine-csharp/src/Triangulator.cs b/spine-csharp/src/Triangulator.cs index dd1768b52..c4bdb0418 100644 --- a/spine-csharp/src/Triangulator.cs +++ b/spine-csharp/src/Triangulator.cs @@ -78,13 +78,13 @@ namespace Spine { float vx = vertices[v], vy = vertices[v + 1]; if (PositiveArea(p3x, p3y, p1x, p1y, vx, vy)) { if (PositiveArea(p1x, p1y, p2x, p2y, vx, vy)) { - if (PositiveArea(p2x, p2y, p3x, p3y, vx, vy)) goto outer; // break outer; + if (PositiveArea(p2x, p2y, p3x, p3y, vx, vy)) goto break_outer; // break outer; } } } break; } - outer: + break_outer: if (next == 0) { do { 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) {