mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-03-07 11:16:53 +08:00
[libgdx] Breaking change: SkeletonRenderer no longer handles updating skeletons attached via SkeletonAttachment. Introduced new Skeleton#updateWorldTransform(Bone) that must be explicitely called by the user before rendering skeletons including attached skeletons. See SkeletonAttachmentTest for an example. Closes #1005
This commit is contained in:
parent
61d9f729d6
commit
69923111b9
@ -154,6 +154,8 @@
|
|||||||
* Added `stride` parameter to `VertexAttachment.computeWorldVertices`.
|
* 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.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.
|
* 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**
|
* **Additions**
|
||||||
* Added support for local and relative transform constraint calculation, including additional fields in `TransformConstraintData`
|
* 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).
|
* 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 `ClippingAttachment`, additional method `newClippingAttachment` in `AttachmentLoader` interface.
|
||||||
* Added `SkeletonClipper` and `Triangulator`, used to implement software clipping of attachments.
|
* Added `SkeletonClipper` and `Triangulator`, used to implement software clipping of attachments.
|
||||||
* `AnimationState#apply` returns boolean indicating if any timeline was applied or not.
|
* `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
|
### libGDX
|
||||||
* Fixed renderer to work with 3.6 changes
|
* Fixed renderer to work with 3.6 changes
|
||||||
|
|||||||
@ -46,6 +46,7 @@ public class SkeletonAttachmentTest extends ApplicationAdapter {
|
|||||||
|
|
||||||
Skeleton spineboy, goblin;
|
Skeleton spineboy, goblin;
|
||||||
AnimationState spineboyState, goblinState;
|
AnimationState spineboyState, goblinState;
|
||||||
|
Bone attachmentBone;
|
||||||
|
|
||||||
public void create () {
|
public void create () {
|
||||||
camera = new OrthographicCamera();
|
camera = new OrthographicCamera();
|
||||||
@ -82,7 +83,9 @@ public class SkeletonAttachmentTest extends ApplicationAdapter {
|
|||||||
// Instead of a right shoulder, spineboy will have a goblin!
|
// Instead of a right shoulder, spineboy will have a goblin!
|
||||||
SkeletonAttachment skeletonAttachment = new SkeletonAttachment("goblin");
|
SkeletonAttachment skeletonAttachment = new SkeletonAttachment("goblin");
|
||||||
skeletonAttachment.setSkeleton(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.update(Gdx.graphics.getDeltaTime());
|
||||||
goblinState.apply(goblin);
|
goblinState.apply(goblin);
|
||||||
goblin.updateWorldTransform();
|
goblin.updateWorldTransform(attachmentBone);
|
||||||
|
|
||||||
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
|
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
|
|||||||
@ -30,6 +30,9 @@
|
|||||||
|
|
||||||
package com.esotericsoftware.spine;
|
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.graphics.Color;
|
||||||
import com.badlogic.gdx.math.Vector2;
|
import com.badlogic.gdx.math.Vector2;
|
||||||
import com.badlogic.gdx.utils.Array;
|
import com.badlogic.gdx.utils.Array;
|
||||||
@ -328,6 +331,62 @@ public class Skeleton {
|
|||||||
for (int i = 0, n = updateCache.size; i < n; i++)
|
for (int i = 0, n = updateCache.size; i < n; i++)
|
||||||
updateCache.get(i).update();
|
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.
|
||||||
|
* <p>
|
||||||
|
* See <a href="http://esotericsoftware.com/spine-runtime-skeletons#World-transforms">World transforms</a> 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<Bone> 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<Updatable> 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. */
|
/** Sets the bones, constraints, slots, and draw order to their setup pose values. */
|
||||||
public void setToSetupPose () {
|
public void setToSetupPose () {
|
||||||
|
|||||||
@ -106,29 +106,7 @@ public class SkeletonRenderer {
|
|||||||
|
|
||||||
} else if (attachment instanceof SkeletonAttachment) {
|
} else if (attachment instanceof SkeletonAttachment) {
|
||||||
Skeleton attachmentSkeleton = ((SkeletonAttachment)attachment).getSkeleton();
|
Skeleton attachmentSkeleton = ((SkeletonAttachment)attachment).getSkeleton();
|
||||||
if (attachmentSkeleton != null) {
|
if (attachmentSkeleton != null) draw(batch, attachmentSkeleton);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
clipper.clipEnd(slot);
|
clipper.clipEnd(slot);
|
||||||
@ -189,28 +167,7 @@ public class SkeletonRenderer {
|
|||||||
|
|
||||||
} else if (attachment instanceof SkeletonAttachment) {
|
} else if (attachment instanceof SkeletonAttachment) {
|
||||||
Skeleton attachmentSkeleton = ((SkeletonAttachment)attachment).getSkeleton();
|
Skeleton attachmentSkeleton = ((SkeletonAttachment)attachment).getSkeleton();
|
||||||
if (attachmentSkeleton != null) {
|
if (attachmentSkeleton != null) draw(batch, attachmentSkeleton);
|
||||||
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 (texture != null) {
|
if (texture != null) {
|
||||||
@ -321,26 +278,7 @@ public class SkeletonRenderer {
|
|||||||
|
|
||||||
} else if (attachment instanceof SkeletonAttachment) {
|
} else if (attachment instanceof SkeletonAttachment) {
|
||||||
Skeleton attachmentSkeleton = ((SkeletonAttachment)attachment).getSkeleton();
|
Skeleton attachmentSkeleton = ((SkeletonAttachment)attachment).getSkeleton();
|
||||||
if (attachmentSkeleton != null) {
|
if (attachmentSkeleton != null) draw(batch, attachmentSkeleton);
|
||||||
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 (texture != null) {
|
if (texture != null) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user