diff --git a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/BonePlotting.java b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/BonePlotting.java index 14b853775..f642a6a93 100644 --- a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/BonePlotting.java +++ b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/BonePlotting.java @@ -76,14 +76,14 @@ public class BonePlotting { SkeletonData skeletonData = json.readSkeletonData(new FileHandle("assets/spineboy/spineboy-ess.json")); Skeleton skeleton = new Skeleton(skeletonData); - Bone bone = skeleton.findBone("gun-tip"); + BoneApplied bone = skeleton.findBone("gun-tip").getApplied(); // Pose the skeleton at regular intervals throughout each animation. float fps = 1 / 15f; for (Animation animation : skeletonData.getAnimations()) { float time = 0; while (time < animation.getDuration()) { - animation.apply(skeleton, time, time, false, null, 1, MixBlend.first, MixDirection.in); + animation.apply(skeleton, time, time, false, null, 1, MixBlend.first, MixDirection.in, false); skeleton.update(fps); skeleton.updateWorldTransform(Physics.update); diff --git a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/Box2DExample.java b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/Box2DExample.java index 41fc02a61..c1f174745 100644 --- a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/Box2DExample.java +++ b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/Box2DExample.java @@ -149,7 +149,7 @@ public class Box2DExample extends ApplicationAdapter { batch.setTransformMatrix(camera.view); batch.begin(); - animation.apply(skeleton, time, time, true, events, 1, MixBlend.first, MixDirection.in); + animation.apply(skeleton, time, time, true, events, 1, MixBlend.first, MixDirection.in, false); skeleton.x += 8 * delta; skeleton.update(delta); skeleton.updateWorldTransform(Physics.update); @@ -162,9 +162,10 @@ public class Box2DExample extends ApplicationAdapter { if (!(slot.getAttachment() instanceof Box2dAttachment)) continue; Box2dAttachment attachment = (Box2dAttachment)slot.getAttachment(); if (attachment.body == null) continue; - float x = slot.getBone().getWorldX(); - float y = slot.getBone().getWorldY(); - float rotation = slot.getBone().getWorldRotationX(); + BoneApplied bone = slot.getBone().getApplied(); + float x = bone.getWorldX(); + float y = bone.getWorldY(); + float rotation = bone.getWorldRotationX(); attachment.body.setTransform(x, y, rotation * MathUtils.degRad); } diff --git a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/EventTimelineTests.java b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/EventTimelineTests.java index a1ae90cfb..433c98187 100644 --- a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/EventTimelineTests.java +++ b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/EventTimelineTests.java @@ -177,7 +177,7 @@ public class EventTimelineTests { int beforeCount = firedEvents.size; Array original = new Array(firedEvents); - timeline.apply(skeleton, lastTimeLooped, timeLooped, firedEvents, 1, MixBlend.first, MixDirection.in); + timeline.apply(skeleton, lastTimeLooped, timeLooped, firedEvents, 1, MixBlend.first, MixDirection.in, false); while (beforeCount < firedEvents.size) { char fired = firedEvents.get(beforeCount).getData().getName().charAt(0); @@ -186,7 +186,7 @@ public class EventTimelineTests { } else { if (firedEvents.size > eventsCount) { if (print) System.out.println(lastTimeLooped + "->" + timeLooped + ": " + fired + " == ?"); - timeline.apply(skeleton, lastTimeLooped, timeLooped, original, 1, MixBlend.first, MixDirection.in); + timeline.apply(skeleton, lastTimeLooped, timeLooped, original, 1, MixBlend.first, MixDirection.in, false); fail("Too many events fired."); } } @@ -194,7 +194,7 @@ public class EventTimelineTests { System.out.println(lastTimeLooped + "->" + timeLooped + ": " + fired + " == " + events[eventIndex]); } if (fired != events[eventIndex]) { - timeline.apply(skeleton, lastTimeLooped, timeLooped, original, 1, MixBlend.first, MixDirection.in); + timeline.apply(skeleton, lastTimeLooped, timeLooped, original, 1, MixBlend.first, MixDirection.in, false); fail("Wrong event fired."); } eventIndex++; @@ -206,7 +206,7 @@ public class EventTimelineTests { i++; } if (firedEvents.size < eventsCount) { - timeline.apply(skeleton, lastTimeLooped, timeLooped, firedEvents, 1, MixBlend.first, MixDirection.in); + timeline.apply(skeleton, lastTimeLooped, timeLooped, firedEvents, 1, MixBlend.first, MixDirection.in, false); if (print) System.out.println(firedEvents); fail("Event not fired: " + events[eventIndex] + ", " + frames[eventIndex]); } diff --git a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/IKTest.java b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/IKTest.java index 57b6857a7..2c7a04ff3 100644 --- a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/IKTest.java +++ b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/IKTest.java @@ -107,7 +107,7 @@ public class IKTest extends ApplicationAdapter { Bone crosshair = skeleton.findBone("crosshair"); // Should be cached. boneCoords.set(cameraCoords.x, cameraCoords.y); - crosshair.getParent().worldToLocal(boneCoords); // camera space to local bone space + crosshair.getParent().getApplied().worldToLocal(boneCoords); // camera space to local bone space crosshair.setPosition(boneCoords.x, boneCoords.y); // override the crosshair position // Calculate final world transform with the crosshair bone set to the mouse cursor position. Update physics this time. diff --git a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/NormalMapTest.java b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/NormalMapTest.java index 2b6b51926..031269d75 100644 --- a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/NormalMapTest.java +++ b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/NormalMapTest.java @@ -137,7 +137,7 @@ public class NormalMapTest extends ApplicationAdapter { float lastTime = time; float delta = Gdx.graphics.getDeltaTime(); time += delta; - if (animation != null) animation.apply(skeleton, lastTime, time, true, null, 1, MixBlend.first, MixDirection.in); + if (animation != null) animation.apply(skeleton, lastTime, time, true, null, 1, MixBlend.first, MixDirection.in, false); skeleton.update(delta); skeleton.updateWorldTransform(Physics.update); diff --git a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/PngExportTest.java b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/PngExportTest.java index 3842c5a2d..72f9276f8 100644 --- a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/PngExportTest.java +++ b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/PngExportTest.java @@ -104,7 +104,7 @@ public class PngExportTest extends ApplicationAdapter { float fps = 1 / 15f, time = 0; int frame = 1; while (time < animation.getDuration()) { - animation.apply(skeleton, time, time, false, null, 1, MixBlend.first, MixDirection.in); + animation.apply(skeleton, time, time, false, null, 1, MixBlend.first, MixDirection.in, false); skeleton.update(fps); skeleton.updateWorldTransform(Physics.update); 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 29948653c..0ed28cce7 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 @@ -48,7 +48,7 @@ public class SkeletonAttachmentTest extends ApplicationAdapter { Skeleton spineboy, goblin; AnimationState spineboyState, goblinState; - Bone attachmentBone; + BoneApplied attachmentBone; public void create () { camera = new OrthographicCamera(); @@ -87,7 +87,7 @@ public class SkeletonAttachmentTest extends ApplicationAdapter { skeletonAttachment.setSkeleton(goblin); Slot slot = spineboy.findSlot("front-upper-arm"); slot.setAttachment(skeletonAttachment); - attachmentBone = slot.getBone(); + attachmentBone = slot.getBone().getApplied(); } } diff --git a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/TimelineApiTest.java b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/TimelineApiTest.java index 400c8ff17..4681c65f3 100644 --- a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/TimelineApiTest.java +++ b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/TimelineApiTest.java @@ -109,23 +109,24 @@ public class TimelineApiTest extends ApplicationAdapter { skeleton.setX(-50); } else if (time > beforeJump + jump) { // just walk after jump - walkAnimation.apply(skeleton, time, time, true, events, 1, MixBlend.first, MixDirection.in); + walkAnimation.apply(skeleton, time, time, true, events, 1, MixBlend.first, MixDirection.in, false); } else if (time > blendOutStart) { // blend out jump - walkAnimation.apply(skeleton, time, time, true, events, 1, MixBlend.first, MixDirection.in); + walkAnimation.apply(skeleton, time, time, true, events, 1, MixBlend.first, MixDirection.in, false); jumpAnimation.apply(skeleton, time - beforeJump, time - beforeJump, false, events, 1 - (time - blendOutStart) / blendOut, - MixBlend.first, MixDirection.in); + MixBlend.first, MixDirection.in, false); } else if (time > beforeJump + blendIn) { // just jump - jumpAnimation.apply(skeleton, time - beforeJump, time - beforeJump, false, events, 1, MixBlend.first, MixDirection.in); + jumpAnimation.apply(skeleton, time - beforeJump, time - beforeJump, false, events, 1, MixBlend.first, MixDirection.in, + false); } else if (time > beforeJump) { // blend in jump - walkAnimation.apply(skeleton, time, time, true, events, 1, MixBlend.first, MixDirection.in); + walkAnimation.apply(skeleton, time, time, true, events, 1, MixBlend.first, MixDirection.in, false); jumpAnimation.apply(skeleton, time - beforeJump, time - beforeJump, false, events, (time - beforeJump) / blendIn, - MixBlend.first, MixDirection.in); + MixBlend.first, MixDirection.in, false); } else { // just walk before jump - walkAnimation.apply(skeleton, time, time, true, events, 1, MixBlend.first, MixDirection.in); + walkAnimation.apply(skeleton, time, time, true, events, 1, MixBlend.first, MixDirection.in, false); } skeleton.update(delta); diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java index bfef2c07c..008bac261 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java @@ -96,7 +96,7 @@ public class Animation { /** Applies the animation's timelines to the specified skeleton. *

- * See Timeline {@link Timeline#apply(Skeleton, float, float, Array, float, MixBlend, MixDirection)}. + * See Timeline {@link Timeline#apply(Skeleton, float, float, Array, float, MixBlend, MixDirection, boolean)}. * @param skeleton The skeleton the animation is being applied to. This provides access to the bones, slots, and other skeleton * components the timelines may change. * @param lastTime The last time in seconds this animation was applied. Some timelines trigger only at specific times rather @@ -115,7 +115,7 @@ public class Animation { * @param direction Indicates whether the timelines are mixing in or out. Used by timelines which perform instant transitions, * such as {@link DrawOrderTimeline} or {@link AttachmentTimeline}. */ public void apply (Skeleton skeleton, float lastTime, float time, boolean loop, @Null Array events, float alpha, - MixBlend blend, MixDirection direction) { + MixBlend blend, MixDirection direction, boolean appliedPose) { if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); if (loop && duration != 0) { @@ -125,7 +125,7 @@ public class Animation { Object[] timelines = this.timelines.items; for (int i = 0, n = this.timelines.size; i < n; i++) - ((Timeline)timelines[i]).apply(skeleton, lastTime, time, events, alpha, blend, direction); + ((Timeline)timelines[i]).apply(skeleton, lastTime, time, events, alpha, blend, direction, appliedPose); } /** The animation's name, which is unique across all animations in the skeleton. */ @@ -140,7 +140,7 @@ public class Animation { /** Controls how timeline values are mixed with setup pose values or current pose values when a timeline is applied with * alpha < 1. *

- * See Timeline {@link Timeline#apply(Skeleton, float, float, Array, float, MixBlend, MixDirection)}. */ + * See Timeline {@link Timeline#apply(Skeleton, float, float, Array, float, MixBlend, MixDirection, boolean)}. */ static public enum MixBlend { /** Transitions from the setup value to the timeline value (the current value is not used). Before the first frame, the * setup value is set. */ @@ -168,7 +168,7 @@ public class Animation { /** Indicates whether a timeline's alpha is mixing out over time toward 0 (the setup or current pose value) or * mixing in toward 1 (the timeline's value). Some timelines use this to decide how values are applied. *

- * See Timeline {@link Timeline#apply(Skeleton, float, float, Array, float, MixBlend, MixDirection)}. */ + * See Timeline {@link Timeline#apply(Skeleton, float, float, Array, float, MixBlend, MixDirection, boolean)}. */ static public enum MixDirection { in, out } @@ -239,9 +239,10 @@ public class Animation { * apply animations on top of each other (layering). * @param blend Controls how mixing is applied when alpha < 1. * @param direction Indicates whether the timeline is mixing in or out. Used by timelines which perform instant transitions, - * such as {@link DrawOrderTimeline} or {@link AttachmentTimeline}, and others such as {@link ScaleTimeline}. */ + * such as {@link DrawOrderTimeline} or {@link AttachmentTimeline}, and others such as {@link ScaleTimeline}. + * @param appliedPose True to to modify the applied pose. */ abstract public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, - MixBlend blend, MixDirection direction); + MixBlend blend, MixDirection direction, boolean appliedPose); /** Linear search using a stride of 1. * @param time Must be >= the first value in frames. @@ -557,10 +558,13 @@ public class Animation { } public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, - MixDirection direction) { + MixDirection direction, boolean appliedPose) { Bone bone = skeleton.bones.get(boneIndex); - if (bone.active) bone.rotation = getRelativeValue(time, alpha, blend, bone.rotation, bone.data.rotation); + if (bone.active) { + if (appliedPose) bone = bone.applied; + bone.rotation = getRelativeValue(time, alpha, blend, bone.rotation, bone.data.rotation); + } } } @@ -580,10 +584,11 @@ public class Animation { } public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, - MixDirection direction) { + MixDirection direction, boolean appliedPose) { Bone bone = skeleton.bones.get(boneIndex); if (!bone.active) return; + if (appliedPose) bone = bone.applied; float[] frames = this.frames; if (time < frames[0]) { @@ -650,10 +655,13 @@ public class Animation { } public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, - MixDirection direction) { + MixDirection direction, boolean appliedPose) { Bone bone = skeleton.bones.get(boneIndex); - if (bone.active) bone.x = getRelativeValue(time, alpha, blend, bone.x, bone.data.x); + if (bone.active) { + if (appliedPose) bone = bone.applied; + bone.x = getRelativeValue(time, alpha, blend, bone.x, bone.data.x); + } } } @@ -671,10 +679,13 @@ public class Animation { } public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, - MixDirection direction) { + MixDirection direction, boolean appliedPose) { Bone bone = skeleton.bones.get(boneIndex); - if (bone.active) bone.y = getRelativeValue(time, alpha, blend, bone.y, bone.data.y); + if (bone.active) { + if (appliedPose) bone = bone.applied; + bone.y = getRelativeValue(time, alpha, blend, bone.y, bone.data.y); + } } } @@ -694,10 +705,11 @@ public class Animation { } public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, - MixDirection direction) { + MixDirection direction, boolean appliedPose) { Bone bone = skeleton.bones.get(boneIndex); if (!bone.active) return; + if (appliedPose) bone = bone.applied; float[] frames = this.frames; if (time < frames[0]) { @@ -803,10 +815,13 @@ public class Animation { } public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, - MixDirection direction) { + MixDirection direction, boolean appliedPose) { Bone bone = skeleton.bones.get(boneIndex); - if (bone.active) bone.scaleX = getScaleValue(time, alpha, blend, direction, bone.scaleX, bone.data.scaleX); + if (bone.active) { + if (appliedPose) bone = bone.applied; + bone.scaleX = getScaleValue(time, alpha, blend, direction, bone.scaleX, bone.data.scaleX); + } } } @@ -824,10 +839,13 @@ public class Animation { } public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, - MixDirection direction) { + MixDirection direction, boolean appliedPose) { Bone bone = skeleton.bones.get(boneIndex); - if (bone.active) bone.scaleY = getScaleValue(time, alpha, blend, direction, bone.scaleY, bone.data.scaleY); + if (bone.active) { + if (appliedPose) bone = bone.applied; + bone.scaleY = getScaleValue(time, alpha, blend, direction, bone.scaleY, bone.data.scaleY); + } } } @@ -847,10 +865,11 @@ public class Animation { } public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, - MixDirection direction) { + MixDirection direction, boolean appliedPose) { Bone bone = skeleton.bones.get(boneIndex); if (!bone.active) return; + if (appliedPose) bone = bone.applied; float[] frames = this.frames; if (time < frames[0]) { @@ -917,10 +936,13 @@ public class Animation { } public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, - MixDirection direction) { + MixDirection direction, boolean appliedPose) { Bone bone = skeleton.bones.get(boneIndex); - if (bone.active) bone.shearX = getRelativeValue(time, alpha, blend, bone.shearX, bone.data.shearX); + if (bone.active) { + if (appliedPose) bone = bone.applied; + bone.shearX = getRelativeValue(time, alpha, blend, bone.shearX, bone.data.shearX); + } } } @@ -938,10 +960,13 @@ public class Animation { } public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, - MixDirection direction) { + MixDirection direction, boolean appliedPose) { Bone bone = skeleton.bones.get(boneIndex); - if (bone.active) bone.shearY = getRelativeValue(time, alpha, blend, bone.shearY, bone.data.shearY); + if (bone.active) { + if (appliedPose) bone = bone.applied; + bone.shearY = getRelativeValue(time, alpha, blend, bone.shearY, bone.data.shearY); + } } } @@ -975,10 +1000,11 @@ public class Animation { } public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, - MixDirection direction) { + MixDirection direction, boolean appliedPose) { Bone bone = skeleton.bones.get(boneIndex); if (!bone.active) return; + if (appliedPose) bone = bone.applied; if (direction == out) { if (blend == setup) bone.inherit = bone.data.inherit; @@ -1029,10 +1055,11 @@ public class Animation { } public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, - MixDirection direction) { + MixDirection direction, boolean appliedPose) { Slot slot = skeleton.slots.get(slotIndex); if (!slot.bone.active) return; + if (appliedPose) slot = slot.applied; float[] frames = this.frames; Color color = slot.color; @@ -1118,10 +1145,11 @@ public class Animation { } public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, - MixDirection direction) { + MixDirection direction, boolean appliedPose) { Slot slot = skeleton.slots.get(slotIndex); if (!slot.bone.active) return; + if (appliedPose) slot = slot.applied; float[] frames = this.frames; Color color = slot.color; @@ -1197,10 +1225,11 @@ public class Animation { } public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, - MixDirection direction) { + MixDirection direction, boolean appliedPose) { Slot slot = skeleton.slots.get(slotIndex); if (!slot.bone.active) return; + if (appliedPose) slot = slot.applied; float[] frames = this.frames; Color color = slot.color; @@ -1267,10 +1296,11 @@ public class Animation { } public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, - MixDirection direction) { + MixDirection direction, boolean appliedPose) { Slot slot = skeleton.slots.get(slotIndex); if (!slot.bone.active) return; + if (appliedPose) slot = slot.applied; float[] frames = this.frames; Color light = slot.color, dark = slot.darkColor; @@ -1393,10 +1423,11 @@ public class Animation { } public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, - MixDirection direction) { + MixDirection direction, boolean appliedPose) { Slot slot = skeleton.slots.get(slotIndex); if (!slot.bone.active) return; + if (appliedPose) slot = slot.applied; float[] frames = this.frames; Color light = slot.color, dark = slot.darkColor; @@ -1518,10 +1549,11 @@ public class Animation { } public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, - MixDirection direction) { + MixDirection direction, boolean appliedPose) { Slot slot = skeleton.slots.get(slotIndex); if (!slot.bone.active) return; + if (appliedPose) slot = slot.applied; if (direction == out) { if (blend == setup) setAttachment(skeleton, slot, slot.data.attachmentName); @@ -1636,11 +1668,12 @@ public class Animation { } public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, - MixDirection direction) { + MixDirection direction, boolean appliedPose) { Slot slot = skeleton.slots.get(slotIndex); if (!slot.bone.active || !(slot.attachment instanceof VertexAttachment vertexAttachment) || vertexAttachment.getTimelineAttachment() != attachment) return; + if (appliedPose) slot = slot.applied; FloatArray deformArray = slot.deform; if (deformArray.size == 0) blend = setup; @@ -1838,7 +1871,7 @@ public class Animation { /** Fires events for frames > lastTime and <= time. */ public void apply (Skeleton skeleton, float lastTime, float time, @Null Array firedEvents, float alpha, - MixBlend blend, MixDirection direction) { + MixBlend blend, MixDirection direction, boolean appliedPose) { if (firedEvents == null) return; @@ -1846,7 +1879,7 @@ public class Animation { int frameCount = frames.length; if (lastTime > time) { // Apply after lastTime for looped animations. - apply(skeleton, lastTime, Integer.MAX_VALUE, firedEvents, alpha, blend, direction); + apply(skeleton, lastTime, Integer.MAX_VALUE, firedEvents, alpha, blend, direction, appliedPose); lastTime = -1f; } else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame. return; @@ -1899,7 +1932,7 @@ public class Animation { } public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, - MixDirection direction) { + MixDirection direction, boolean appliedPose) { if (direction == out) { if (blend == setup) arraycopy(skeleton.slots.items, 0, skeleton.drawOrder.items, 0, skeleton.slots.size); @@ -1963,10 +1996,11 @@ public class Animation { } public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, - MixDirection direction) { + MixDirection direction, boolean appliedPose) { IkConstraint constraint = skeleton.ikConstraints.get(constraintIndex); if (!constraint.active) return; + if (appliedPose) constraint = constraint.applied; float[] frames = this.frames; if (time < frames[0]) { @@ -2072,10 +2106,11 @@ public class Animation { } public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, - MixDirection direction) { + MixDirection direction, boolean appliedPose) { TransformConstraint constraint = skeleton.transformConstraints.get(constraintIndex); if (!constraint.active) return; + if (appliedPose) constraint = constraint.applied; float[] frames = this.frames; if (time < frames[0]) { @@ -2171,11 +2206,13 @@ public class Animation { } public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, - MixDirection direction) { + MixDirection direction, boolean appliedPose) { PathConstraint constraint = skeleton.pathConstraints.get(constraintIndex); - if (constraint.active) + if (constraint.active) { + if (appliedPose) constraint = constraint.applied; constraint.position = getAbsoluteValue(time, alpha, blend, constraint.position, constraint.data.position); + } } } @@ -2195,11 +2232,13 @@ public class Animation { } public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, - MixDirection direction) { + MixDirection direction, boolean appliedPose) { PathConstraint constraint = skeleton.pathConstraints.get(constraintIndex); - if (constraint.active) + if (constraint.active) { + if (appliedPose) constraint = constraint.applied; constraint.spacing = getAbsoluteValue(time, alpha, blend, constraint.spacing, constraint.data.spacing); + } } } @@ -2238,10 +2277,11 @@ public class Animation { } public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, - MixDirection direction) { + MixDirection direction, boolean appliedPose) { PathConstraint constraint = skeleton.pathConstraints.get(constraintIndex); if (!constraint.active) return; + if (appliedPose) constraint = constraint.applied; float[] frames = this.frames; if (time < frames[0]) { @@ -2314,7 +2354,7 @@ public class Animation { } public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, - MixDirection direction) { + MixDirection direction, boolean appliedPose) { PhysicsConstraint constraint; if (constraintIndex == -1) { @@ -2323,12 +2363,17 @@ public class Animation { Object[] constraints = skeleton.physicsConstraints.items; for (int i = 0, n = skeleton.physicsConstraints.size; i < n; i++) { constraint = (PhysicsConstraint)constraints[i]; - if (constraint.active && global(constraint.data)) + if (constraint.active && global(constraint.data)) { + if (appliedPose) constraint = constraint.applied; set(constraint, getAbsoluteValue(time, alpha, blend, get(constraint), setup(constraint), value)); + } } } else { constraint = skeleton.physicsConstraints.get(constraintIndex); - if (constraint.active) set(constraint, getAbsoluteValue(time, alpha, blend, get(constraint), setup(constraint))); + if (constraint.active) { + if (appliedPose) constraint = constraint.applied; + set(constraint, getAbsoluteValue(time, alpha, blend, get(constraint), setup(constraint))); + } } } @@ -2532,7 +2577,7 @@ public class Animation { /** Resets the physics constraint when frames > lastTime and <= time. */ public void apply (Skeleton skeleton, float lastTime, float time, @Null Array firedEvents, float alpha, - MixBlend blend, MixDirection direction) { + MixBlend blend, MixDirection direction, boolean appliedPose) { PhysicsConstraint constraint = null; if (constraintIndex != -1) { @@ -2543,20 +2588,24 @@ public class Animation { float[] frames = this.frames; if (lastTime > time) { // Apply after lastTime for looped animations. - apply(skeleton, lastTime, Integer.MAX_VALUE, null, alpha, blend, direction); + apply(skeleton, lastTime, Integer.MAX_VALUE, null, alpha, blend, direction, appliedPose); lastTime = -1f; } else if (lastTime >= frames[frames.length - 1]) // Last time is after last frame. return; if (time < frames[0]) return; if (lastTime < frames[0] || time >= frames[search(frames, lastTime) + 1]) { - if (constraint != null) + if (constraint != null) { + if (appliedPose) constraint = constraint.applied; constraint.reset(); - else { + } else { Object[] constraints = skeleton.physicsConstraints.items; for (int i = 0, n = skeleton.physicsConstraints.size; i < n; i++) { constraint = (PhysicsConstraint)constraints[i]; - if (constraint.active) constraint.reset(); + if (constraint.active) { + if (appliedPose) constraint = constraint.applied; + constraint.reset(); + } } } } @@ -2601,10 +2650,12 @@ public class Animation { } public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, - MixDirection direction) { + MixDirection direction, boolean appliedPose) { Slot slot = skeleton.slots.get(slotIndex); if (!slot.bone.active) return; + if (appliedPose) slot = slot.applied; + Attachment slotAttachment = slot.attachment; if (slotAttachment != attachment) { if (!(slotAttachment instanceof VertexAttachment vertexAttachment) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java index e0a2dd9ec..2f9335c70 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java @@ -238,8 +238,10 @@ public class AnimationState { Object timeline = timelines[ii]; if (timeline instanceof AttachmentTimeline attachmentTimeline) applyAttachmentTimeline(attachmentTimeline, skeleton, applyTime, blend, attachments); - else - ((Timeline)timeline).apply(skeleton, animationLast, applyTime, applyEvents, alpha, blend, MixDirection.in); + else { + ((Timeline)timeline).apply(skeleton, animationLast, applyTime, applyEvents, alpha, blend, MixDirection.in, + false); + } } } else { int[] timelineMode = current.timelineMode.items; @@ -258,7 +260,7 @@ public class AnimationState { } else if (timeline instanceof AttachmentTimeline attachmentTimeline) applyAttachmentTimeline(attachmentTimeline, skeleton, applyTime, blend, attachments); else - timeline.apply(skeleton, animationLast, applyTime, applyEvents, alpha, timelineBlend, MixDirection.in); + timeline.apply(skeleton, animationLast, applyTime, applyEvents, alpha, timelineBlend, MixDirection.in, false); } } queueEvents(current, animationTime); @@ -313,7 +315,7 @@ public class AnimationState { if (blend == MixBlend.add) { for (int i = 0; i < timelineCount; i++) - ((Timeline)timelines[i]).apply(skeleton, animationLast, applyTime, events, alphaMix, blend, MixDirection.out); + ((Timeline)timelines[i]).apply(skeleton, animationLast, applyTime, events, alphaMix, blend, MixDirection.out, false); } else { int[] timelineMode = from.timelineMode.items; Object[] timelineHoldMix = from.timelineHoldMix.items; @@ -363,7 +365,7 @@ public class AnimationState { else { if (drawOrder && timeline instanceof DrawOrderTimeline && timelineBlend == MixBlend.setup) direction = MixDirection.in; - timeline.apply(skeleton, animationLast, applyTime, events, alpha, timelineBlend, direction); + timeline.apply(skeleton, animationLast, applyTime, events, alpha, timelineBlend, direction, false); } } } @@ -409,7 +411,7 @@ public class AnimationState { if (firstFrame) timelinesRotation[i] = 0; if (alpha == 1) { - timeline.apply(skeleton, 0, time, null, 1, blend, MixDirection.in); + timeline.apply(skeleton, 0, time, null, 1, blend, MixDirection.in, false); return; } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java index ce4a13e2b..a55b32737 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java @@ -29,41 +29,45 @@ package com.esotericsoftware.spine; -import static com.badlogic.gdx.math.Matrix3.*; -import static com.esotericsoftware.spine.utils.SpineUtils.*; - -import com.badlogic.gdx.math.Matrix3; -import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Null; import com.esotericsoftware.spine.BoneData.Inherit; -import com.esotericsoftware.spine.Skeleton.Physics; /** Stores a bone's current pose. *

* A bone has a local transform which is used to compute its world transform. A bone also has an applied transform, which is a * local transform that can be applied to compute the world transform. The local transform and applied transform may differ if a * constraint or application code modifies the world transform after it was computed from the local transform. */ -public class Bone implements Updatable { +public class Bone { final BoneData data; final Skeleton skeleton; @Null final Bone parent; - final Array children = new Array(); + final Array children; + BoneApplied applied; + float x, y, rotation, scaleX, scaleY, shearX, shearY; - float ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY; - float a, b, worldX; - float c, d, worldY; Inherit inherit; boolean sorted, active; + Bone (Bone bone) { + this.data = bone.data; + this.skeleton = bone.skeleton; + this.parent = bone.parent; + this.children = bone.children; + } + public Bone (BoneData data, Skeleton skeleton, @Null Bone parent) { if (data == null) throw new IllegalArgumentException("data cannot be null."); if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); this.data = data; this.skeleton = skeleton; this.parent = parent; + children = new Array(); + + applied = new BoneApplied(this); + setToSetupPose(); } @@ -73,6 +77,7 @@ public class Bone implements Updatable { if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); this.skeleton = skeleton; this.parent = parent; + children = new Array(); data = bone.data; x = bone.x; y = bone.y; @@ -84,131 +89,6 @@ public class Bone implements Updatable { inherit = bone.inherit; } - /** Computes the world transform using the parent bone and this bone's local applied transform. */ - public void update (Physics physics) { - updateWorldTransform(ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY); - } - - /** Computes the world transform using the parent bone and this bone's local transform. - *

- * See {@link #updateWorldTransform(float, float, float, float, float, float, float)}. */ - public void updateWorldTransform () { - updateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); - } - - /** Computes the world transform using the parent bone and the specified local transform. The applied transform is set to the - * specified local transform. Child bones are not updated. - *

- * See World transforms in the Spine - * Runtimes Guide. */ - public void updateWorldTransform (float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY) { - ax = x; - ay = y; - arotation = rotation; - ascaleX = scaleX; - ascaleY = scaleY; - ashearX = shearX; - ashearY = shearY; - - Bone parent = this.parent; - if (parent == null) { // Root bone. - Skeleton skeleton = this.skeleton; - float sx = skeleton.scaleX, sy = skeleton.scaleY; - float rx = (rotation + shearX) * degRad; - float ry = (rotation + 90 + shearY) * degRad; - a = cos(rx) * scaleX * sx; - b = cos(ry) * scaleY * sx; - c = sin(rx) * scaleX * sy; - d = sin(ry) * scaleY * sy; - worldX = x * sx + skeleton.x; - worldY = y * sy + skeleton.y; - return; - } - - float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d; - worldX = pa * x + pb * y + parent.worldX; - worldY = pc * x + pd * y + parent.worldY; - - switch (inherit) { - case normal -> { - float rx = (rotation + shearX) * degRad; - float ry = (rotation + 90 + shearY) * degRad; - float la = cos(rx) * scaleX; - float lb = cos(ry) * scaleY; - float lc = sin(rx) * scaleX; - float ld = sin(ry) * scaleY; - a = pa * la + pb * lc; - b = pa * lb + pb * ld; - c = pc * la + pd * lc; - d = pc * lb + pd * ld; - return; - } - case onlyTranslation -> { - float rx = (rotation + shearX) * degRad; - float ry = (rotation + 90 + shearY) * degRad; - a = cos(rx) * scaleX; - b = cos(ry) * scaleY; - c = sin(rx) * scaleX; - d = sin(ry) * scaleY; - } - case noRotationOrReflection -> { - float sx = 1 / skeleton.scaleX, sy = 1 / skeleton.scaleY; - pa *= sx; - pc *= sy; - float s = pa * pa + pc * pc, prx; - if (s > 0.0001f) { - s = Math.abs(pa * pd * sy - pb * sx * pc) / s; - pb = pc * s; - pd = pa * s; - prx = atan2Deg(pc, pa); - } else { - pa = 0; - pc = 0; - prx = 90 - atan2Deg(pd, pb); - } - float rx = (rotation + shearX - prx) * degRad; - float ry = (rotation + shearY - prx + 90) * degRad; - float la = cos(rx) * scaleX; - float lb = cos(ry) * scaleY; - float lc = sin(rx) * scaleX; - float ld = sin(ry) * scaleY; - a = pa * la - pb * lc; - b = pa * lb - pb * ld; - c = pc * la + pd * lc; - d = pc * lb + pd * ld; - } - case noScale, noScaleOrReflection -> { - rotation *= degRad; - float cos = cos(rotation), sin = sin(rotation); - float za = (pa * cos + pb * sin) / skeleton.scaleX; - float zc = (pc * cos + pd * sin) / skeleton.scaleY; - float s = (float)Math.sqrt(za * za + zc * zc); - if (s > 0.00001f) s = 1 / s; - za *= s; - zc *= s; - s = (float)Math.sqrt(za * za + zc * zc); - if (inherit == Inherit.noScale && (pa * pd - pb * pc < 0) != (skeleton.scaleX < 0 != skeleton.scaleY < 0)) s = -s; - rotation = PI / 2 + atan2(zc, za); - float zb = cos(rotation) * s; - float zd = sin(rotation) * s; - shearX *= degRad; - shearY = (90 + shearY) * degRad; - float la = cos(shearX) * scaleX; - float lb = cos(shearY) * scaleY; - float lc = sin(shearX) * scaleX; - float ld = sin(shearY) * scaleY; - a = za * la + zb * lc; - b = za * lb + zb * ld; - c = zc * la + zd * lc; - d = zc * lb + zd * ld; - } - } - a *= skeleton.scaleX; - b *= skeleton.scaleX; - c *= skeleton.scaleY; - d *= skeleton.scaleY; - } - /** Sets this bone's local transform to the setup pose. */ public void setToSetupPose () { BoneData data = this.data; @@ -246,8 +126,6 @@ public class Bone implements Updatable { return active; } - // -- Local transform - /** The local x translation. */ public float getX () { return x; @@ -336,309 +214,11 @@ public class Bone implements Updatable { this.inherit = inherit; } - // -- Applied transform - - /** The applied local x translation. */ - public float getAX () { - return ax; + /** Returns the bone for applied pose. */ + public BoneApplied getApplied () { + return applied; } - public void setAX (float ax) { - this.ax = ax; - } - - /** The applied local y translation. */ - public float getAY () { - return ay; - } - - public void setAY (float ay) { - this.ay = ay; - } - - /** The applied local rotation in degrees, counter clockwise. */ - public float getARotation () { - return arotation; - } - - public void setARotation (float arotation) { - this.arotation = arotation; - } - - /** The applied local scaleX. */ - public float getAScaleX () { - return ascaleX; - } - - public void setAScaleX (float ascaleX) { - this.ascaleX = ascaleX; - } - - /** The applied local scaleY. */ - public float getAScaleY () { - return ascaleY; - } - - public void setAScaleY (float ascaleY) { - this.ascaleY = ascaleY; - } - - /** The applied local shearX. */ - public float getAShearX () { - return ashearX; - } - - public void setAShearX (float ashearX) { - this.ashearX = ashearX; - } - - /** The applied local shearY. */ - public float getAShearY () { - return ashearY; - } - - public void setAShearY (float ashearY) { - this.ashearY = ashearY; - } - - /** Computes the applied transform values from the world transform. - *

- * If the world transform is modified (by a constraint, {@link #rotateWorld(float)}, etc) then this method should be called so - * the applied transform matches the world transform. The applied transform may be needed by other code (eg to apply another - * constraint). - *

- * Some information is ambiguous in the world transform, such as -1,-1 scale versus 180 rotation. The applied transform after - * calling this method is equivalent to the local transform used to compute the world transform, but may not be identical. */ - public void updateAppliedTransform () { - Bone parent = this.parent; - if (parent == null) { - ax = worldX - skeleton.x; - ay = worldY - skeleton.y; - float a = this.a, b = this.b, c = this.c, d = this.d; - arotation = atan2Deg(c, a); - ascaleX = (float)Math.sqrt(a * a + c * c); - ascaleY = (float)Math.sqrt(b * b + d * d); - ashearX = 0; - ashearY = atan2Deg(a * b + c * d, a * d - b * c); - return; - } - - float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d; - float pid = 1 / (pa * pd - pb * pc); - float ia = pd * pid, ib = pb * pid, ic = pc * pid, id = pa * pid; - float dx = worldX - parent.worldX, dy = worldY - parent.worldY; - ax = (dx * ia - dy * ib); - ay = (dy * id - dx * ic); - - float ra, rb, rc, rd; - if (inherit == Inherit.onlyTranslation) { - ra = a; - rb = b; - rc = c; - rd = d; - } else { - switch (inherit) { - case noRotationOrReflection -> { - float s = Math.abs(pa * pd - pb * pc) / (pa * pa + pc * pc); - pb = -pc * skeleton.scaleX * s / skeleton.scaleY; - pd = pa * skeleton.scaleY * s / skeleton.scaleX; - pid = 1 / (pa * pd - pb * pc); - ia = pd * pid; - ib = pb * pid; - } - case noScale, noScaleOrReflection -> { - float r = rotation * degRad, cos = cos(r), sin = sin(r); - pa = (pa * cos + pb * sin) / skeleton.scaleX; - pc = (pc * cos + pd * sin) / skeleton.scaleY; - float s = (float)Math.sqrt(pa * pa + pc * pc); - if (s > 0.00001f) s = 1 / s; - pa *= s; - pc *= s; - s = (float)Math.sqrt(pa * pa + pc * pc); - if (inherit == Inherit.noScale && pid < 0 != (skeleton.scaleX < 0 != skeleton.scaleY < 0)) s = -s; - r = PI / 2 + atan2(pc, pa); - pb = cos(r) * s; - pd = sin(r) * s; - pid = 1 / (pa * pd - pb * pc); - ia = pd * pid; - ib = pb * pid; - ic = pc * pid; - id = pa * pid; - } - } - ra = ia * a - ib * c; - rb = ia * b - ib * d; - rc = id * c - ic * a; - rd = id * d - ic * b; - } - - ashearX = 0; - ascaleX = (float)Math.sqrt(ra * ra + rc * rc); - if (ascaleX > 0.0001f) { - float det = ra * rd - rb * rc; - ascaleY = det / ascaleX; - ashearY = -atan2Deg(ra * rb + rc * rd, det); - arotation = atan2Deg(rc, ra); - } else { - ascaleX = 0; - ascaleY = (float)Math.sqrt(rb * rb + rd * rd); - ashearY = 0; - arotation = 90 - atan2Deg(rd, rb); - } - } - - // -- World transform - - /** Part of the world transform matrix for the X axis. If changed, {@link #updateAppliedTransform()} should be called. */ - public float getA () { - return a; - } - - public void setA (float a) { - this.a = a; - } - - /** Part of the world transform matrix for the Y axis. If changed, {@link #updateAppliedTransform()} should be called. */ - public float getB () { - return b; - } - - public void setB (float b) { - this.b = b; - } - - /** Part of the world transform matrix for the X axis. If changed, {@link #updateAppliedTransform()} should be called. */ - public float getC () { - return c; - } - - public void setC (float c) { - this.c = c; - } - - /** Part of the world transform matrix for the Y axis. If changed, {@link #updateAppliedTransform()} should be called. */ - public float getD () { - return d; - } - - public void setD (float d) { - this.d = d; - } - - /** The world X position. If changed, {@link #updateAppliedTransform()} should be called. */ - public float getWorldX () { - return worldX; - } - - public void setWorldX (float worldX) { - this.worldX = worldX; - } - - /** The world Y position. If changed, {@link #updateAppliedTransform()} should be called. */ - public float getWorldY () { - return worldY; - } - - public void setWorldY (float worldY) { - this.worldY = worldY; - } - - /** The world rotation for the X axis, calculated using {@link #a} and {@link #c}. */ - public float getWorldRotationX () { - return atan2Deg(c, a); - } - - /** The world rotation for the Y axis, calculated using {@link #b} and {@link #d}. */ - public float getWorldRotationY () { - return atan2Deg(d, b); - } - - /** The magnitude (always positive) of the world scale X, calculated using {@link #a} and {@link #c}. */ - public float getWorldScaleX () { - return (float)Math.sqrt(a * a + c * c); - } - - /** The magnitude (always positive) of the world scale Y, calculated using {@link #b} and {@link #d}. */ - public float getWorldScaleY () { - return (float)Math.sqrt(b * b + d * d); - } - - public Matrix3 getWorldTransform (Matrix3 worldTransform) { - if (worldTransform == null) throw new IllegalArgumentException("worldTransform cannot be null."); - float[] val = worldTransform.val; - val[M00] = a; - val[M01] = b; - val[M10] = c; - val[M11] = d; - val[M02] = worldX; - val[M12] = worldY; - val[M20] = 0; - val[M21] = 0; - val[M22] = 1; - return worldTransform; - } - - /** Transforms a point from world coordinates to the bone's local coordinates. */ - public Vector2 worldToLocal (Vector2 world) { - if (world == null) throw new IllegalArgumentException("world cannot be null."); - float det = a * d - b * c; - float x = world.x - worldX, y = world.y - worldY; - world.x = (x * d - y * b) / det; - world.y = (y * a - x * c) / det; - return world; - } - - /** Transforms a point from the bone's local coordinates to world coordinates. */ - public Vector2 localToWorld (Vector2 local) { - if (local == null) throw new IllegalArgumentException("local cannot be null."); - float x = local.x, y = local.y; - local.x = x * a + y * b + worldX; - local.y = x * c + y * d + worldY; - return local; - } - - /** Transforms a point from world coordinates to the parent bone's local coordinates. */ - public Vector2 worldToParent (Vector2 world) { - if (world == null) throw new IllegalArgumentException("world cannot be null."); - return parent == null ? world : parent.worldToLocal(world); - } - - /** Transforms a point from the parent bone's coordinates to world coordinates. */ - public Vector2 parentToWorld (Vector2 world) { - if (world == null) throw new IllegalArgumentException("world cannot be null."); - return parent == null ? world : parent.localToWorld(world); - } - - /** Transforms a world rotation to a local rotation. */ - public float worldToLocalRotation (float worldRotation) { - worldRotation *= degRad; - float sin = sin(worldRotation), cos = cos(worldRotation); - return atan2Deg(a * sin - c * cos, d * cos - b * sin) + rotation - shearX; - } - - /** Transforms a local rotation to a world rotation. */ - public float localToWorldRotation (float localRotation) { - localRotation = (localRotation - rotation - shearX) * degRad; - float sin = sin(localRotation), cos = cos(localRotation); - return atan2Deg(cos * c + sin * d, cos * a + sin * b); - } - - /** Rotates the world transform the specified amount. - *

- * After changes are made to the world transform, {@link #updateAppliedTransform()} should be called and - * {@link #update(Physics)} will need to be called on any child bones, recursively. */ - public void rotateWorld (float degrees) { - degrees *= degRad; - float sin = sin(degrees), cos = cos(degrees); - float ra = a, rb = b; - a = cos * ra - sin * c; - b = cos * rb - sin * d; - c = sin * ra + cos * c; - d = sin * rb + cos * d; - } - - // --- - public String toString () { return data.name; } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/BoneApplied.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/BoneApplied.java new file mode 100644 index 000000000..43fff06ca --- /dev/null +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/BoneApplied.java @@ -0,0 +1,372 @@ + +package com.esotericsoftware.spine; + +import static com.badlogic.gdx.math.Matrix3.*; +import static com.esotericsoftware.spine.utils.SpineUtils.*; + +import com.badlogic.gdx.math.Matrix3; +import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.utils.Null; + +import com.esotericsoftware.spine.BoneData.Inherit; +import com.esotericsoftware.spine.Skeleton.Physics; + +public class BoneApplied extends Bone implements Updatable { + final Bone pose; + @Null final BoneApplied parentApplied; + float a, b, worldX; + float c, d, worldY; + + BoneApplied (Bone bone) { + super(bone); + pose = bone; + parentApplied = parent == null ? null : parent.applied; + } + + /** Computes the world transform using the parent bone and this bone's local applied transform. */ + public void updateWorldTransform () { + updateWorldTransform(); + } + + /** Computes the world transform using the parent bone and this bone's local transform. + *

+ * See {@link #updateWorldTransform(float, float, float, float, float, float, float)}. */ + /** Computes the world transform using the parent bone and the specified local transform. The applied transform is set to the + * specified local transform. Child bones are not updated. + *

+ * See World transforms in the Spine + * Runtimes Guide. */ + public void update (Physics physics) { + BoneApplied parent = parentApplied; + if (parent == null) { // Root bone. + Skeleton skeleton = this.skeleton; + float sx = skeleton.scaleX, sy = skeleton.scaleY; + float rx = (rotation + shearX) * degRad; + float ry = (rotation + 90 + shearY) * degRad; + a = cos(rx) * scaleX * sx; + b = cos(ry) * scaleY * sx; + c = sin(rx) * scaleX * sy; + d = sin(ry) * scaleY * sy; + worldX = x * sx + skeleton.x; + worldY = y * sy + skeleton.y; + return; + } + + float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d; + worldX = pa * x + pb * y + parent.worldX; + worldY = pc * x + pd * y + parent.worldY; + + switch (inherit) { + case normal -> { + float rx = (rotation + shearX) * degRad; + float ry = (rotation + 90 + shearY) * degRad; + float la = cos(rx) * scaleX; + float lb = cos(ry) * scaleY; + float lc = sin(rx) * scaleX; + float ld = sin(ry) * scaleY; + a = pa * la + pb * lc; + b = pa * lb + pb * ld; + c = pc * la + pd * lc; + d = pc * lb + pd * ld; + return; + } + case onlyTranslation -> { + float rx = (rotation + shearX) * degRad; + float ry = (rotation + 90 + shearY) * degRad; + a = cos(rx) * scaleX; + b = cos(ry) * scaleY; + c = sin(rx) * scaleX; + d = sin(ry) * scaleY; + } + case noRotationOrReflection -> { + float sx = 1 / skeleton.scaleX, sy = 1 / skeleton.scaleY; + pa *= sx; + pc *= sy; + float s = pa * pa + pc * pc, prx; + if (s > 0.0001f) { + s = Math.abs(pa * pd * sy - pb * sx * pc) / s; + pb = pc * s; + pd = pa * s; + prx = atan2Deg(pc, pa); + } else { + pa = 0; + pc = 0; + prx = 90 - atan2Deg(pd, pb); + } + float rx = (rotation + shearX - prx) * degRad; + float ry = (rotation + shearY - prx + 90) * degRad; + float la = cos(rx) * scaleX; + float lb = cos(ry) * scaleY; + float lc = sin(rx) * scaleX; + float ld = sin(ry) * scaleY; + a = pa * la - pb * lc; + b = pa * lb - pb * ld; + c = pc * la + pd * lc; + d = pc * lb + pd * ld; + } + case noScale, noScaleOrReflection -> { + rotation *= degRad; + float cos = cos(rotation), sin = sin(rotation); + float za = (pa * cos + pb * sin) / skeleton.scaleX; + float zc = (pc * cos + pd * sin) / skeleton.scaleY; + float s = (float)Math.sqrt(za * za + zc * zc); + if (s > 0.00001f) s = 1 / s; + za *= s; + zc *= s; + s = (float)Math.sqrt(za * za + zc * zc); + if (inherit == Inherit.noScale && (pa * pd - pb * pc < 0) != (skeleton.scaleX < 0 != skeleton.scaleY < 0)) s = -s; + rotation = PI / 2 + atan2(zc, za); + float zb = cos(rotation) * s; + float zd = sin(rotation) * s; + shearX *= degRad; + shearY = (90 + shearY) * degRad; + float la = cos(shearX) * scaleX; + float lb = cos(shearY) * scaleY; + float lc = sin(shearX) * scaleX; + float ld = sin(shearY) * scaleY; + a = za * la + zb * lc; + b = za * lb + zb * ld; + c = zc * la + zd * lc; + d = zc * lb + zd * ld; + } + } + a *= skeleton.scaleX; + b *= skeleton.scaleX; + c *= skeleton.scaleY; + d *= skeleton.scaleY; + } + + /** Computes the applied transform values from the world transform. + *

+ * If the world transform is modified (by a constraint, {@link #rotateWorld(float)}, etc) then this method should be called so + * the applied transform matches the world transform. The applied transform may be needed by other code (eg to apply another + * constraint). + *

+ * Some information is ambiguous in the world transform, such as -1,-1 scale versus 180 rotation. The applied transform after + * calling this method is equivalent to the local transform used to compute the world transform, but may not be identical. */ + public void updateAppliedTransform () { + BoneApplied parent = parentApplied; + if (parent == null) { + x = worldX - skeleton.x; + y = worldY - skeleton.y; + float a = this.a, b = this.b, c = this.c, d = this.d; + rotation = atan2Deg(c, a); + scaleX = (float)Math.sqrt(a * a + c * c); + scaleY = (float)Math.sqrt(b * b + d * d); + shearX = 0; + shearY = atan2Deg(a * b + c * d, a * d - b * c); + return; + } + + float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d; + float pid = 1 / (pa * pd - pb * pc); + float ia = pd * pid, ib = pb * pid, ic = pc * pid, id = pa * pid; + float dx = worldX - parent.worldX, dy = worldY - parent.worldY; + x = (dx * ia - dy * ib); + y = (dy * id - dx * ic); + + float ra, rb, rc, rd; + if (inherit == Inherit.onlyTranslation) { + ra = a; + rb = b; + rc = c; + rd = d; + } else { + switch (inherit) { + case noRotationOrReflection -> { + float s = Math.abs(pa * pd - pb * pc) / (pa * pa + pc * pc); + pb = -pc * skeleton.scaleX * s / skeleton.scaleY; + pd = pa * skeleton.scaleY * s / skeleton.scaleX; + pid = 1 / (pa * pd - pb * pc); + ia = pd * pid; + ib = pb * pid; + } + case noScale, noScaleOrReflection -> { + float r = rotation * degRad, cos = cos(r), sin = sin(r); + pa = (pa * cos + pb * sin) / skeleton.scaleX; + pc = (pc * cos + pd * sin) / skeleton.scaleY; + float s = (float)Math.sqrt(pa * pa + pc * pc); + if (s > 0.00001f) s = 1 / s; + pa *= s; + pc *= s; + s = (float)Math.sqrt(pa * pa + pc * pc); + if (inherit == Inherit.noScale && pid < 0 != (skeleton.scaleX < 0 != skeleton.scaleY < 0)) s = -s; + r = PI / 2 + atan2(pc, pa); + pb = cos(r) * s; + pd = sin(r) * s; + pid = 1 / (pa * pd - pb * pc); + ia = pd * pid; + ib = pb * pid; + ic = pc * pid; + id = pa * pid; + } + } + ra = ia * a - ib * c; + rb = ia * b - ib * d; + rc = id * c - ic * a; + rd = id * d - ic * b; + } + + shearX = 0; + scaleX = (float)Math.sqrt(ra * ra + rc * rc); + if (scaleX > 0.0001f) { + float det = ra * rd - rb * rc; + scaleY = det / scaleX; + shearY = -atan2Deg(ra * rb + rc * rd, det); + rotation = atan2Deg(rc, ra); + } else { + scaleX = 0; + scaleY = (float)Math.sqrt(rb * rb + rd * rd); + shearY = 0; + rotation = 90 - atan2Deg(rd, rb); + } + } + + /** Part of the world transform matrix for the X axis. If changed, {@link #updateAppliedTransform()} should be called. */ + public float getA () { + return a; + } + + public void setA (float a) { + this.a = a; + } + + /** Part of the world transform matrix for the Y axis. If changed, {@link #updateAppliedTransform()} should be called. */ + public float getB () { + return b; + } + + public void setB (float b) { + this.b = b; + } + + /** Part of the world transform matrix for the X axis. If changed, {@link #updateAppliedTransform()} should be called. */ + public float getC () { + return c; + } + + public void setC (float c) { + this.c = c; + } + + /** Part of the world transform matrix for the Y axis. If changed, {@link #updateAppliedTransform()} should be called. */ + public float getD () { + return d; + } + + public void setD (float d) { + this.d = d; + } + + /** The world X position. If changed, {@link #updateAppliedTransform()} should be called. */ + public float getWorldX () { + return worldX; + } + + public void setWorldX (float worldX) { + this.worldX = worldX; + } + + /** The world Y position. If changed, {@link #updateAppliedTransform()} should be called. */ + public float getWorldY () { + return worldY; + } + + public void setWorldY (float worldY) { + this.worldY = worldY; + } + + /** The world rotation for the X axis, calculated using {@link #a} and {@link #c}. */ + public float getWorldRotationX () { + return atan2Deg(c, a); + } + + /** The world rotation for the Y axis, calculated using {@link #b} and {@link #d}. */ + public float getWorldRotationY () { + return atan2Deg(d, b); + } + + /** The magnitude (always positive) of the world scale X, calculated using {@link #a} and {@link #c}. */ + public float getWorldScaleX () { + return (float)Math.sqrt(a * a + c * c); + } + + /** The magnitude (always positive) of the world scale Y, calculated using {@link #b} and {@link #d}. */ + public float getWorldScaleY () { + return (float)Math.sqrt(b * b + d * d); + } + + public Matrix3 getWorldTransform (Matrix3 worldTransform) { + if (worldTransform == null) throw new IllegalArgumentException("worldTransform cannot be null."); + float[] val = worldTransform.val; + val[M00] = a; + val[M01] = b; + val[M10] = c; + val[M11] = d; + val[M02] = worldX; + val[M12] = worldY; + val[M20] = 0; + val[M21] = 0; + val[M22] = 1; + return worldTransform; + } + + /** Transforms a point from world coordinates to the bone's local coordinates. */ + public Vector2 worldToLocal (Vector2 world) { + if (world == null) throw new IllegalArgumentException("world cannot be null."); + float det = a * d - b * c; + float x = world.x - worldX, y = world.y - worldY; + world.x = (x * d - y * b) / det; + world.y = (y * a - x * c) / det; + return world; + } + + /** Transforms a point from the bone's local coordinates to world coordinates. */ + public Vector2 localToWorld (Vector2 local) { + if (local == null) throw new IllegalArgumentException("local cannot be null."); + float x = local.x, y = local.y; + local.x = x * a + y * b + worldX; + local.y = x * c + y * d + worldY; + return local; + } + + /** Transforms a point from world coordinates to the parent bone's local coordinates. */ + public Vector2 worldToParent (Vector2 world) { + if (world == null) throw new IllegalArgumentException("world cannot be null."); + return parent == null ? world : parentApplied.worldToLocal(world); + } + + /** Transforms a point from the parent bone's coordinates to world coordinates. */ + public Vector2 parentToWorld (Vector2 world) { + if (world == null) throw new IllegalArgumentException("world cannot be null."); + return parent == null ? world : parentApplied.localToWorld(world); + } + + /** Transforms a world rotation to a local rotation. */ + public float worldToLocalRotation (float worldRotation) { + worldRotation *= degRad; + float sin = sin(worldRotation), cos = cos(worldRotation); + return atan2Deg(a * sin - c * cos, d * cos - b * sin) + rotation - shearX; + } + + /** Transforms a local rotation to a world rotation. */ + public float localToWorldRotation (float localRotation) { + localRotation = (localRotation - rotation - shearX) * degRad; + float sin = sin(localRotation), cos = cos(localRotation); + return atan2Deg(cos * c + sin * d, cos * a + sin * b); + } + + /** Rotates the world transform the specified amount. + *

+ * After changes are made to the world transform, {@link #updateAppliedTransform()} should be called and + * {@link #update(Physics)} will need to be called on any child bones, recursively. */ + public void rotateWorld (float degrees) { + degrees *= degRad; + float sin = sin(degrees), cos = cos(degrees); + float ra = a, rb = b; + a = cos * ra - sin * c; + b = cos * rb - sin * d; + c = sin * ra + cos * c; + d = sin * rb + cos * d; + } +} diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Event.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Event.java index 8bcd74faa..c5dbf7f47 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Event.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Event.java @@ -35,7 +35,7 @@ import com.esotericsoftware.spine.AnimationState.AnimationStateListener; /** Stores the current pose values for an {@link Event}. *

* See Timeline - * {@link Timeline#apply(Skeleton, float, float, com.badlogic.gdx.utils.Array, float, com.esotericsoftware.spine.Animation.MixBlend, com.esotericsoftware.spine.Animation.MixDirection)}, + * {@link Timeline#apply(Skeleton, float, float, com.badlogic.gdx.utils.Array, float, com.esotericsoftware.spine.Animation.MixBlend, com.esotericsoftware.spine.Animation.MixDirection, boolean)}, * AnimationStateListener {@link AnimationStateListener#event(com.esotericsoftware.spine.AnimationState.TrackEntry, Event)}, and * Events in the Spine User Guide. */ public class Event { diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraint.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraint.java index cf91d8c49..256637a5b 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraint.java @@ -42,13 +42,20 @@ import com.esotericsoftware.spine.Skeleton.Physics; * See IK constraints in the Spine User Guide. */ public class IkConstraint implements Updatable { final IkConstraintData data; - final Array bones; - Bone target; + final Array bones; + BoneApplied target; + IkConstraint applied; + boolean active; + int bendDirection; boolean compress, stretch; float mix = 1, softness; - boolean active; + private IkConstraint (IkConstraintData data, Array bones, BoneApplied target) { + this.data = data; + this.bones = bones; + this.target = target; + } public IkConstraint (IkConstraintData data, Skeleton skeleton) { if (data == null) throw new IllegalArgumentException("data cannot be null."); @@ -57,9 +64,11 @@ public class IkConstraint implements Updatable { bones = new Array(data.bones.size); for (BoneData boneData : data.bones) - bones.add(skeleton.bones.get(boneData.index)); + bones.add(skeleton.bones.get(boneData.index).applied); - target = skeleton.bones.get(data.target.index); + target = skeleton.bones.get(data.target.index).applied; + + applied = new IkConstraint(data, bones, target); setToSetupPose(); } @@ -82,17 +91,18 @@ public class IkConstraint implements Updatable { /** Applies the constraint to the constrained bones. */ public void update (Physics physics) { if (mix == 0) return; - Bone target = this.target; + BoneApplied target = this.target; Object[] bones = this.bones.items; switch (this.bones.size) { - case 1 -> apply((Bone)bones[0], target.worldX, target.worldY, compress, stretch, data.uniform, mix); + case 1 -> apply((BoneApplied)bones[0], target.worldX, target.worldY, compress, stretch, data.uniform, mix); case 2 -> // - apply((Bone)bones[0], (Bone)bones[1], target.worldX, target.worldY, bendDirection, stretch, data.uniform, softness, mix); + apply((BoneApplied)bones[0], (BoneApplied)bones[1], target.worldX, target.worldY, bendDirection, stretch, data.uniform, + softness, mix); } } - /** The bones that will be modified by this IK constraint. */ - public Array getBones () { + /** The 1 or 2 bones that will be modified by this IK constraint. */ + public Array getBones () { return bones; } @@ -101,7 +111,7 @@ public class IkConstraint implements Updatable { return target; } - public void setTarget (Bone target) { + public void setTarget (BoneApplied target) { if (target == null) throw new IllegalArgumentException("target cannot be null."); this.target = target; } @@ -171,12 +181,12 @@ public class IkConstraint implements Updatable { } /** Applies 1 bone IK. The target is specified in the world coordinate system. */ - static public void apply (Bone bone, float targetX, float targetY, boolean compress, boolean stretch, boolean uniform, + static public void apply (BoneApplied bone, float targetX, float targetY, boolean compress, boolean stretch, boolean uniform, float alpha) { if (bone == null) throw new IllegalArgumentException("bone cannot be null."); - Bone p = bone.parent; + BoneApplied p = bone.parentApplied; float pa = p.a, pb = p.b, pc = p.c, pd = p.d; - float rotationIK = -bone.ashearX - bone.arotation, tx, ty; + float rotationIK = -bone.shearX - bone.rotation, tx, ty; switch (bone.inherit) { case onlyTranslation: tx = (targetX - bone.worldX) * Math.signum(bone.skeleton.scaleX); @@ -197,17 +207,17 @@ public class IkConstraint implements Updatable { tx = 0; ty = 0; } else { - tx = (x * pd - y * pb) / d - bone.ax; - ty = (y * pa - x * pc) / d - bone.ay; + tx = (x * pd - y * pb) / d - bone.x; + ty = (y * pa - x * pc) / d - bone.y; } } rotationIK += atan2Deg(ty, tx); - if (bone.ascaleX < 0) rotationIK += 180; + if (bone.scaleX < 0) rotationIK += 180; if (rotationIK > 180) rotationIK -= 360; else if (rotationIK < -180) // rotationIK += 360; - float sx = bone.ascaleX, sy = bone.ascaleY; + bone.rotation += rotationIK * alpha; if (compress || stretch) { switch (bone.inherit) { case noScale, noScaleOrReflection -> { @@ -215,27 +225,27 @@ public class IkConstraint implements Updatable { ty = targetY - bone.worldY; } } - float b = bone.data.length * sx; + float b = bone.data.length * bone.scaleX; if (b > 0.0001f) { float dd = tx * tx + ty * ty; if ((compress && dd < b * b) || (stretch && dd > b * b)) { float s = ((float)Math.sqrt(dd) / b - 1) * alpha + 1; - sx *= s; - if (uniform) sy *= s; + bone.scaleX *= s; + if (uniform) bone.scaleY *= s; } } } - bone.updateWorldTransform(bone.ax, bone.ay, bone.arotation + rotationIK * alpha, sx, sy, bone.ashearX, bone.ashearY); + bone.updateWorldTransform(); } /** Applies 2 bone IK. The target is specified in the world coordinate system. * @param child A direct descendant of the parent bone. */ - static public void apply (Bone parent, Bone child, float targetX, float targetY, int bendDir, boolean stretch, boolean uniform, - float softness, float alpha) { + static public void apply (BoneApplied parent, BoneApplied child, float targetX, float targetY, int bendDir, boolean stretch, + boolean uniform, float softness, float alpha) { if (parent == null) throw new IllegalArgumentException("parent cannot be null."); if (child == null) throw new IllegalArgumentException("child cannot be null."); if (parent.inherit != Inherit.normal || child.inherit != Inherit.normal) return; - float px = parent.ax, py = parent.ay, psx = parent.ascaleX, psy = parent.ascaleY, sx = psx, sy = psy, csx = child.ascaleX; + float px = parent.x, py = parent.y, psx = parent.scaleX, psy = parent.scaleY, csx = child.scaleX; int os1, os2, s2; if (psx < 0) { psx = -psx; @@ -254,18 +264,17 @@ public class IkConstraint implements Updatable { os2 = 180; } else os2 = 0; - float cx = child.ax, cy, cwx, cwy, a = parent.a, b = parent.b, c = parent.c, d = parent.d; + float cwx, cwy, a = parent.a, b = parent.b, c = parent.c, d = parent.d; boolean u = Math.abs(psx - psy) <= 0.0001f; if (!u || stretch) { - cy = 0; - cwx = a * cx + parent.worldX; - cwy = c * cx + parent.worldY; + child.y = 0; + cwx = a * child.x + parent.worldX; + cwy = c * child.x + parent.worldY; } else { - cy = child.ay; - cwx = a * cx + b * cy + parent.worldX; - cwy = c * cx + d * cy + parent.worldY; + cwx = a * child.x + b * child.y + parent.worldX; + cwy = c * child.x + d * child.y + parent.worldY; } - Bone pp = parent.parent; + BoneApplied pp = parent.parentApplied; a = pp.a; b = pp.b; c = pp.c; @@ -276,7 +285,8 @@ public class IkConstraint implements Updatable { float l1 = (float)Math.sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2; if (l1 < 0.0001f) { apply(parent, targetX, targetY, false, stretch, false, alpha); - child.updateWorldTransform(cx, cy, 0, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY); + child.rotation = 0; + child.updateWorldTransform(); return; } x = targetX - pp.worldX; @@ -306,8 +316,8 @@ public class IkConstraint implements Updatable { a2 = 0; if (stretch) { a = ((float)Math.sqrt(dd) / (l1 + l2) - 1) * alpha + 1; - sx *= a; - if (uniform) sy *= a; + parent.scaleX *= a; + if (uniform) parent.scaleY *= a; } } else a2 = (float)Math.acos(cos) * bendDir; @@ -364,20 +374,20 @@ public class IkConstraint implements Updatable { a2 = maxAngle * bendDir; } } - float os = atan2(cy, cx) * s2; - float rotation = parent.arotation; - a1 = (a1 - os) * radDeg + os1 - rotation; + float os = atan2(child.y, child.x) * s2; + a1 = (a1 - os) * radDeg + os1 - parent.rotation; if (a1 > 180) a1 -= 360; else if (a1 < -180) // a1 += 360; - parent.updateWorldTransform(px, py, rotation + a1 * alpha, sx, sy, 0, 0); - rotation = child.arotation; - a2 = ((a2 + os) * radDeg - child.ashearX) * s2 + os2 - rotation; + parent.rotation += a1 * alpha; + parent.updateWorldTransform(); + a2 = ((a2 + os) * radDeg - child.shearX) * s2 + os2 - child.rotation; if (a2 > 180) a2 -= 360; else if (a2 < -180) // a2 += 360; - child.updateWorldTransform(cx, cy, rotation + a2 * alpha, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY); + child.rotation += a2 * alpha; + child.updateWorldTransform(); } } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java index 375faef48..ba0f1ba7b 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java @@ -35,6 +35,7 @@ import java.util.Arrays; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.FloatArray; +import com.badlogic.gdx.utils.Null; import com.esotericsoftware.spine.PathConstraintData.PositionMode; import com.esotericsoftware.spine.PathConstraintData.RotateMode; @@ -51,16 +52,23 @@ public class PathConstraint implements Updatable { static final float epsilon = 0.00001f; final PathConstraintData data; - final Array bones; + final Array bones; Slot slot; - float position, spacing, mixRotate, mixX, mixY; - + PathConstraint applied; boolean active; + float position, spacing, mixRotate, mixX, mixY; + private final FloatArray spaces = new FloatArray(), positions = new FloatArray(); private final FloatArray world = new FloatArray(), curves = new FloatArray(), lengths = new FloatArray(); private final float[] segments = new float[10]; + public PathConstraint (PathConstraintData data, Array bones, Slot slot) { + this.data = data; + this.bones = bones; + this.slot = slot; + } + public PathConstraint (PathConstraintData data, Skeleton skeleton) { if (data == null) throw new IllegalArgumentException("data cannot be null."); if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); @@ -68,10 +76,12 @@ public class PathConstraint implements Updatable { bones = new Array(data.bones.size); for (BoneData boneData : data.bones) - bones.add(skeleton.bones.get(boneData.index)); + bones.add(skeleton.bones.get(boneData.index).applied); slot = skeleton.slots.get(data.slot.index); + applied = new PathConstraint(data, bones, slot); + setToSetupPose(); } @@ -108,7 +118,7 @@ public class PathConstraint implements Updatable { case percent -> { if (scale) { for (int i = 0, n = spacesCount - 1; i < n; i++) { - var bone = (Bone)bones[i]; + var bone = (BoneApplied)bones[i]; float setupLength = bone.data.length; float x = setupLength * bone.a, y = setupLength * bone.c; lengths[i] = (float)Math.sqrt(x * x + y * y); @@ -119,7 +129,7 @@ public class PathConstraint implements Updatable { case proportional -> { float sum = 0; for (int i = 0, n = spacesCount - 1; i < n;) { - var bone = (Bone)bones[i]; + var bone = (BoneApplied)bones[i]; float setupLength = bone.data.length; if (setupLength < epsilon) { if (scale) lengths[i] = 0; @@ -141,7 +151,7 @@ public class PathConstraint implements Updatable { default -> { boolean lengthSpacing = data.spacingMode == SpacingMode.length; for (int i = 0, n = spacesCount - 1; i < n;) { - var bone = (Bone)bones[i]; + var bone = (BoneApplied)bones[i]; float setupLength = bone.data.length; if (setupLength < epsilon) { if (scale) lengths[i] = 0; @@ -163,11 +173,11 @@ public class PathConstraint implements Updatable { tip = data.rotateMode == RotateMode.chain; else { tip = false; - Bone p = slot.bone; + BoneApplied p = slot.bone.applied; offsetRotation *= p.a * p.d - p.b * p.c > 0 ? degRad : -degRad; } for (int i = 0, p = 3; i < boneCount; i++, p += 3) { - var bone = (Bone)bones[i]; + var bone = (BoneApplied)bones[i]; bone.worldX += (boneX - bone.worldX) * mixX; bone.worldY += (boneY - bone.worldY) * mixY; float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY; @@ -518,7 +528,7 @@ public class PathConstraint implements Updatable { } /** The bones that will be modified by this path constraint. */ - public Array getBones () { + public Array getBones () { return bones; } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PhysicsConstraint.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PhysicsConstraint.java index e59c7e076..1f8feb3e1 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PhysicsConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PhysicsConstraint.java @@ -38,7 +38,11 @@ import com.esotericsoftware.spine.Skeleton.Physics; * See Physics constraints in the Spine User Guide. */ public class PhysicsConstraint implements Updatable { final PhysicsConstraintData data; - Bone bone; + final Skeleton skeleton; + BoneApplied bone; + PhysicsConstraint applied; + boolean active; + float inertia, strength, damping, massInverse, wind, gravity, mix; boolean reset = true; @@ -47,19 +51,23 @@ public class PhysicsConstraint implements Updatable { float yOffset, yVelocity; float rotateOffset, rotateVelocity; float scaleOffset, scaleVelocity; - - boolean active; - - final Skeleton skeleton; float remaining, lastTime; + private PhysicsConstraint (PhysicsConstraintData data, Skeleton skeleton, BoneApplied bone) { + this.data = data; + this.skeleton = skeleton; + this.bone = bone; + } + public PhysicsConstraint (PhysicsConstraintData data, Skeleton skeleton) { if (data == null) throw new IllegalArgumentException("data cannot be null."); if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); this.data = data; this.skeleton = skeleton; - bone = skeleton.bones.get(data.bone.index); + bone = skeleton.bones.get(data.bone.index).applied; + + applied = new PhysicsConstraint(data, skeleton, bone); setToSetupPose(); } @@ -118,7 +126,7 @@ public class PhysicsConstraint implements Updatable { if (mix == 0) return; boolean x = data.x > 0, y = data.y > 0, rotateOrShearX = data.rotate > 0 || data.shearX > 0, scaleX = data.scaleX > 0; - Bone bone = this.bone; + BoneApplied bone = this.bone; float l = bone.data.length; switch (physics) { @@ -278,11 +286,11 @@ public class PhysicsConstraint implements Updatable { } /** The bone constrained by this physics constraint. */ - public Bone getBone () { + public BoneApplied getBone () { return bone; } - public void setBone (Bone bone) { + public void setBone (BoneApplied bone) { this.bone = bone; } 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 dac59fd07..e07d73978 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java @@ -37,6 +37,8 @@ import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.FloatArray; import com.badlogic.gdx.utils.Null; +import com.esotericsoftware.spine.Animation.BoneTimeline; +import com.esotericsoftware.spine.Animation.Timeline; import com.esotericsoftware.spine.Skin.SkinEntry; import com.esotericsoftware.spine.attachments.Attachment; import com.esotericsoftware.spine.attachments.ClippingAttachment; @@ -55,6 +57,7 @@ public class Skeleton { final Array bones; final Array slots; Array drawOrder; + final Array sliders; final Array ikConstraints; final Array transformConstraints; final Array pathConstraints; @@ -85,27 +88,30 @@ public class Skeleton { slots = new Array(data.slots.size); drawOrder = new Array(data.slots.size); for (SlotData slotData : data.slots) { - var bone = (Bone)bones[slotData.boneData.index]; - var slot = new Slot(slotData, bone); + var slot = new Slot(slotData, this); slots.add(slot); drawOrder.add(slot); } + sliders = new Array(data.sliders.size); + for (SliderData constraint : data.sliders) + sliders.add(new Slider(constraint, this)); + ikConstraints = new Array(data.ikConstraints.size); - for (IkConstraintData ikConstraintData : data.ikConstraints) - ikConstraints.add(new IkConstraint(ikConstraintData, this)); + for (IkConstraintData constraint : data.ikConstraints) + ikConstraints.add(new IkConstraint(constraint, this)); transformConstraints = new Array(data.transformConstraints.size); - for (TransformConstraintData transformConstraintData : data.transformConstraints) - transformConstraints.add(new TransformConstraint(transformConstraintData, this)); + for (TransformConstraintData constraint : data.transformConstraints) + transformConstraints.add(new TransformConstraint(constraint, this)); pathConstraints = new Array(data.pathConstraints.size); - for (PathConstraintData pathConstraintData : data.pathConstraints) - pathConstraints.add(new PathConstraint(pathConstraintData, this)); + for (PathConstraintData constraint : data.pathConstraints) + pathConstraints.add(new PathConstraint(constraint, this)); physicsConstraints = new Array(data.physicsConstraints.size); - for (PhysicsConstraintData physicsConstraintData : data.physicsConstraints) - physicsConstraints.add(new PhysicsConstraint(physicsConstraintData, this)); + for (PhysicsConstraintData constraint : data.physicsConstraints) + physicsConstraints.add(new PhysicsConstraint(constraint, this)); color = new Color(1, 1, 1, 1); @@ -140,21 +146,25 @@ public class Skeleton { for (Slot slot : skeleton.drawOrder) drawOrder.add(slots.get(slot.data.index)); + sliders = new Array(skeleton.sliders.size); + for (Slider constraint : skeleton.sliders) + sliders.add(new Slider(constraint, skeleton)); + ikConstraints = new Array(skeleton.ikConstraints.size); - for (IkConstraint ikConstraint : skeleton.ikConstraints) - ikConstraints.add(new IkConstraint(ikConstraint, skeleton)); + for (IkConstraint constraint : skeleton.ikConstraints) + ikConstraints.add(new IkConstraint(constraint, skeleton)); transformConstraints = new Array(skeleton.transformConstraints.size); - for (TransformConstraint transformConstraint : skeleton.transformConstraints) - transformConstraints.add(new TransformConstraint(transformConstraint, skeleton)); + for (TransformConstraint constraint : skeleton.transformConstraints) + transformConstraints.add(new TransformConstraint(constraint, skeleton)); pathConstraints = new Array(skeleton.pathConstraints.size); - for (PathConstraint pathConstraint : skeleton.pathConstraints) - pathConstraints.add(new PathConstraint(pathConstraint, skeleton)); + for (PathConstraint constraint : skeleton.pathConstraints) + pathConstraints.add(new PathConstraint(constraint, skeleton)); physicsConstraints = new Array(skeleton.physicsConstraints.size); - for (PhysicsConstraint physicsConstraint : skeleton.physicsConstraints) - physicsConstraints.add(new PhysicsConstraint(physicsConstraint, skeleton)); + for (PhysicsConstraint constraint : skeleton.physicsConstraints) + physicsConstraints.add(new PhysicsConstraint(constraint, skeleton)); skin = skeleton.skin; color = new Color(skeleton.color); @@ -192,13 +202,21 @@ public class Skeleton { } } - int ikCount = ikConstraints.size, transformCount = transformConstraints.size, pathCount = pathConstraints.size, - physicsCount = physicsConstraints.size; - Object[] ikConstraints = this.ikConstraints.items, transformConstraints = this.transformConstraints.items, - pathConstraints = this.pathConstraints.items, physicsConstraints = this.physicsConstraints.items; + int sliderCount = sliders.size, ikCount = ikConstraints.size, transformCount = transformConstraints.size, + pathCount = pathConstraints.size, physicsCount = physicsConstraints.size; + Object[] sliders = this.sliders.items, ikConstraints = this.ikConstraints.items, + transformConstraints = this.transformConstraints.items, pathConstraints = this.pathConstraints.items, + physicsConstraints = this.physicsConstraints.items; int constraintCount = ikCount + transformCount + pathCount + physicsCount; outer: for (int i = 0; i < constraintCount; i++) { + for (int ii = 0; ii < sliderCount; ii++) { + var constraint = (Slider)sliders[ii]; + if (constraint.data.order == i) { + sortSlider(constraint); + continue outer; + } + } for (int ii = 0; ii < ikCount; ii++) { var constraint = (IkConstraint)ikConstraints[ii]; if (constraint.data.order == i) { @@ -233,21 +251,42 @@ public class Skeleton { sortBone((Bone)bones[i]); } + private void sortSlider (Slider constraint) { + constraint.active = !constraint.data.skinRequired || (skin != null && skin.constraints.contains(constraint.data, true)); + if (!constraint.active) return; + + Object[] bones = this.bones.items; + for (Timeline timeline : constraint.animation.timelines) + if (timeline instanceof BoneTimeline boneTimeline) sortBone((Bone)bones[boneTimeline.getBoneIndex()]); + + updateCache.add(constraint); + + for (Timeline timeline : constraint.animation.timelines) { + if (timeline instanceof BoneTimeline boneTimeline) { + var bone = (Bone)bones[boneTimeline.getBoneIndex()]; + sortReset(bone.children); + bone.sorted = false; + } + } + for (Timeline timeline : constraint.animation.timelines) + if (timeline instanceof BoneTimeline boneTimeline) sortBone((Bone)bones[boneTimeline.getBoneIndex()]); + } + private void sortIkConstraint (IkConstraint constraint) { constraint.active = constraint.target.active && (!constraint.data.skinRequired || (skin != null && skin.constraints.contains(constraint.data, true))); if (!constraint.active) return; - sortBone(constraint.target); + sortBone(constraint.target.pose); - Array constrained = constraint.bones; - Bone parent = constrained.first(); + Array constrained = constraint.bones; + Bone parent = constrained.first().pose; sortBone(parent); if (constrained.size == 1) { updateCache.add(constraint); sortReset(parent.children); } else { - Bone child = constrained.peek(); + Bone child = constrained.peek().pose; sortBone(child); updateCache.add(constraint); @@ -262,27 +301,27 @@ public class Skeleton { && (!constraint.data.skinRequired || (skin != null && skin.constraints.contains(constraint.data, true))); if (!constraint.active) return; - sortBone(constraint.source); + sortBone(constraint.source.pose); Object[] constrained = constraint.bones.items; int boneCount = constraint.bones.size; if (constraint.data.localSource) { for (int i = 0; i < boneCount; i++) { - var child = (Bone)constrained[i]; + Bone child = ((BoneApplied)constrained[i]).pose; sortBone(child.parent); sortBone(child); } } else { for (int i = 0; i < boneCount; i++) - sortBone((Bone)constrained[i]); + sortBone(((BoneApplied)constrained[i]).pose); } updateCache.add(constraint); for (int i = 0; i < boneCount; i++) - sortReset(((Bone)constrained[i]).children); + sortReset(((BoneApplied)constrained[i]).children); for (int i = 0; i < boneCount; i++) - ((Bone)constrained[i]).sorted = true; + ((BoneApplied)constrained[i]).pose.sorted = true; } private void sortPathConstraint (PathConstraint constraint) { @@ -302,7 +341,7 @@ public class Skeleton { Object[] constrained = constraint.bones.items; int boneCount = constraint.bones.size; for (int i = 0; i < boneCount; i++) - sortBone((Bone)constrained[i]); + sortBone(((BoneApplied)constrained[i]).pose); updateCache.add(constraint); @@ -331,13 +370,13 @@ public class Skeleton { int nn = pathBones[i++]; nn += i; while (i < nn) - sortBone((Bone)bones[pathBones[i++]]); + sortBone(((BoneApplied)bones[pathBones[i++]]).pose); } } } private void sortPhysicsConstraint (PhysicsConstraint constraint) { - Bone bone = constraint.bone; + Bone bone = constraint.bone.pose; constraint.active = bone.active && (!constraint.data.skinRequired || (skin != null && skin.constraints.contains(constraint.data, true))); if (!constraint.active) return; @@ -355,7 +394,7 @@ public class Skeleton { Bone parent = bone.parent; if (parent != null) sortBone(parent); bone.sorted = true; - updateCache.add(bone); + updateCache.add(bone.applied); } private void sortReset (Array bones) { @@ -376,14 +415,29 @@ public class Skeleton { Object[] bones = this.bones.items; for (int i = 0, n = this.bones.size; i < n; i++) { var bone = (Bone)bones[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; + if (!bone.active) continue; + BoneApplied applied = bone.applied; + applied.x = bone.x; + applied.y = bone.y; + applied.rotation = bone.rotation; + applied.scaleX = bone.scaleX; + applied.scaleY = bone.scaleY; + applied.shearX = bone.shearX; + applied.shearY = bone.shearY; + applied.inherit = bone.inherit; } + Object[] slots = this.slots.items; + for (int i = 0, n = this.slots.size; i < n; i++) { + var slot = (Slot)slots[i]; + if (!slot.bone.active) continue; + Slot applied = slot.applied; + applied.color.set(slot.color); + if (applied.darkColor != null) applied.darkColor.set(slot.darkColor); + applied.attachment = slot.attachment; + applied.sequenceIndex = slot.sequenceIndex; + applied.deform = slot.deform; + } + // BOZO! - Reset the rest. Object[] updateCache = this.updateCache.items; for (int i = 0, n = this.updateCache.size; i < n; i++) @@ -395,23 +449,26 @@ public class Skeleton { *

* See World transforms in the Spine * Runtimes Guide. */ - public void updateWorldTransform (Physics physics, Bone parent) { + public void updateWorldTransform (Physics physics, BoneApplied parent) { if (parent == null) throw new IllegalArgumentException("parent cannot be null."); Object[] bones = this.bones.items; for (int i = 1, n = this.bones.size; i < n; i++) { // Skip root bone. var bone = (Bone)bones[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; + BoneApplied applied = bone.applied; + applied.x = bone.x; + applied.y = bone.y; + applied.rotation = bone.rotation; + applied.scaleX = bone.scaleX; + applied.scaleY = bone.scaleY; + applied.shearX = bone.shearX; + applied.shearY = bone.shearY; + applied.inherit = bone.inherit; } + // BOZO! - Reset the rest. // Apply the parent bone transform to the root bone. The root bone always inherits scale, rotation and reflection. - Bone rootBone = getRootBone(); + BoneApplied rootBone = getRootBone().applied; 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; @@ -447,21 +504,25 @@ public class Skeleton { for (int i = 0, n = this.bones.size; i < n; i++) ((Bone)bones[i]).setToSetupPose(); - Object[] ikConstraints = this.ikConstraints.items; - for (int i = 0, n = this.ikConstraints.size; i < n; i++) - ((IkConstraint)ikConstraints[i]).setToSetupPose(); + Object[] constraints = sliders.items; + for (int i = 0, n = sliders.size; i < n; i++) + ((Slider)constraints[i]).setToSetupPose(); - Object[] transformConstraints = this.transformConstraints.items; - for (int i = 0, n = this.transformConstraints.size; i < n; i++) - ((TransformConstraint)transformConstraints[i]).setToSetupPose(); + constraints = ikConstraints.items; + for (int i = 0, n = ikConstraints.size; i < n; i++) + ((IkConstraint)constraints[i]).setToSetupPose(); - Object[] pathConstraints = this.pathConstraints.items; - for (int i = 0, n = this.pathConstraints.size; i < n; i++) - ((PathConstraint)pathConstraints[i]).setToSetupPose(); + constraints = transformConstraints.items; + for (int i = 0, n = transformConstraints.size; i < n; i++) + ((TransformConstraint)constraints[i]).setToSetupPose(); - Object[] physicsConstraints = this.physicsConstraints.items; - for (int i = 0, n = this.physicsConstraints.size; i < n; i++) - ((PhysicsConstraint)physicsConstraints[i]).setToSetupPose(); + constraints = pathConstraints.items; + for (int i = 0, n = pathConstraints.size; i < n; i++) + ((PathConstraint)constraints[i]).setToSetupPose(); + + constraints = physicsConstraints.items; + for (int i = 0, n = physicsConstraints.size; i < n; i++) + ((PhysicsConstraint)constraints[i]).setToSetupPose(); } /** Sets the slots and draw order to their setup pose values. */ @@ -617,6 +678,23 @@ public class Skeleton { slot.setAttachment(attachment); } + /** The skeleton's sliders. */ + public Array getSliders () { + return sliders; + } + + /** Finds a slider by comparing each slider's name. It is more efficient to cache the results of this method than to call it + * repeatedly. */ + public @Null Slider findSlider (String constraintName) { + if (constraintName == null) throw new IllegalArgumentException("constraintName cannot be null."); + Object[] sliders = this.sliders.items; + for (int i = 0, n = this.sliders.size; i < n; i++) { + var slider = (Slider)sliders[i]; + if (slider.data.name.equals(constraintName)) return slider; + } + return null; + } + /** The skeleton's IK constraints. */ public Array getIkConstraints () { return ikConstraints; diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonData.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonData.java index e11d7420a..21acc90ec 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonData.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonData.java @@ -44,6 +44,7 @@ public class SkeletonData { @Null Skin defaultSkin; final Array events = new Array(); final Array animations = new Array(); + final Array sliders = new Array(); final Array ikConstraints = new Array(); final Array transformConstraints = new Array(); final Array pathConstraints = new Array(); @@ -159,6 +160,25 @@ public class SkeletonData { return null; } + // --- Sliders + + /** The skeleton's sliders. */ + public Array getSliders () { + return sliders; + } + + /** Finds a slider by comparing each IK constraint's name. It is more efficient to cache the results of this method than to + * call it multiple times. */ + public @Null SliderData findSlider (String constraintName) { + if (constraintName == null) throw new IllegalArgumentException("constraintName cannot be null."); + Object[] sliders = this.sliders.items; + for (int i = 0, n = this.sliders.size; i < n; i++) { + var constraint = (SliderData)sliders[i]; + if (constraint.name.equals(constraintName)) return constraint; + } + return null; + } + // --- IK constraints /** The skeleton's IK constraints. */ diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRendererDebug.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRendererDebug.java index 3348eff7b..28c6380d8 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRendererDebug.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRendererDebug.java @@ -80,8 +80,8 @@ public class SkeletonRendererDebug { Gdx.gl.glBlendFunc(srcFunc, GL20.GL_ONE_MINUS_SRC_ALPHA); ShapeRenderer shapes = this.shapes; - Array bones = skeleton.getBones(); - Array slots = skeleton.getSlots(); + Array bones = skeleton.bones; + Array slots = skeleton.slots; shapes.begin(ShapeType.Filled); @@ -96,11 +96,12 @@ public class SkeletonRendererDebug { shapes.setColor(boneOriginColor); } else shapes.setColor(boneLineColor); - float x = length * bone.a + bone.worldX; - float y = length * bone.c + bone.worldY; - shapes.rectLine(bone.worldX, bone.worldY, x, y, width * scale); + BoneApplied applied = bone.applied; + float x = length * applied.a + applied.worldX; + float y = length * applied.c + applied.worldY; + shapes.rectLine(applied.worldX, applied.worldY, x, y, width * scale); } - shapes.x(skeleton.getX(), skeleton.getY(), 4 * scale); + shapes.x(skeleton.x, skeleton.y, 4 * scale); } if (drawPoints) { @@ -110,8 +111,8 @@ public class SkeletonRendererDebug { if (!slot.bone.active) continue; Attachment attachment = slot.attachment; if (!(attachment instanceof PointAttachment point)) continue; - point.computeWorldPosition(slot.getBone(), temp1); - temp2.set(8, 0).rotate(point.computeWorldRotation(slot.getBone())); + point.computeWorldPosition(slot.bone.applied, temp1); + temp2.set(8, 0).rotate(point.computeWorldRotation(slot.bone.applied)); shapes.rectLine(temp1, temp2, boneWidth / 2 * scale); } } @@ -244,7 +245,7 @@ public class SkeletonRendererDebug { for (int i = 0, n = bones.size; i < n; i++) { Bone bone = bones.get(i); if (!bone.active) continue; - shapes.circle(bone.worldX, bone.worldY, 3 * scale, 8); + shapes.circle(bone.applied.worldX, bone.applied.worldY, 3 * scale, 8); } } @@ -255,7 +256,7 @@ public class SkeletonRendererDebug { if (!slot.bone.active) continue; Attachment attachment = slot.attachment; if (!(attachment instanceof PointAttachment point)) continue; - point.computeWorldPosition(slot.getBone(), temp1); + point.computeWorldPosition(slot.bone.applied, temp1); shapes.circle(temp1.x, temp1.y, 3 * scale, 8); } } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Slider.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Slider.java new file mode 100644 index 000000000..d3ce3997a --- /dev/null +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Slider.java @@ -0,0 +1,100 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +package com.esotericsoftware.spine; + +import com.esotericsoftware.spine.Animation.MixBlend; +import com.esotericsoftware.spine.Animation.MixDirection; +import com.esotericsoftware.spine.Skeleton.Physics; + +/** Stores the setup pose for a {@link PhysicsConstraint}. + *

+ * See Physics constraints in the Spine User Guide. */ +public class Slider implements Updatable { + final SliderData data; + final Skeleton skeleton; + Animation animation; + float time, mix; + + boolean active; + + public Slider (SliderData data, Skeleton skeleton) { + if (data == null) throw new IllegalArgumentException("data cannot be null."); + if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); + this.data = data; + this.skeleton = skeleton; + + setToSetupPose(); + } + + /** Copy constructor. */ + public Slider (Slider slider, Skeleton skeleton) { + this(slider.data, skeleton); + setToSetupPose(); + } + + public void update (Physics physics) { + animation.apply(skeleton, time, time, false, null, mix, MixBlend.replace, MixDirection.in, true); + } + + public void setToSetupPose () { + SliderData data = this.data; + animation = data.animation; + time = data.time; + mix = data.mix; + } + + public boolean isActive () { + return true; + } + + public Animation getAnimation () { + return animation; + } + + public void setAnimation (Animation animation) { + this.animation = animation; + } + + public float getTime () { + return time; + } + + public void setTime (float time) { + this.time = time; + } + + public float getMix () { + return mix; + } + + public void setMix (float mix) { + this.mix = mix; + } +} diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SliderData.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SliderData.java new file mode 100644 index 000000000..321a11d62 --- /dev/null +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SliderData.java @@ -0,0 +1,66 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +package com.esotericsoftware.spine; + +/** Stores the setup pose for a {@link PhysicsConstraint}. + *

+ * See Physics constraints in the Spine User Guide. */ +public class SliderData extends ConstraintData { + Animation animation; + float time, mix; + + public SliderData (String name) { + super(name); + } + + public Animation getAnimation () { + return animation; + } + + public void setAnimation (Animation animation) { + this.animation = animation; + } + + public float getTime () { + return time; + } + + public void setTime (float time) { + this.time = time; + } + + public float getMix () { + return mix; + } + + public void setMix (float mix) { + this.mix = mix; + } +} diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Slot.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Slot.java index 5b35f37e8..bc13a3065 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Slot.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Slot.java @@ -44,20 +44,34 @@ import com.esotericsoftware.spine.attachments.VertexAttachment; public class Slot { final SlotData data; final Bone bone; - Color color = new Color(); + Slot applied; + + final Color color = new Color(); @Null final Color darkColor; @Null Attachment attachment; int sequenceIndex; - FloatArray deform = new FloatArray(); + FloatArray deform; int attachmentState; - public Slot (SlotData data, Bone bone) { - if (data == null) throw new IllegalArgumentException("data cannot be null."); - if (bone == null) throw new IllegalArgumentException("bone cannot be null."); + private Slot (SlotData data, Bone bone) { this.data = data; this.bone = bone; + darkColor = data.darkColor == null ? null : new Color(); + } + + public Slot (SlotData data, Skeleton skeleton) { + if (data == null) throw new IllegalArgumentException("data cannot be null."); + if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); + this.data = data; + this.bone = skeleton.bones.get(data.boneData.index); + + darkColor = data.darkColor == null ? null : new Color(); + deform = new FloatArray(); + + applied = new Slot(data, bone); + setToSetupPose(); } @@ -71,7 +85,7 @@ public class Slot { darkColor = slot.darkColor == null ? null : new Color(slot.darkColor); attachment = slot.attachment; sequenceIndex = slot.sequenceIndex; - deform.addAll(slot.deform); + deform = new FloatArray(slot.deform); } /** The slot's setup pose data. */ @@ -95,10 +109,6 @@ public class Slot { return color; } - public void setColor (Color color) { - this.color = color; - } - /** The dark color used to tint the slot's attachment for two color tinting, or null if two color tinting is not used. The dark * color's alpha is not used. */ public @Null Color getDarkColor () { @@ -158,6 +168,11 @@ public class Slot { } } + /** Returns the bone for applied pose. */ + public Slot getApplied () { + return applied; + } + public String toString () { return data.name; } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraint.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraint.java index 8da1b1d2e..82e5534b4 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraint.java @@ -31,8 +31,8 @@ package com.esotericsoftware.spine; import static com.badlogic.gdx.math.MathUtils.*; -import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.Null; import com.esotericsoftware.spine.Skeleton.Physics; import com.esotericsoftware.spine.TransformConstraintData.FromProperty; @@ -44,12 +44,18 @@ import com.esotericsoftware.spine.TransformConstraintData.ToProperty; * See Transform constraints in the Spine User Guide. */ public class TransformConstraint implements Updatable { final TransformConstraintData data; - final Array bones; - Bone source; + final Array bones; + BoneApplied source; + TransformConstraint applied; + boolean active; + float mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY; - boolean active; - final Vector2 temp = new Vector2(); + public TransformConstraint (TransformConstraintData data, Array bones, BoneApplied source) { + this.data = data; + this.bones = bones; + this.source = source; + } public TransformConstraint (TransformConstraintData data, Skeleton skeleton) { if (data == null) throw new IllegalArgumentException("data cannot be null."); @@ -58,9 +64,11 @@ public class TransformConstraint implements Updatable { bones = new Array(data.bones.size); for (BoneData boneData : data.bones) - bones.add(skeleton.bones.get(boneData.index)); + bones.add(skeleton.bones.get(boneData.index).applied); - source = skeleton.bones.get(data.source.index); + source = skeleton.bones.get(data.source.index).applied; + + applied = new TransformConstraint(data, bones, source); setToSetupPose(); } @@ -87,12 +95,12 @@ public class TransformConstraint implements Updatable { TransformConstraintData data = this.data; boolean localFrom = data.localSource, localTarget = data.localTarget, additive = data.additive, clamp = data.clamp; - Bone source = this.source; + BoneApplied source = this.source; Object[] fromItems = data.properties.items; int fn = data.properties.size; Object[] bones = this.bones.items; for (int i = 0, n = this.bones.size; i < n; i++) { - var bone = (Bone)bones[i]; + var bone = (BoneApplied)bones[i]; for (int f = 0; f < fn; f++) { var from = (FromProperty)fromItems[f]; float value = from.value(data, source, localFrom) - from.offset; @@ -119,16 +127,16 @@ public class TransformConstraint implements Updatable { } /** The bones that will be modified by this transform constraint. */ - public Array getBones () { + public Array getBones () { return bones; } /** The bone whose world transform will be copied to the constrained bones. */ - public Bone getSource () { + public BoneApplied getSource () { return source; } - public void setSource (Bone source) { + public void setSource (BoneApplied source) { if (source == null) throw new IllegalArgumentException("source cannot be null."); this.source = source; } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraintData.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraintData.java index fefb1a483..17c5baebe 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraintData.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraintData.java @@ -221,7 +221,7 @@ public class TransformConstraintData extends ConstraintData { public final Array to = new Array(); /** Reads this property from the specified bone. */ - abstract public float value (TransformConstraintData data, Bone source, boolean local); + abstract public float value (TransformConstraintData data, BoneApplied source, boolean local); } /** Constrained property for a {@link TransformConstraint}. */ @@ -239,12 +239,12 @@ public class TransformConstraintData extends ConstraintData { abstract public float mix (TransformConstraint constraint); /** Applies the value to this property. */ - abstract public void apply (TransformConstraint constraint, Bone bone, float value, boolean local, boolean additive); + abstract public void apply (TransformConstraint constraint, BoneApplied bone, float value, boolean local, boolean additive); } static public class FromRotate extends FromProperty { - public float value (TransformConstraintData data, Bone source, boolean local) { - if (local) return source.arotation + data.offsetRotation; + public float value (TransformConstraintData data, BoneApplied source, boolean local) { + if (local) return source.rotation + data.offsetRotation; float value = atan2(source.c, source.a) * radDeg + (source.a * source.d - source.b * source.c > 0 ? data.offsetRotation : -data.offsetRotation); if (value < 0) value += 360; @@ -257,10 +257,10 @@ public class TransformConstraintData extends ConstraintData { return constraint.mixRotate; } - public void apply (TransformConstraint constraint, Bone bone, float value, boolean local, boolean additive) { + public void apply (TransformConstraint constraint, BoneApplied bone, float value, boolean local, boolean additive) { if (local) { - if (!additive) value -= bone.arotation; - bone.arotation += value * constraint.mixRotate; + if (!additive) value -= bone.rotation; + bone.rotation += value * constraint.mixRotate; } else { float a = bone.a, b = bone.b, c = bone.c, d = bone.d; value *= degRad; @@ -280,8 +280,8 @@ public class TransformConstraintData extends ConstraintData { } static public class FromX extends FromProperty { - public float value (TransformConstraintData data, Bone source, boolean local) { - return local ? source.ax + data.offsetX : data.offsetX * source.a + data.offsetY * source.b + source.worldX; + public float value (TransformConstraintData data, BoneApplied source, boolean local) { + return local ? source.x + data.offsetX : data.offsetX * source.a + data.offsetY * source.b + source.worldX; } } @@ -290,10 +290,10 @@ public class TransformConstraintData extends ConstraintData { return constraint.mixX; } - public void apply (TransformConstraint constraint, Bone bone, float value, boolean local, boolean additive) { + public void apply (TransformConstraint constraint, BoneApplied bone, float value, boolean local, boolean additive) { if (local) { - if (!additive) value -= bone.ax; - bone.ax += value * constraint.mixX; + if (!additive) value -= bone.x; + bone.x += value * constraint.mixX; } else { if (!additive) value -= bone.worldX; bone.worldX += value * constraint.mixX; @@ -302,8 +302,8 @@ public class TransformConstraintData extends ConstraintData { } static public class FromY extends FromProperty { - public float value (TransformConstraintData data, Bone source, boolean local) { - return local ? source.ay + data.offsetY : data.offsetX * source.c + data.offsetY * source.d + source.worldY; + public float value (TransformConstraintData data, BoneApplied source, boolean local) { + return local ? source.y + data.offsetY : data.offsetX * source.c + data.offsetY * source.d + source.worldY; } } @@ -312,10 +312,10 @@ public class TransformConstraintData extends ConstraintData { return constraint.mixY; } - public void apply (TransformConstraint constraint, Bone bone, float value, boolean local, boolean additive) { + public void apply (TransformConstraint constraint, BoneApplied bone, float value, boolean local, boolean additive) { if (local) { - if (!additive) value -= bone.ay; - bone.ay += value * constraint.mixY; + if (!additive) value -= bone.y; + bone.y += value * constraint.mixY; } else { if (!additive) value -= bone.worldY; bone.worldY += value * constraint.mixY; @@ -324,8 +324,8 @@ public class TransformConstraintData extends ConstraintData { } static public class FromScaleX extends FromProperty { - public float value (TransformConstraintData data, Bone source, boolean local) { - return (local ? source.ascaleX : (float)Math.sqrt(source.a * source.a + source.c * source.c)) + data.offsetScaleX; + public float value (TransformConstraintData data, BoneApplied source, boolean local) { + return (local ? source.scaleX : (float)Math.sqrt(source.a * source.a + source.c * source.c)) + data.offsetScaleX; } } @@ -334,12 +334,12 @@ public class TransformConstraintData extends ConstraintData { return constraint.mixScaleX; } - public void apply (TransformConstraint constraint, Bone bone, float value, boolean local, boolean additive) { + public void apply (TransformConstraint constraint, BoneApplied bone, float value, boolean local, boolean additive) { if (local) { if (additive) - bone.ascaleX *= 1 + ((value - 1) * constraint.mixScaleX); - else if (bone.ascaleX != 0) // - bone.ascaleX = 1 + (value / bone.ascaleX - 1) * constraint.mixScaleX; + bone.scaleX *= 1 + ((value - 1) * constraint.mixScaleX); + else if (bone.scaleX != 0) // + bone.scaleX = 1 + (value / bone.scaleX - 1) * constraint.mixScaleX; } else { float s; if (additive) @@ -355,8 +355,8 @@ public class TransformConstraintData extends ConstraintData { } static public class FromScaleY extends FromProperty { - public float value (TransformConstraintData data, Bone source, boolean local) { - return (local ? source.ascaleY : (float)Math.sqrt(source.b * source.b + source.d * source.d)) + data.offsetScaleY; + public float value (TransformConstraintData data, BoneApplied source, boolean local) { + return (local ? source.scaleY : (float)Math.sqrt(source.b * source.b + source.d * source.d)) + data.offsetScaleY; } } @@ -365,12 +365,12 @@ public class TransformConstraintData extends ConstraintData { return constraint.mixScaleY; } - public void apply (TransformConstraint constraint, Bone bone, float value, boolean local, boolean additive) { + public void apply (TransformConstraint constraint, BoneApplied bone, float value, boolean local, boolean additive) { if (local) { if (additive) - bone.ascaleY *= 1 + ((value - 1) * constraint.mixScaleY); - else if (bone.ascaleY != 0) // - bone.ascaleY = 1 + (value / bone.ascaleY - 1) * constraint.mixScaleY; + bone.scaleY *= 1 + ((value - 1) * constraint.mixScaleY); + else if (bone.scaleY != 0) // + bone.scaleY = 1 + (value / bone.scaleY - 1) * constraint.mixScaleY; } else { float s; if (additive) @@ -386,8 +386,8 @@ public class TransformConstraintData extends ConstraintData { } static public class FromShearY extends FromProperty { - public float value (TransformConstraintData data, Bone source, boolean local) { - return (local ? source.ashearY : (atan2(source.d, source.b) - atan2(source.c, source.a)) * radDeg - 90) + public float value (TransformConstraintData data, BoneApplied source, boolean local) { + return (local ? source.shearY : (atan2(source.d, source.b) - atan2(source.c, source.a)) * radDeg - 90) + data.offsetShearY; } } @@ -397,10 +397,10 @@ public class TransformConstraintData extends ConstraintData { return constraint.mixShearY; } - public void apply (TransformConstraint constraint, Bone bone, float value, boolean local, boolean additive) { + public void apply (TransformConstraint constraint, BoneApplied bone, float value, boolean local, boolean additive) { if (local) { - if (!additive) value -= bone.ashearY; - bone.ashearY += value * constraint.mixShearY; + if (!additive) value -= bone.shearY; + bone.shearY += value * constraint.mixShearY; } else { float b = bone.b, d = bone.d, by = atan2(d, b); value = (value + 90) * degRad; diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PointAttachment.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PointAttachment.java index 87ffb217c..adaf3d05c 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PointAttachment.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/PointAttachment.java @@ -34,7 +34,7 @@ import static com.esotericsoftware.spine.utils.SpineUtils.*; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.math.Vector2; -import com.esotericsoftware.spine.Bone; +import com.esotericsoftware.spine.BoneApplied; /** An attachment which is a single point and a rotation. This can be used to spawn projectiles, particles, etc. A bone can be * used in similar ways, but a PointAttachment is slightly less expensive to compute and can be hidden, shown, and placed in a @@ -90,13 +90,13 @@ public class PointAttachment extends Attachment { return color; } - public Vector2 computeWorldPosition (Bone bone, Vector2 point) { + public Vector2 computeWorldPosition (BoneApplied bone, Vector2 point) { point.x = x * bone.getA() + y * bone.getB() + bone.getWorldX(); point.y = x * bone.getC() + y * bone.getD() + bone.getWorldY(); return point; } - public float computeWorldRotation (Bone bone) { + public float computeWorldRotation (BoneApplied bone) { float r = rotation * degRad, cos = cos(r), sin = sin(r); float x = cos * bone.getA() + sin * bone.getB(); float y = cos * bone.getC() + sin * bone.getD(); diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/RegionAttachment.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/RegionAttachment.java index 381cc4d36..7ee8b85ad 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/RegionAttachment.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/RegionAttachment.java @@ -37,6 +37,7 @@ import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.utils.Null; import com.esotericsoftware.spine.Bone; +import com.esotericsoftware.spine.BoneApplied; import com.esotericsoftware.spine.Slot; /** An attachment that displays a textured quadrilateral. @@ -179,7 +180,7 @@ public class RegionAttachment extends Attachment implements HasTextureRegion { if (sequence != null) sequence.apply(slot, this); float[] vertexOffset = this.offset; - Bone bone = slot.getBone(); + BoneApplied bone = slot.getBone().getApplied(); float x = bone.getWorldX(), y = bone.getWorldY(); float a = bone.getA(), b = bone.getB(), c = bone.getC(), d = bone.getD(); float offsetX, offsetY; diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/VertexAttachment.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/VertexAttachment.java index 7f69e3d2e..5d39629a1 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/VertexAttachment.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/VertexAttachment.java @@ -35,6 +35,7 @@ import com.badlogic.gdx.utils.FloatArray; import com.badlogic.gdx.utils.Null; import com.esotericsoftware.spine.Bone; +import com.esotericsoftware.spine.BoneApplied; import com.esotericsoftware.spine.Skeleton; import com.esotericsoftware.spine.Slot; @@ -91,7 +92,7 @@ abstract public class VertexAttachment extends Attachment { int[] bones = this.bones; if (bones == null) { if (deformArray.size > 0) vertices = deformArray.items; - Bone bone = slot.getBone(); + BoneApplied bone = slot.getBone().getApplied(); float x = bone.getWorldX(), y = bone.getWorldY(); float a = bone.getA(), b = bone.getB(), c = bone.getC(), d = bone.getD(); for (int v = start, w = offset; w < count; v += 2, w += stride) { @@ -114,7 +115,7 @@ abstract public class VertexAttachment extends Attachment { int n = bones[v++]; n += v; for (; v < n; v++, b += 3) { - var bone = (Bone)skeletonBones[bones[v]]; + BoneApplied bone = ((Bone)skeletonBones[bones[v]]).getApplied(); float vx = vertices[b], vy = vertices[b + 1], weight = vertices[b + 2]; wx += (vx * bone.getA() + vy * bone.getB() + bone.getWorldX()) * weight; wy += (vx * bone.getC() + vy * bone.getD() + bone.getWorldY()) * weight; @@ -129,7 +130,7 @@ abstract public class VertexAttachment extends Attachment { int n = bones[v++]; n += v; for (; v < n; v++, b += 3, f += 2) { - var bone = (Bone)skeletonBones[bones[v]]; + BoneApplied bone = ((Bone)skeletonBones[bones[v]]).getApplied(); float vx = vertices[b] + deform[f], vy = vertices[b + 1] + deform[f + 1], weight = vertices[b + 2]; wx += (vx * bone.getA() + vy * bone.getB() + bone.getWorldX()) * weight; wy += (vx * bone.getC() + vy * bone.getD() + bone.getWorldY()) * weight; diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/utils/SkeletonClipping.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/utils/SkeletonClipping.java index 4b4f6a521..b88a788ac 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/utils/SkeletonClipping.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/utils/SkeletonClipping.java @@ -41,7 +41,7 @@ public class SkeletonClipping { private final FloatArray clippingPolygon = new FloatArray(); private final FloatArray clipOutput = new FloatArray(128); private final FloatArray clippedVertices = new FloatArray(128); - private final FloatArray clippedUvs = new FloatArray(128); + private final FloatArray clippedUvs = new FloatArray(0); private final ShortArray clippedTriangles = new ShortArray(128); private final FloatArray scratch = new FloatArray(); @@ -459,7 +459,7 @@ public class SkeletonClipping { return clippedVertices; } - /** Only returns a non-empty array if clipTrianglesUnpacked() was used **/ + /** Returns an empty array unless {@link #clipTrianglesUnpacked(float[], int, short[], int, float[])} was used. **/ public FloatArray getClippedUvs () { return clippedUvs; }