From 4259e86e195d7a94dd396cbb0d01e5824747b536 Mon Sep 17 00:00:00 2001 From: Nathan Sweet Date: Sat, 21 Mar 2026 15:36:53 -0400 Subject: [PATCH] [libgdx] Animation/AnimationState additive improvements. * Replaced MixBlend and MixDirection with booleans. * MixBlend.first functionality is no longer needed, cases simplified. * Timelines know if they support additive or use instant transitions. * When applying an animation with additive, timelines that don't support additive use the hold system to prevent dipping. * Additive uses setup pose to prevent accumulation across frames. No longer need to reset additive properties. * Added TrackEntry#setAdditive(boolean). * Simplified AnimationState code. --- .../esotericsoftware/spine/BonePlotting.java | 4 +- .../esotericsoftware/spine/Box2DExample.java | 5 +- .../spine/EventTimelineTests.java | 10 +- .../com/esotericsoftware/spine/FboTest.java | 4 +- .../esotericsoftware/spine/HeadlessTest.java | 1 + .../esotericsoftware/spine/NormalMapTest.java | 5 +- .../esotericsoftware/spine/PngExportTest.java | 4 +- .../spine/TimelineApiTest.java | 20 +- .../spine/utils/SkeletonSerializer.java | 4 +- .../com/esotericsoftware/spine/Animation.java | 1261 +++++++---------- .../spine/AnimationState.java | 240 ++-- .../src/com/esotericsoftware/spine/Event.java | 2 +- .../com/esotericsoftware/spine/Slider.java | 5 +- .../spine/SkeletonViewer.java | 3 +- .../spine/SkeletonViewerUI.java | 3 +- 15 files changed, 625 insertions(+), 946 deletions(-) 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 41c95f282..98f25349b 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 @@ -31,8 +31,6 @@ package com.esotericsoftware.spine; import com.badlogic.gdx.files.FileHandle; -import com.esotericsoftware.spine.Animation.MixBlend; -import com.esotericsoftware.spine.Animation.MixDirection; import com.esotericsoftware.spine.attachments.AttachmentLoader; import com.esotericsoftware.spine.attachments.BoundingBoxAttachment; import com.esotericsoftware.spine.attachments.ClippingAttachment; @@ -81,7 +79,7 @@ public class BonePlotting { for (Animation animation : skeletonData.getAnimations()) { float time = 0; while (time < animation.getDuration()) { - animation.apply(skeleton, time, time, false, null, 1, MixBlend.first, MixDirection.in, false); + animation.apply(skeleton, time, time, false, null, 1, true, false, false, 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 10fa8e0c6..eae537af7 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 @@ -36,7 +36,6 @@ import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration; import com.badlogic.gdx.graphics.OrthographicCamera; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.g2d.TextureAtlas; -import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion; import com.badlogic.gdx.graphics.glutils.ShapeRenderer; import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.math.Matrix4; @@ -51,8 +50,6 @@ import com.badlogic.gdx.physics.box2d.World; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.ScreenUtils; -import com.esotericsoftware.spine.Animation.MixBlend; -import com.esotericsoftware.spine.Animation.MixDirection; import com.esotericsoftware.spine.attachments.AtlasAttachmentLoader; import com.esotericsoftware.spine.attachments.RegionAttachment; import com.esotericsoftware.spine.attachments.Sequence; @@ -144,7 +141,7 @@ public class Box2DExample extends ApplicationAdapter { batch.setTransformMatrix(camera.view); batch.begin(); - animation.apply(skeleton, time, time, true, events, 1, MixBlend.first, MixDirection.in, false); + animation.apply(skeleton, time, time, true, events, 1, true, false, false, false); skeleton.x += 8 * delta; skeleton.update(delta); skeleton.updateWorldTransform(Physics.update); 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 cf61f546c..3c26a3dc0 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 @@ -34,8 +34,6 @@ import java.util.Arrays; import com.badlogic.gdx.utils.Array; import com.esotericsoftware.spine.Animation.EventTimeline; -import com.esotericsoftware.spine.Animation.MixBlend; -import com.esotericsoftware.spine.Animation.MixDirection; /** Unit tests to ensure {@link EventTimeline} is working as expected. */ public class EventTimelineTests { @@ -176,7 +174,7 @@ public class EventTimelineTests { int beforeCount = firedEvents.size; Array original = new Array(firedEvents); - timeline.apply(skeleton, lastTimeLooped, timeLooped, firedEvents, 1, MixBlend.first, MixDirection.in, false); + timeline.apply(skeleton, lastTimeLooped, timeLooped, firedEvents, 1, true, false, false, false); while (beforeCount < firedEvents.size) { char fired = firedEvents.get(beforeCount).getData().getName().charAt(0); @@ -185,7 +183,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, false); + timeline.apply(skeleton, lastTimeLooped, timeLooped, original, 1, true, false, false, false); fail("Too many events fired."); } } @@ -193,7 +191,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, false); + timeline.apply(skeleton, lastTimeLooped, timeLooped, original, 1, true, false, false, false); fail("Wrong event fired."); } eventIndex++; @@ -205,7 +203,7 @@ public class EventTimelineTests { i++; } if (firedEvents.size < eventsCount) { - timeline.apply(skeleton, lastTimeLooped, timeLooped, firedEvents, 1, MixBlend.first, MixDirection.in, false); + timeline.apply(skeleton, lastTimeLooped, timeLooped, firedEvents, 1, true, false, false, 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/FboTest.java b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/FboTest.java index 81e97065f..8f5aa9b19 100644 --- a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/FboTest.java +++ b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/FboTest.java @@ -42,8 +42,6 @@ import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.graphics.glutils.FrameBuffer; import com.badlogic.gdx.utils.ScreenUtils; -import com.esotericsoftware.spine.Animation.MixBlend; -import com.esotericsoftware.spine.Animation.MixDirection; import com.esotericsoftware.spine.utils.TwoColorPolygonBatch; /** Demonstrates rendering an animation to a frame buffer (FBO) and then rendering the FBO to the screen. */ @@ -80,7 +78,7 @@ public class FboTest extends ApplicationAdapter { // Apply the pose for the first frame of the run animation. Animation animation = skeleton.getData().findAnimation("run"); - animation.apply(skeleton, -1, 0, true, null, 1, MixBlend.first, MixDirection.in, false); + animation.apply(skeleton, -1, 0, true, null, 1, true, false, false, false); // Compute the world transform for the pose. skeleton.updateWorldTransform(Physics.update); diff --git a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/HeadlessTest.java b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/HeadlessTest.java index 845a12db3..c7a8ae4d2 100644 --- a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/HeadlessTest.java +++ b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/HeadlessTest.java @@ -36,6 +36,7 @@ import com.badlogic.gdx.backends.headless.HeadlessApplicationConfiguration; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.TextureAtlas; + import com.esotericsoftware.spine.utils.SkeletonSerializer; public class HeadlessTest implements ApplicationListener { 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 3e4554827..ad207cab1 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 @@ -56,9 +56,6 @@ import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener; import com.badlogic.gdx.utils.Align; import com.badlogic.gdx.utils.ScreenUtils; -import com.esotericsoftware.spine.Animation.MixBlend; -import com.esotericsoftware.spine.Animation.MixDirection; - /** Demonstrates simplistic usage of lighting with normal maps. *

* Note the normals are not rotated when bones are rotated, making lighting incorrect. */ @@ -136,7 +133,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, false); + if (animation != null) animation.apply(skeleton, lastTime, time, true, null, 1, true, false, false, 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 f88d061ca..72537a5be 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 @@ -46,8 +46,6 @@ import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.graphics.glutils.FrameBuffer; import com.badlogic.gdx.utils.ScreenUtils; -import com.esotericsoftware.spine.Animation.MixBlend; -import com.esotericsoftware.spine.Animation.MixDirection; import com.esotericsoftware.spine.utils.TwoColorPolygonBatch; /** Demonstrates rendering an animation to a frame buffer (FBO) and then writing each frame as a PNG. */ @@ -103,7 +101,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, false); + animation.apply(skeleton, time, time, false, null, 1, true, false, false, false); skeleton.update(fps); skeleton.updateWorldTransform(Physics.update); 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 eaaad0933..d181ce271 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 @@ -37,9 +37,6 @@ import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.ScreenUtils; -import com.esotericsoftware.spine.Animation.MixBlend; -import com.esotericsoftware.spine.Animation.MixDirection; - /** Demonstrates using the timeline API. See {@link SimpleTest1} for a higher level API using {@link AnimationState}. *

* See: https://esotericsoftware.com/spine-applying-animations */ @@ -108,24 +105,23 @@ 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, false); + walkAnimation.apply(skeleton, time, time, true, events, 1, true, false, false, false); } else if (time > blendOutStart) { // blend out jump - walkAnimation.apply(skeleton, time, time, true, events, 1, MixBlend.first, MixDirection.in, false); + walkAnimation.apply(skeleton, time, time, true, events, 1, true, false, false, false); jumpAnimation.apply(skeleton, time - beforeJump, time - beforeJump, false, events, 1 - (time - blendOutStart) / blendOut, - MixBlend.first, MixDirection.in, false); + true, false, false, false); } else if (time > beforeJump + blendIn) { // just jump - jumpAnimation.apply(skeleton, time - beforeJump, time - beforeJump, false, events, 1, MixBlend.first, MixDirection.in, - false); + jumpAnimation.apply(skeleton, time - beforeJump, time - beforeJump, false, events, 1, true, false, false, false); } else if (time > beforeJump) { // blend in jump - 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, false); + walkAnimation.apply(skeleton, time, time, true, events, 1, true, false, false, false); + jumpAnimation.apply(skeleton, time - beforeJump, time - beforeJump, false, events, (time - beforeJump) / blendIn, true, + false, false, false); } else { // just walk before jump - walkAnimation.apply(skeleton, time, time, true, events, 1, MixBlend.first, MixDirection.in, false); + walkAnimation.apply(skeleton, time, time, true, events, 1, true, false, false, false); } skeleton.update(delta); diff --git a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/utils/SkeletonSerializer.java b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/utils/SkeletonSerializer.java index bfa76abcc..e83bf9c96 100644 --- a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/utils/SkeletonSerializer.java +++ b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/utils/SkeletonSerializer.java @@ -1900,8 +1900,8 @@ public class SkeletonSerializer { json.writeName("mixDuration"); json.writeValue(obj.getMixDuration()); - json.writeName("mixBlend"); - json.writeValue(obj.getMixBlend().name()); + json.writeName("additive"); + json.writeValue(obj.getAdditive()); json.writeName("mixingFrom"); if (obj.getMixingFrom() == null) { 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 6dd1faced..9cf014172 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java @@ -29,8 +29,6 @@ package com.esotericsoftware.spine; -import static com.esotericsoftware.spine.Animation.MixBlend.*; -import static com.esotericsoftware.spine.Animation.MixDirection.*; import static com.esotericsoftware.spine.utils.SpineUtils.*; import com.badlogic.gdx.graphics.Color; @@ -82,7 +80,7 @@ public class Animation { Timeline[] items = timelines.items; for (int i = 0; i < n; i++) { Timeline timeline = items[i]; - timelineIds.addAll(timeline.getPropertyIds()); + timelineIds.addAll(timeline.propertyIds); if (timeline instanceof BoneTimeline boneTimeline && boneSet.add(boneTimeline.getBoneIndex())) bones.add(boneTimeline.getBoneIndex()); } @@ -112,26 +110,29 @@ public class Animation { /** Applies the animation's timelines to the specified skeleton. *

- * 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 + * See Timeline {@link Timeline#apply(Skeleton, float, float, Array, float, boolean, boolean, boolean, boolean)}. + * @param skeleton The skeleton the animation is 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 - * than every frame. Pass -1 the first time an animation is applied to ensure frame 0 is triggered. - * @param time The time in seconds the skeleton is being posed for. Most timelines find the frame before and the frame after - * this time and interpolate between the frame values. If beyond the {@link #getDuration()} and loop is - * true then the animation will repeat, else the last frame will be applied. - * @param loop If true, the animation repeats after the {@link #getDuration()}. - * @param events If any events are fired, they are added to this list. Can be null to ignore fired events or if no timelines - * fire events. - * @param alpha 0 applies the current or setup values (depending on blend). 1 applies the timeline values. Between - * 0 and 1 applies values between the current or setup values and the timeline values. By adjusting - * alpha over time, an animation can be mixed in or out. alpha can also be useful to apply - * animations on top of each other (layering). - * @param blend Controls how mixing is applied when alpha < 1. - * @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}. */ + * @param lastTime The last time in seconds this animation was applied. Some timelines trigger only at discrete times, in which + * case all keys are triggered between lastTime (exclusive) and time (inclusive). Pass -1 + * the first time an animation is applied to ensure frame 0 is triggered. + * @param time The time in seconds the skeleton is being posed for. Timelines find the frame before and after this time and + * interpolate between the frame values. + * @param loop True if time beyond the {@link #getDuration()} repeats the animation, else the last frame is used. + * @param events If any events are fired, they are added to this list. Pass null to ignore fired events or if no timelines fire + * events. + * @param alpha 0 applies setup or current values (depending on fromSetup), 1 uses timeline values, and + * intermediate values interpolate between them. Adjusting alpha over time can mix an animation in or + * out. + * @param fromSetup If true, alpha transitions between setup and timeline values, setup values are used before the + * first frame (current values are not used). If false, alpha transitions between current and timeline + * values, no change is made before the first frame. + * @param add If true, for timelines that support it, their values are added to the setup or current values (depending on + * fromSetup). + * @param out True when the animation is mixing out, else it is mixing in. Used by timelines that perform instant transitions. + * @param appliedPose True to modify the {@link Posed#getAppliedPose()}, else modify the {@link Posed#getPose()}. */ public void apply (Skeleton skeleton, float lastTime, float time, boolean loop, @Null Array events, float alpha, - MixBlend blend, MixDirection direction, boolean appliedPose) { + boolean fromSetup, boolean add, boolean out, boolean appliedPose) { if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); if (loop && duration != 0) { @@ -141,7 +142,7 @@ public class Animation { Timeline[] timelines = this.timelines.items; for (int i = 0, n = this.timelines.size; i < n; i++) - timelines[i].apply(skeleton, lastTime, time, events, alpha, blend, direction, appliedPose); + timelines[i].apply(skeleton, lastTime, time, events, alpha, fromSetup, add, out, appliedPose); } /** The animation's name, which is unique across all animations in the skeleton. */ @@ -153,43 +154,6 @@ public class Animation { return name; } - /** 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, boolean)}. */ - static public enum MixBlend { - /** Transitions between the setup and timeline values (the current value is not used). Before the first frame, the setup - * value is used. - *

- * setup is intended to transition to or from the setup pose, not for animations layered on top of others. */ - setup, - /** Transitions between the current and timeline values. Before the first frame, transitions between the current and setup - * values. Timelines which perform instant transitions, such as {@link DrawOrderTimeline} or {@link AttachmentTimeline}, use - * the setup value before the first frame. - *

- * first is intended for the first animations applied, not for animations layered on top of others. */ - first, - /** Transitions between the current and timeline values. No change is made before the first frame. - *

- * replace is intended for animations layered on top of others, not for the first animations applied. */ - replace, - /** Transitions between the current value and the current plus timeline values. No change is made before the first frame. - *

- * add is intended for animations layered on top of others, not for the first animations applied. - *

- * Properties set by additive animations must be set manually or by another animation before applying the additive - * animations, else the property values will increase each time the additive animations are applied. */ - add - } - - /** 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, boolean)}. */ - static public enum MixDirection { - in, out - } - static private enum Property { rotate, x, y, scaleX, scaleY, shearX, shearY, inherit, // rgb, alpha, rgb2, // @@ -205,8 +169,9 @@ public class Animation { /** The base class for all timelines. */ static abstract public class Timeline { - private final String[] propertyIds; + final String[] propertyIds; final float[] frames; + boolean additive, instant; /** @param propertyIds Unique identifiers for the properties the timeline modifies. */ public Timeline (int frameCount, String... propertyIds) { @@ -239,28 +204,38 @@ public class Animation { return frames[frames.length - getFrameEntries()]; } + /** True if this timeline supports additive blending. */ + public boolean getAdditive () { + return additive; + } + + /** True if this timeline sets values instantaneously and does not support interpolation between frames. */ + public boolean getInstant () { + return instant; + } + /** Applies this timeline to the skeleton. - * @param skeleton The skeleton to which the timeline is being applied. This provides access to the bones, slots, and other - * skeleton components that the timeline may change. - * @param lastTime The last time in seconds this timeline was applied. Timelines such as {@link EventTimeline} trigger only - * at specific times rather than every frame. In that case, the timeline triggers everything between - * lastTime (exclusive) and time (inclusive). Pass -1 the first time an animation is - * applied to ensure frame 0 is triggered. - * @param time The time in seconds that the skeleton is being posed for. Most timelines find the frame before and the frame - * after this time and interpolate between the frame values. If beyond the last frame, the last frame will be - * applied. - * @param events If any events are fired, they are added to this list. Can be null to ignore fired events or if the timeline - * does not fire events. - * @param alpha 0 applies the current or setup value (depending on blend). 1 applies the timeline value. - * Between 0 and 1 applies a value between the current or setup value and the timeline value. By adjusting - * alpha over time, an animation can be mixed in or out. alpha can also be useful to - * 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}. - * @param appliedPose True to to modify the applied pose. */ + * @param skeleton The skeleton the timeline is 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 timline was applied. Some timelines trigger only at discrete times, in + * which case all keys are triggered between lastTime (exclusive) and time (inclusive). + * Pass -1 the first time a timeline is applied to ensure frame 0 is triggered. + * @param time The time in seconds the skeleton is being posed for. Timelines find the frame before and after this time and + * interpolate between the frame values. + * @param events If any events are fired, they are added to this list. Pass null to ignore fired events or if no timelines + * fire events. + * @param alpha 0 applies setup or current values (depending on fromSetup), 1 uses timeline values, and + * intermediate values interpolate between them. Adjusting alpha over time can mix a timeline in or + * out. + * @param fromSetup If true, alpha transitions between setup and timeline values, setup values are used before + * the first frame (current values are not used). If false, alpha transitions between current and + * timeline values, no change is made before the first frame. + * @param add If true, for timelines that support it, their values are added to the setup or current values (depending on + * fromSetup). + * @param out True when the animation is mixing out, else it is mixing in. + * @param appliedPose True to modify the {@link Posed#getAppliedPose()}, else modify the {@link Posed#getPose()}. */ abstract public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, - MixBlend blend, MixDirection direction, boolean appliedPose); + boolean fromSetup, boolean add, boolean out, boolean appliedPose); /** Linear search using a stride of 1. * @param time Must be >= the first value in frames. @@ -283,7 +258,7 @@ public class Animation { } } - /** An interface for timelines which change the property of a slot. */ + /** An interface for timelines that change the property of a slot. */ static public interface SlotTimeline { /** The index of the slot in {@link Skeleton#getSlots()} that will be changed when this timeline is applied. */ public int getSlotIndex (); @@ -438,89 +413,66 @@ public class Animation { }; } - public float getRelativeValue (float time, float alpha, MixBlend blend, float current, float setup) { - if (time < frames[0]) { - return switch (blend) { - case setup -> setup; - case first -> current + (setup - current) * alpha; - default -> current; - }; - } + public float getRelativeValue (float time, float alpha, boolean fromSetup, boolean add, float current, float setup) { + if (time < frames[0]) return fromSetup ? setup : current; float value = getCurveValue(time); - return switch (blend) { - case setup -> setup + value * alpha; - case first, replace -> current + (value + setup - current) * alpha; - case add -> current + value * alpha; - }; + return fromSetup ? setup + value * alpha : current + (add ? value : value + setup - current) * alpha; } - public float getAbsoluteValue (float time, float alpha, MixBlend blend, float current, float setup) { - if (time < frames[0]) { - return switch (blend) { - case setup -> setup; - case first -> current + (setup - current) * alpha; - default -> current; - }; - } + public float getAbsoluteValue (float time, float alpha, boolean fromSetup, boolean add, float current, float setup) { + if (time < frames[0]) return fromSetup ? setup : current; float value = getCurveValue(time); - return switch (blend) { - case setup -> setup + (value - setup) * alpha; - case first, replace -> current + (value - current) * alpha; - case add -> current + value * alpha; - }; + return fromSetup ? setup + (value - setup) * alpha : current + (add ? value : value - current) * alpha; } - public float getAbsoluteValue (float time, float alpha, MixBlend blend, float current, float setup, float value) { - if (time < frames[0]) { - return switch (blend) { - case setup -> setup; - case first -> current + (setup - current) * alpha; - default -> current; - }; - } - return switch (blend) { - case setup -> setup + (value - setup) * alpha; - case first, replace -> current + (value - current) * alpha; - case add -> current + value * alpha; - }; + public float getAbsoluteValue (float time, float alpha, boolean fromSetup, boolean add, float current, float setup, + float value) { + if (time < frames[0]) return fromSetup ? setup : current; + return fromSetup ? setup + (value - setup) * alpha : current + (add ? value : value - current) * alpha; } - public float getScaleValue (float time, float alpha, MixBlend blend, MixDirection direction, float current, float setup) { - float[] frames = this.frames; - if (time < frames[0]) { - return switch (blend) { - case setup -> setup; - case first -> current + (setup - current) * alpha; - default -> current; - }; - } + public float getScaleValue (float time, float alpha, boolean fromSetup, boolean add, boolean out, float current, + float setup) { + if (time < frames[0]) return fromSetup ? setup : current; float value = getCurveValue(time) * setup; - if (alpha == 1) return blend == add ? current + value - setup : value; - // Mixing out uses sign of setup or current pose, else use sign of key. - if (direction == out) { - switch (blend) { - case setup: - return setup + (Math.abs(value) * Math.signum(setup) - setup) * alpha; - case first: - case replace: - return current + (Math.abs(value) * Math.signum(current) - current) * alpha; - } - } else { - float s; - switch (blend) { - case setup: - s = Math.abs(setup) * Math.signum(value); - return s + (value - s) * alpha; - case first: - case replace: - s = Math.abs(current) * Math.signum(value); - return s + (value - s) * alpha; - } - } - return current + (value - setup) * alpha; + if (alpha == 1) return value; + float base = fromSetup ? setup : current; + if (add) return base + (value - setup) * alpha; + if (out) return base + (Math.abs(value) * Math.signum(base) - base) * alpha; + base = Math.abs(base) * Math.signum(value); + return base + (value - base) * alpha; } } + /** An interface for timelines that change the property of a bone. */ + static public interface BoneTimeline { + /** The index of the bone in {@link Skeleton#getBones()} that will be changed when this timeline is applied. */ + public int getBoneIndex (); + } + + static abstract public class BoneTimeline1 extends CurveTimeline1 implements BoneTimeline { + final int boneIndex; + + public BoneTimeline1 (int frameCount, int bezierCount, int boneIndex, Property property) { + super(frameCount, bezierCount, property.ordinal() + "|" + boneIndex); + this.boneIndex = boneIndex; + additive = true; + } + + public int getBoneIndex () { + return boneIndex; + } + + public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, boolean fromSetup, + boolean add, boolean out, boolean appliedPose) { + Bone bone = skeleton.bones.items[boneIndex]; + if (bone.active) apply(appliedPose ? bone.applied : bone.pose, bone.data.setup, time, alpha, fromSetup, add, out); + } + + abstract protected void apply (BoneLocal pose, BoneLocal setup, float time, float alpha, boolean fromSetup, boolean add, + boolean out); + } + /** The base class for a {@link CurveTimeline} that is a {@link BoneTimeline} and sets two properties. */ static abstract public class BoneTimeline2 extends CurveTimeline implements BoneTimeline { static public final int ENTRIES = 3; @@ -532,6 +484,7 @@ public class Animation { public BoneTimeline2 (int frameCount, int bezierCount, int boneIndex, Property property1, Property property2) { super(frameCount, bezierCount, property1.ordinal() + "|" + boneIndex, property2.ordinal() + "|" + boneIndex); this.boneIndex = boneIndex; + additive = true; } public int getFrameEntries () { @@ -552,44 +505,14 @@ public class Animation { return boneIndex; } - public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, - MixDirection direction, boolean appliedPose) { - + public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, boolean fromSetup, + boolean add, boolean out, boolean appliedPose) { Bone bone = skeleton.bones.items[boneIndex]; - if (bone.active) apply(appliedPose ? bone.applied : bone.pose, bone.data.setup, time, alpha, blend, direction); + if (bone.active) apply(appliedPose ? bone.applied : bone.pose, bone.data.setup, time, alpha, fromSetup, add, out); } - abstract protected void apply (BoneLocal pose, BoneLocal setup, float time, float alpha, MixBlend blend, - MixDirection direction); - } - - /** An interface for timelines which change the property of a bone. */ - static public interface BoneTimeline { - /** The index of the bone in {@link Skeleton#getBones()} that will be changed when this timeline is applied. */ - public int getBoneIndex (); - } - - static abstract public class BoneTimeline1 extends CurveTimeline1 implements BoneTimeline { - final int boneIndex; - - public BoneTimeline1 (int frameCount, int bezierCount, int boneIndex, Property property) { - super(frameCount, bezierCount, property.ordinal() + "|" + boneIndex); - this.boneIndex = boneIndex; - } - - public int getBoneIndex () { - return boneIndex; - } - - public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, - MixDirection direction, boolean appliedPose) { - - Bone bone = skeleton.bones.items[boneIndex]; - if (bone.active) apply(appliedPose ? bone.applied : bone.pose, bone.data.setup, time, alpha, blend, direction); - } - - abstract protected void apply (BoneLocal pose, BoneLocal setup, float time, float alpha, MixBlend blend, - MixDirection direction); + abstract protected void apply (BoneLocal pose, BoneLocal setup, float time, float alpha, boolean fromSetup, boolean add, + boolean out); } /** Changes a bone's local {@link BoneLocal#getRotation()}. */ @@ -598,8 +521,9 @@ public class Animation { super(frameCount, bezierCount, boneIndex, Property.rotate); } - protected void apply (BoneLocal pose, BoneLocal setup, float time, float alpha, MixBlend blend, MixDirection direction) { - pose.rotation = getRelativeValue(time, alpha, blend, pose.rotation, setup.rotation); + protected void apply (BoneLocal pose, BoneLocal setup, float time, float alpha, boolean fromSetup, boolean add, + boolean out) { + pose.rotation = getRelativeValue(time, alpha, fromSetup, add, pose.rotation, setup.rotation); } } @@ -609,19 +533,14 @@ public class Animation { super(frameCount, bezierCount, boneIndex, Property.x, Property.y); } - protected void apply (BoneLocal pose, BoneLocal setup, float time, float alpha, MixBlend blend, MixDirection direction) { + protected void apply (BoneLocal pose, BoneLocal setup, float time, float alpha, boolean fromSetup, boolean add, + boolean out) { float[] frames = this.frames; if (time < frames[0]) { - switch (blend) { - case setup -> { + if (fromSetup) { pose.x = setup.x; pose.y = setup.y; } - case first -> { - pose.x += (setup.x - pose.x) * alpha; - pose.y += (setup.y - pose.y) * alpha; - } - } return; } @@ -646,19 +565,15 @@ public class Animation { } } - switch (blend) { - case setup -> { + if (fromSetup) { pose.x = setup.x + x * alpha; pose.y = setup.y + y * alpha; - } - case first, replace -> { - pose.x += (setup.x + x - pose.x) * alpha; - pose.y += (setup.y + y - pose.y) * alpha; - } - case add -> { + } else if (add) { pose.x += x * alpha; pose.y += y * alpha; - } + } else { + pose.x += (setup.x + x - pose.x) * alpha; + pose.y += (setup.y + y - pose.y) * alpha; } } } @@ -669,8 +584,9 @@ public class Animation { super(frameCount, bezierCount, boneIndex, Property.x); } - protected void apply (BoneLocal pose, BoneLocal setup, float time, float alpha, MixBlend blend, MixDirection direction) { - pose.x = getRelativeValue(time, alpha, blend, pose.x, setup.x); + protected void apply (BoneLocal pose, BoneLocal setup, float time, float alpha, boolean fromSetup, boolean add, + boolean out) { + pose.x = getRelativeValue(time, alpha, fromSetup, add, pose.x, setup.x); } } @@ -680,8 +596,9 @@ public class Animation { super(frameCount, bezierCount, boneIndex, Property.y); } - protected void apply (BoneLocal pose, BoneLocal setup, float time, float alpha, MixBlend blend, MixDirection direction) { - pose.y = getRelativeValue(time, alpha, blend, pose.y, setup.y); + protected void apply (BoneLocal pose, BoneLocal setup, float time, float alpha, boolean fromSetup, boolean add, + boolean out) { + pose.y = getRelativeValue(time, alpha, fromSetup, add, pose.y, setup.y); } } @@ -691,19 +608,14 @@ public class Animation { super(frameCount, bezierCount, boneIndex, Property.scaleX, Property.scaleY); } - protected void apply (BoneLocal pose, BoneLocal setup, float time, float alpha, MixBlend blend, MixDirection direction) { + protected void apply (BoneLocal pose, BoneLocal setup, float time, float alpha, boolean fromSetup, boolean add, + boolean out) { float[] frames = this.frames; if (time < frames[0]) { - switch (blend) { - case setup -> { + if (fromSetup) { pose.scaleX = setup.scaleX; pose.scaleY = setup.scaleY; } - case first -> { - pose.scaleX += (setup.scaleX - pose.scaleX) * alpha; - pose.scaleY += (setup.scaleY - pose.scaleY) * alpha; - } - } return; } @@ -730,55 +642,29 @@ public class Animation { x *= setup.scaleX; y *= setup.scaleY; - if (alpha == 1) { - if (blend == add) { - pose.scaleX += x - setup.scaleX; - pose.scaleY += y - setup.scaleY; - } else { - pose.scaleX = x; - pose.scaleY = y; - } + if (!add && alpha == 1) { + pose.scaleX = x; + pose.scaleY = y; } else { - // Mixing out uses sign of setup or current pose, else use sign of key. float bx, by; - if (direction == out) { - switch (blend) { - case setup -> { - bx = setup.scaleX; - by = setup.scaleY; - pose.scaleX = bx + (Math.abs(x) * Math.signum(bx) - bx) * alpha; - pose.scaleY = by + (Math.abs(y) * Math.signum(by) - by) * alpha; - } - case first, replace -> { - bx = pose.scaleX; - by = pose.scaleY; - pose.scaleX = bx + (Math.abs(x) * Math.signum(bx) - bx) * alpha; - pose.scaleY = by + (Math.abs(y) * Math.signum(by) - by) * alpha; - } - case add -> { - pose.scaleX += (x - setup.scaleX) * alpha; - pose.scaleY += (y - setup.scaleY) * alpha; - } - } + if (fromSetup) { + bx = setup.scaleX; + by = setup.scaleY; } else { - switch (blend) { - case setup -> { - bx = Math.abs(setup.scaleX) * Math.signum(x); - by = Math.abs(setup.scaleY) * Math.signum(y); - pose.scaleX = bx + (x - bx) * alpha; - pose.scaleY = by + (y - by) * alpha; - } - case first, replace -> { - bx = Math.abs(pose.scaleX) * Math.signum(x); - by = Math.abs(pose.scaleY) * Math.signum(y); - pose.scaleX = bx + (x - bx) * alpha; - pose.scaleY = by + (y - by) * alpha; - } - case add -> { - pose.scaleX += (x - setup.scaleX) * alpha; - pose.scaleY += (y - setup.scaleY) * alpha; - } - } + bx = pose.scaleX; + by = pose.scaleY; + } + if (add) { + pose.scaleX = bx + (x - setup.scaleX) * alpha; + pose.scaleY = by + (y - setup.scaleY) * alpha; + } else if (out) { + pose.scaleX = bx + (Math.abs(x) * Math.signum(bx) - bx) * alpha; + pose.scaleY = by + (Math.abs(y) * Math.signum(by) - by) * alpha; + } else { + bx = Math.abs(bx) * Math.signum(x); + by = Math.abs(by) * Math.signum(y); + pose.scaleX = bx + (x - bx) * alpha; + pose.scaleY = by + (y - by) * alpha; } } } @@ -790,8 +676,9 @@ public class Animation { super(frameCount, bezierCount, boneIndex, Property.scaleX); } - protected void apply (BoneLocal pose, BoneLocal setup, float time, float alpha, MixBlend blend, MixDirection direction) { - pose.scaleX = getScaleValue(time, alpha, blend, direction, pose.scaleX, setup.scaleX); + protected void apply (BoneLocal pose, BoneLocal setup, float time, float alpha, boolean fromSetup, boolean add, + boolean out) { + pose.scaleX = getScaleValue(time, alpha, fromSetup, add, out, pose.scaleX, setup.scaleX); } } @@ -801,8 +688,9 @@ public class Animation { super(frameCount, bezierCount, boneIndex, Property.scaleY); } - protected void apply (BoneLocal pose, BoneLocal setup, float time, float alpha, MixBlend blend, MixDirection direction) { - pose.scaleY = getScaleValue(time, alpha, blend, direction, pose.scaleY, setup.scaleY); + protected void apply (BoneLocal pose, BoneLocal setup, float time, float alpha, boolean fromSetup, boolean add, + boolean out) { + pose.scaleY = getScaleValue(time, alpha, fromSetup, add, out, pose.scaleY, setup.scaleY); } } @@ -812,19 +700,14 @@ public class Animation { super(frameCount, bezierCount, boneIndex, Property.shearX, Property.shearY); } - protected void apply (BoneLocal pose, BoneLocal setup, float time, float alpha, MixBlend blend, MixDirection direction) { + protected void apply (BoneLocal pose, BoneLocal setup, float time, float alpha, boolean fromSetup, boolean add, + boolean out) { float[] frames = this.frames; if (time < frames[0]) { - switch (blend) { - case setup -> { + if (fromSetup) { pose.shearX = setup.shearX; pose.shearY = setup.shearY; } - case first -> { - pose.shearX += (setup.shearX - pose.shearX) * alpha; - pose.shearY += (setup.shearY - pose.shearY) * alpha; - } - } return; } @@ -849,19 +732,15 @@ public class Animation { } } - switch (blend) { - case setup -> { + if (fromSetup) { pose.shearX = setup.shearX + x * alpha; pose.shearY = setup.shearY + y * alpha; - } - case first, replace -> { - pose.shearX += (setup.shearX + x - pose.shearX) * alpha; - pose.shearY += (setup.shearY + y - pose.shearY) * alpha; - } - case add -> { + } else if (add) { pose.shearX += x * alpha; pose.shearY += y * alpha; - } + } else { + pose.shearX += (setup.shearX + x - pose.shearX) * alpha; + pose.shearY += (setup.shearY + y - pose.shearY) * alpha; } } } @@ -872,8 +751,9 @@ public class Animation { super(frameCount, bezierCount, boneIndex, Property.shearX); } - protected void apply (BoneLocal pose, BoneLocal setup, float time, float alpha, MixBlend blend, MixDirection direction) { - pose.shearX = getRelativeValue(time, alpha, blend, pose.shearX, setup.shearX); + protected void apply (BoneLocal pose, BoneLocal setup, float time, float alpha, boolean fromSetup, boolean add, + boolean out) { + pose.shearX = getRelativeValue(time, alpha, fromSetup, add, pose.shearX, setup.shearX); } } @@ -883,8 +763,9 @@ public class Animation { super(frameCount, bezierCount, boneIndex, Property.shearY); } - protected void apply (BoneLocal pose, BoneLocal setup, float time, float alpha, MixBlend blend, MixDirection direction) { - pose.shearY = getRelativeValue(time, alpha, blend, pose.shearY, setup.shearY); + protected void apply (BoneLocal pose, BoneLocal setup, float time, float alpha, boolean fromSetup, boolean add, + boolean out) { + pose.shearY = getRelativeValue(time, alpha, fromSetup, add, pose.shearY, setup.shearY); } } @@ -898,6 +779,7 @@ public class Animation { public InheritTimeline (int frameCount, int boneIndex) { super(frameCount, Property.inherit.ordinal() + "|" + boneIndex); this.boneIndex = boneIndex; + instant = true; } public int getBoneIndex () { @@ -917,23 +799,21 @@ public class Animation { frames[frame + INHERIT] = inherit.ordinal(); } - public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, - MixDirection direction, boolean appliedPose) { - + public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, boolean fromSetup, + boolean add, boolean out, boolean appliedPose) { Bone bone = skeleton.bones.items[boneIndex]; if (!bone.active) return; BoneLocal pose = appliedPose ? bone.applied : bone.pose; - if (direction == out) { - if (blend == setup) pose.inherit = bone.data.setup.inherit; - return; + if (out) { + if (fromSetup) pose.inherit = bone.data.setup.inherit; + } else { + float[] frames = this.frames; + if (time < frames[0]) { + if (fromSetup) pose.inherit = bone.data.setup.inherit; + } else + pose.inherit = Inherit.values[(int)frames[search(frames, time, ENTRIES) + INHERIT]]; } - - float[] frames = this.frames; - if (time < frames[0]) { - if (blend == setup || blend == first) pose.inherit = bone.data.setup.inherit; - } else - pose.inherit = Inherit.values[(int)frames[search(frames, time, ENTRIES) + INHERIT]]; } } @@ -949,14 +829,13 @@ public class Animation { return slotIndex; } - public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, - MixDirection direction, boolean appliedPose) { - + public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, boolean fromSetup, + boolean add, boolean out, boolean appliedPose) { Slot slot = skeleton.slots.items[slotIndex]; - if (slot.bone.active) apply(slot, appliedPose ? slot.applied : slot.pose, time, alpha, blend); + if (slot.bone.active) apply(slot, appliedPose ? slot.applied : slot.pose, time, alpha, fromSetup, add); } - abstract protected void apply (Slot slot, SlotPose pose, float time, float alpha, MixBlend blend); + abstract protected void apply (Slot slot, SlotPose pose, float time, float alpha, boolean fromSetup, boolean add); } /** Changes a slot's {@link SlotPose#getColor()}. */ @@ -986,16 +865,11 @@ public class Animation { frames[frame + A] = a; } - protected void apply (Slot slot, SlotPose pose, float time, float alpha, MixBlend blend) { + protected void apply (Slot slot, SlotPose pose, float time, float alpha, boolean fromSetup, boolean add) { Color color = pose.color; float[] frames = this.frames; if (time < frames[0]) { - Color setup = slot.data.setup.color; - switch (blend) { - case setup -> color.set(setup); - case first -> color.add((setup.r - color.r) * alpha, (setup.g - color.g) * alpha, (setup.b - color.b) * alpha, - (setup.a - color.a) * alpha); - } + if (fromSetup) color.set(slot.data.setup.color); return; } @@ -1031,7 +905,7 @@ public class Animation { if (alpha == 1) color.set(r, g, b, a); else { - if (blend == setup) { + if (fromSetup) { Color setup = slot.data.setup.color; color.set(setup.r + (r - setup.r) * alpha, setup.g + (g - setup.g) * alpha, setup.b + (b - setup.b) * alpha, setup.a + (a - setup.a) * alpha); @@ -1065,61 +939,54 @@ public class Animation { frames[frame + B] = b; } - protected void apply (Slot slot, SlotPose pose, float time, float alpha, MixBlend blend) { + protected void apply (Slot slot, SlotPose pose, float time, float alpha, boolean fromSetup, boolean add) { Color color = pose.color; float r, g, b; float[] frames = this.frames; if (time < frames[0]) { - Color setup = slot.data.setup.color; - switch (blend) { - case setup: + if (fromSetup) { + Color setup = slot.data.setup.color; color.r = setup.r; color.g = setup.g; color.b = setup.b; - // Fall through. - default: - return; - case first: - r = color.r + (setup.r - color.r) * alpha; - g = color.g + (setup.g - color.g) * alpha; - b = color.b + (setup.b - color.b) * alpha; - } - } else { - int i = search(frames, time, ENTRIES), curveType = (int)curves[i >> 2]; - switch (curveType) { - case LINEAR -> { - float before = frames[i]; - r = frames[i + R]; - g = frames[i + G]; - b = frames[i + B]; - float t = (time - before) / (frames[i + ENTRIES] - before); - r += (frames[i + ENTRIES + R] - r) * t; - g += (frames[i + ENTRIES + G] - g) * t; - b += (frames[i + ENTRIES + B] - b) * t; - } - case STEPPED -> { - r = frames[i + R]; - g = frames[i + G]; - b = frames[i + B]; - } - default -> { - r = getBezierValue(time, i, R, curveType - BEZIER); - g = getBezierValue(time, i, G, curveType + BEZIER_SIZE - BEZIER); - b = getBezierValue(time, i, B, curveType + BEZIER_SIZE * 2 - BEZIER); - } } + return; + } - if (alpha != 1) { - if (blend == setup) { - Color setup = slot.data.setup.color; - r = setup.r + (r - setup.r) * alpha; - g = setup.g + (g - setup.g) * alpha; - b = setup.b + (b - setup.b) * alpha; - } else { - r = color.r + (r - color.r) * alpha; - g = color.g + (g - color.g) * alpha; - b = color.b + (b - color.b) * alpha; - } + int i = search(frames, time, ENTRIES), curveType = (int)curves[i >> 2]; + switch (curveType) { + case LINEAR -> { + float before = frames[i]; + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + float t = (time - before) / (frames[i + ENTRIES] - before); + r += (frames[i + ENTRIES + R] - r) * t; + g += (frames[i + ENTRIES + G] - g) * t; + b += (frames[i + ENTRIES + B] - b) * t; + } + case STEPPED -> { + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + } + default -> { + r = getBezierValue(time, i, R, curveType - BEZIER); + g = getBezierValue(time, i, G, curveType + BEZIER_SIZE - BEZIER); + b = getBezierValue(time, i, B, curveType + BEZIER_SIZE * 2 - BEZIER); + } + } + + if (alpha != 1) { + if (fromSetup) { + Color setup = slot.data.setup.color; + r = setup.r + (r - setup.r) * alpha; + g = setup.g + (g - setup.g) * alpha; + b = setup.b + (b - setup.b) * alpha; + } else { + r = color.r + (r - color.r) * alpha; + g = color.g + (g - color.g) * alpha; + b = color.b + (b - color.b) * alpha; } } color.r = r < 0 ? 0 : (r > 1 ? 1 : r); @@ -1141,9 +1008,8 @@ public class Animation { return slotIndex; } - public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, - MixDirection direction, boolean appliedPose) { - + public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, boolean fromSetup, + boolean add, boolean out, boolean appliedPose) { Slot slot = skeleton.slots.items[slotIndex]; if (!slot.bone.active) return; @@ -1151,25 +1017,17 @@ public class Animation { float a; float[] frames = this.frames; if (time < frames[0]) { - Color setup = slot.data.setup.color; - switch (blend) { - case setup: - color.a = setup.a; - // Fall through. - default: - return; - case first: - a = color.a + (setup.a - color.a) * alpha; - } - } else { - a = getCurveValue(time); - if (alpha != 1) { - if (blend == setup) { - Color setup = slot.data.setup.color; - a = setup.a + (a - setup.a) * alpha; - } else - a = color.a + (a - color.a) * alpha; - } + if (fromSetup) color.a = slot.data.setup.color.a; + return; + } + + a = getCurveValue(time); + if (alpha != 1) { + if (fromSetup) { + Color setup = slot.data.setup.color; + a = setup.a + (a - setup.a) * alpha; + } else + a = color.a + (a - color.a) * alpha; } color.a = a < 0 ? 0 : (a > 1 ? 1 : a); } @@ -1206,88 +1064,79 @@ public class Animation { frames[frame + B2] = b2; } - protected void apply (Slot slot, SlotPose pose, float time, float alpha, MixBlend blend) { + protected void apply (Slot slot, SlotPose pose, float time, float alpha, boolean fromSetup, boolean add) { Color light = pose.color, dark = pose.darkColor; float r2, g2, b2; float[] frames = this.frames; if (time < frames[0]) { - SlotPose setup = slot.data.setup; - Color setupLight = setup.color, setupDark = setup.darkColor; - switch (blend) { - case setup: - light.set(setupLight); + if (fromSetup) { + SlotPose setup = slot.data.setup; + light.set(setup.color); + Color setupDark = setup.darkColor; dark.r = setupDark.r; dark.g = setupDark.g; dark.b = setupDark.b; - // Fall through. - default: - return; - case first: - light.add((setupLight.r - light.r) * alpha, (setupLight.g - light.g) * alpha, (setupLight.b - light.b) * alpha, - (setupLight.a - light.a) * alpha); - r2 = dark.r + (setupDark.r - dark.r) * alpha; - g2 = dark.g + (setupDark.g - dark.g) * alpha; - b2 = dark.b + (setupDark.b - dark.b) * alpha; - } - } else { - float r, g, b, a; - int i = search(frames, time, ENTRIES), curveType = (int)curves[i >> 3]; - switch (curveType) { - case LINEAR -> { - float before = frames[i]; - r = frames[i + R]; - g = frames[i + G]; - b = frames[i + B]; - a = frames[i + A]; - r2 = frames[i + R2]; - g2 = frames[i + G2]; - b2 = frames[i + B2]; - float t = (time - before) / (frames[i + ENTRIES] - before); - r += (frames[i + ENTRIES + R] - r) * t; - g += (frames[i + ENTRIES + G] - g) * t; - b += (frames[i + ENTRIES + B] - b) * t; - a += (frames[i + ENTRIES + A] - a) * t; - r2 += (frames[i + ENTRIES + R2] - r2) * t; - g2 += (frames[i + ENTRIES + G2] - g2) * t; - b2 += (frames[i + ENTRIES + B2] - b2) * t; - } - case STEPPED -> { - r = frames[i + R]; - g = frames[i + G]; - b = frames[i + B]; - a = frames[i + A]; - r2 = frames[i + R2]; - g2 = frames[i + G2]; - b2 = frames[i + B2]; - } - default -> { - r = getBezierValue(time, i, R, curveType - BEZIER); - g = getBezierValue(time, i, G, curveType + BEZIER_SIZE - BEZIER); - b = getBezierValue(time, i, B, curveType + BEZIER_SIZE * 2 - BEZIER); - a = getBezierValue(time, i, A, curveType + BEZIER_SIZE * 3 - BEZIER); - r2 = getBezierValue(time, i, R2, curveType + BEZIER_SIZE * 4 - BEZIER); - g2 = getBezierValue(time, i, G2, curveType + BEZIER_SIZE * 5 - BEZIER); - b2 = getBezierValue(time, i, B2, curveType + BEZIER_SIZE * 6 - BEZIER); - } } + return; + } - if (alpha == 1) - light.set(r, g, b, a); - else if (blend == setup) { - SlotPose setupPose = slot.data.setup; - Color setup = setupPose.color; - light.set(setup.r + (r - setup.r) * alpha, setup.g + (g - setup.g) * alpha, setup.b + (b - setup.b) * alpha, - setup.a + (a - setup.a) * alpha); - setup = setupPose.darkColor; - r2 = setup.r + (r2 - setup.r) * alpha; - g2 = setup.g + (g2 - setup.g) * alpha; - b2 = setup.b + (b2 - setup.b) * alpha; - } else { - light.add((r - light.r) * alpha, (g - light.g) * alpha, (b - light.b) * alpha, (a - light.a) * alpha); - r2 = dark.r + (r2 - dark.r) * alpha; - g2 = dark.g + (g2 - dark.g) * alpha; - b2 = dark.b + (b2 - dark.b) * alpha; - } + float r, g, b, a; + int i = search(frames, time, ENTRIES), curveType = (int)curves[i >> 3]; + switch (curveType) { + case LINEAR -> { + float before = frames[i]; + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + a = frames[i + A]; + r2 = frames[i + R2]; + g2 = frames[i + G2]; + b2 = frames[i + B2]; + float t = (time - before) / (frames[i + ENTRIES] - before); + r += (frames[i + ENTRIES + R] - r) * t; + g += (frames[i + ENTRIES + G] - g) * t; + b += (frames[i + ENTRIES + B] - b) * t; + a += (frames[i + ENTRIES + A] - a) * t; + r2 += (frames[i + ENTRIES + R2] - r2) * t; + g2 += (frames[i + ENTRIES + G2] - g2) * t; + b2 += (frames[i + ENTRIES + B2] - b2) * t; + } + case STEPPED -> { + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + a = frames[i + A]; + r2 = frames[i + R2]; + g2 = frames[i + G2]; + b2 = frames[i + B2]; + } + default -> { + r = getBezierValue(time, i, R, curveType - BEZIER); + g = getBezierValue(time, i, G, curveType + BEZIER_SIZE - BEZIER); + b = getBezierValue(time, i, B, curveType + BEZIER_SIZE * 2 - BEZIER); + a = getBezierValue(time, i, A, curveType + BEZIER_SIZE * 3 - BEZIER); + r2 = getBezierValue(time, i, R2, curveType + BEZIER_SIZE * 4 - BEZIER); + g2 = getBezierValue(time, i, G2, curveType + BEZIER_SIZE * 5 - BEZIER); + b2 = getBezierValue(time, i, B2, curveType + BEZIER_SIZE * 6 - BEZIER); + } + } + + if (alpha == 1) + light.set(r, g, b, a); + else if (fromSetup) { + SlotPose setupPose = slot.data.setup; + Color setup = setupPose.color; + light.set(setup.r + (r - setup.r) * alpha, setup.g + (g - setup.g) * alpha, setup.b + (b - setup.b) * alpha, + setup.a + (a - setup.a) * alpha); + setup = setupPose.darkColor; + r2 = setup.r + (r2 - setup.r) * alpha; + g2 = setup.g + (g2 - setup.g) * alpha; + b2 = setup.b + (b2 - setup.b) * alpha; + } else { + light.add((r - light.r) * alpha, (g - light.g) * alpha, (b - light.b) * alpha, (a - light.a) * alpha); + r2 = dark.r + (r2 - dark.r) * alpha; + g2 = dark.g + (g2 - dark.g) * alpha; + b2 = dark.b + (b2 - dark.b) * alpha; } dark.r = r2 < 0 ? 0 : (r2 > 1 ? 1 : r2); dark.g = g2 < 0 ? 0 : (g2 > 1 ? 1 : g2); @@ -1324,88 +1173,78 @@ public class Animation { frames[frame + B2] = b2; } - protected void apply (Slot slot, SlotPose pose, float time, float alpha, MixBlend blend) { + protected void apply (Slot slot, SlotPose pose, float time, float alpha, boolean fromSetup, boolean add) { Color light = pose.color, dark = pose.darkColor; float r, g, b, r2, g2, b2; float[] frames = this.frames; if (time < frames[0]) { - SlotPose setup = slot.data.setup; - Color setupLight = setup.color, setupDark = setup.darkColor; - switch (blend) { - case setup: + if (fromSetup) { + SlotPose setup = slot.data.setup; + Color setupLight = setup.color, setupDark = setup.darkColor; light.r = setupLight.r; light.g = setupLight.g; light.b = setupLight.b; dark.r = setupDark.r; dark.g = setupDark.g; dark.b = setupDark.b; - // Fall through. - default: - return; - case first: - r = light.r + (setupLight.r - light.r) * alpha; - g = light.g + (setupLight.g - light.g) * alpha; - b = light.b + (setupLight.b - light.b) * alpha; - r2 = dark.r + (setupDark.r - dark.r) * alpha; - g2 = dark.g + (setupDark.g - dark.g) * alpha; - b2 = dark.b + (setupDark.b - dark.b) * alpha; - } - } else { - int i = search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; - switch (curveType) { - case LINEAR -> { - float before = frames[i]; - r = frames[i + R]; - g = frames[i + G]; - b = frames[i + B]; - r2 = frames[i + R2]; - g2 = frames[i + G2]; - b2 = frames[i + B2]; - float t = (time - before) / (frames[i + ENTRIES] - before); - r += (frames[i + ENTRIES + R] - r) * t; - g += (frames[i + ENTRIES + G] - g) * t; - b += (frames[i + ENTRIES + B] - b) * t; - r2 += (frames[i + ENTRIES + R2] - r2) * t; - g2 += (frames[i + ENTRIES + G2] - g2) * t; - b2 += (frames[i + ENTRIES + B2] - b2) * t; - } - case STEPPED -> { - r = frames[i + R]; - g = frames[i + G]; - b = frames[i + B]; - r2 = frames[i + R2]; - g2 = frames[i + G2]; - b2 = frames[i + B2]; - } - default -> { - r = getBezierValue(time, i, R, curveType - BEZIER); - g = getBezierValue(time, i, G, curveType + BEZIER_SIZE - BEZIER); - b = getBezierValue(time, i, B, curveType + BEZIER_SIZE * 2 - BEZIER); - r2 = getBezierValue(time, i, R2, curveType + BEZIER_SIZE * 3 - BEZIER); - g2 = getBezierValue(time, i, G2, curveType + BEZIER_SIZE * 4 - BEZIER); - b2 = getBezierValue(time, i, B2, curveType + BEZIER_SIZE * 5 - BEZIER); - } } + return; + } - if (alpha != 1) { - if (blend == setup) { - SlotPose setupPose = slot.data.setup; - Color setup = setupPose.color; - r = setup.r + (r - setup.r) * alpha; - g = setup.g + (g - setup.g) * alpha; - b = setup.b + (b - setup.b) * alpha; - setup = setupPose.darkColor; - r2 = setup.r + (r2 - setup.r) * alpha; - g2 = setup.g + (g2 - setup.g) * alpha; - b2 = setup.b + (b2 - setup.b) * alpha; - } else { - r = light.r + (r - light.r) * alpha; - g = light.g + (g - light.g) * alpha; - b = light.b + (b - light.b) * alpha; - r2 = dark.r + (r2 - dark.r) * alpha; - g2 = dark.g + (g2 - dark.g) * alpha; - b2 = dark.b + (b2 - dark.b) * alpha; - } + int i = search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; + switch (curveType) { + case LINEAR -> { + float before = frames[i]; + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + r2 = frames[i + R2]; + g2 = frames[i + G2]; + b2 = frames[i + B2]; + float t = (time - before) / (frames[i + ENTRIES] - before); + r += (frames[i + ENTRIES + R] - r) * t; + g += (frames[i + ENTRIES + G] - g) * t; + b += (frames[i + ENTRIES + B] - b) * t; + r2 += (frames[i + ENTRIES + R2] - r2) * t; + g2 += (frames[i + ENTRIES + G2] - g2) * t; + b2 += (frames[i + ENTRIES + B2] - b2) * t; + } + case STEPPED -> { + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + r2 = frames[i + R2]; + g2 = frames[i + G2]; + b2 = frames[i + B2]; + } + default -> { + r = getBezierValue(time, i, R, curveType - BEZIER); + g = getBezierValue(time, i, G, curveType + BEZIER_SIZE - BEZIER); + b = getBezierValue(time, i, B, curveType + BEZIER_SIZE * 2 - BEZIER); + r2 = getBezierValue(time, i, R2, curveType + BEZIER_SIZE * 3 - BEZIER); + g2 = getBezierValue(time, i, G2, curveType + BEZIER_SIZE * 4 - BEZIER); + b2 = getBezierValue(time, i, B2, curveType + BEZIER_SIZE * 5 - BEZIER); + } + } + + if (alpha != 1) { + if (fromSetup) { + SlotPose setupPose = slot.data.setup; + Color setup = setupPose.color; + r = setup.r + (r - setup.r) * alpha; + g = setup.g + (g - setup.g) * alpha; + b = setup.b + (b - setup.b) * alpha; + setup = setupPose.darkColor; + r2 = setup.r + (r2 - setup.r) * alpha; + g2 = setup.g + (g2 - setup.g) * alpha; + b2 = setup.b + (b2 - setup.b) * alpha; + } else { + r = light.r + (r - light.r) * alpha; + g = light.g + (g - light.g) * alpha; + b = light.b + (b - light.b) * alpha; + r2 = dark.r + (r2 - dark.r) * alpha; + g2 = dark.g + (g2 - dark.g) * alpha; + b2 = dark.b + (b2 - dark.b) * alpha; } } light.r = r < 0 ? 0 : (r > 1 ? 1 : r); @@ -1426,6 +1265,7 @@ public class Animation { super(frameCount, Property.attachment.ordinal() + "|" + slotIndex); this.slotIndex = slotIndex; attachmentNames = new String[frameCount]; + instant = true; } public int getFrameCount () { @@ -1449,17 +1289,14 @@ public class Animation { attachmentNames[frame] = attachmentName; } - public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, - MixDirection direction, boolean appliedPose) { - + public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, boolean fromSetup, + boolean add, boolean out, boolean appliedPose) { Slot slot = skeleton.slots.items[slotIndex]; if (!slot.bone.active) return; SlotPose pose = appliedPose ? slot.applied : slot.pose; - if (direction == out) { - if (blend == setup) setAttachment(skeleton, pose, slot.data.attachmentName); - } else if (time < this.frames[0]) { - if (blend == setup || blend == first) setAttachment(skeleton, pose, slot.data.attachmentName); + if (out || time < this.frames[0]) { + if (fromSetup) setAttachment(skeleton, pose, slot.data.attachmentName); } else setAttachment(skeleton, pose, attachmentNames[search(this.frames, time)]); } @@ -1478,6 +1315,7 @@ public class Animation { super(frameCount, bezierCount, slotIndex, Property.deform.ordinal() + "|" + slotIndex + "|" + attachment.getId()); this.attachment = attachment; vertices = new float[frameCount][]; + additive = true; } public int getFrameCount () { @@ -1557,37 +1395,19 @@ public class Animation { return y + (1 - y) * (time - x) / (frames[frame + getFrameEntries()] - x); } - protected void apply (Slot slot, SlotPose pose, float time, float alpha, MixBlend blend) { + protected void apply (Slot slot, SlotPose pose, float time, float alpha, boolean fromSetup, boolean add) { if (!(pose.attachment instanceof VertexAttachment vertexAttachment) || vertexAttachment.getTimelineAttachment() != attachment) return; FloatArray deformArray = pose.deform; - if (deformArray.size == 0) blend = setup; + if (deformArray.size == 0) fromSetup = true; float[][] vertices = this.vertices; int vertexCount = vertices[0].length; float[] frames = this.frames; if (time < frames[0]) { - switch (blend) { - case setup -> deformArray.clear(); - case first -> { - if (alpha == 1) { - deformArray.clear(); - return; - } - float[] deform = deformArray.setSize(vertexCount); - if (vertexAttachment.getBones() == null) { // Unweighted vertex positions. - float[] setupVertices = vertexAttachment.getVertices(); - for (int i = 0; i < vertexCount; i++) - deform[i] += (setupVertices[i] - deform[i]) * alpha; - } else { // Weighted deform offsets. - alpha = 1 - alpha; - for (int i = 0; i < vertexCount; i++) - deform[i] *= alpha; - } - } - } + if (fromSetup) deformArray.clear(); return; } @@ -1596,7 +1416,7 @@ public class Animation { if (time >= frames[frames.length - 1]) { // Time is after last frame. float[] lastVertices = vertices[frames.length - 1]; if (alpha == 1) { - if (blend == add) { + if (add && !fromSetup) { if (vertexAttachment.getBones() == null) { // Unweighted vertex positions, no alpha. float[] setupVertices = vertexAttachment.getVertices(); for (int i = 0; i < vertexCount; i++) @@ -1607,35 +1427,29 @@ public class Animation { } } else // Vertex positions or deform offsets, no alpha. arraycopy(lastVertices, 0, deform, 0, vertexCount); - } else { - switch (blend) { - case setup -> { - if (vertexAttachment.getBones() == null) { // Unweighted vertex positions, with alpha. - float[] setupVertices = vertexAttachment.getVertices(); - for (int i = 0; i < vertexCount; i++) { - float setup = setupVertices[i]; - deform[i] = setup + (lastVertices[i] - setup) * alpha; - } - } else { // Weighted deform offsets, with alpha. - for (int i = 0; i < vertexCount; i++) - deform[i] = lastVertices[i] * alpha; + } else if (fromSetup) { + if (vertexAttachment.getBones() == null) { // Unweighted vertex positions, with alpha. + float[] setupVertices = vertexAttachment.getVertices(); + for (int i = 0; i < vertexCount; i++) { + float setup = setupVertices[i]; + deform[i] = setup + (lastVertices[i] - setup) * alpha; } - } - case first, replace -> { // Vertex positions or deform offsets, with alpha. + } else { // Weighted deform offsets, with alpha. for (int i = 0; i < vertexCount; i++) - deform[i] += (lastVertices[i] - deform[i]) * alpha; - } - case add -> { - if (vertexAttachment.getBones() == null) { // Unweighted vertex positions, no alpha. - float[] setupVertices = vertexAttachment.getVertices(); - for (int i = 0; i < vertexCount; i++) - deform[i] += (lastVertices[i] - setupVertices[i]) * alpha; - } else { // Weighted deform offsets, alpha. - for (int i = 0; i < vertexCount; i++) - deform[i] += lastVertices[i] * alpha; - } + deform[i] = lastVertices[i] * alpha; } + } else if (add) { + if (vertexAttachment.getBones() == null) { // Unweighted vertex positions, with alpha. + float[] setupVertices = vertexAttachment.getVertices(); + for (int i = 0; i < vertexCount; i++) + deform[i] += (lastVertices[i] - setupVertices[i]) * alpha; + } else { // Weighted deform offsets, with alpha. + for (int i = 0; i < vertexCount; i++) + deform[i] += lastVertices[i] * alpha; } + } else { // Vertex positions or deform offsets, with alpha. + for (int i = 0; i < vertexCount; i++) + deform[i] += (lastVertices[i] - deform[i]) * alpha; } return; } @@ -1646,7 +1460,7 @@ public class Animation { float[] nextVertices = vertices[frame + 1]; if (alpha == 1) { - if (blend == add) { + if (add && !fromSetup) { if (vertexAttachment.getBones() == null) { // Unweighted vertex positions, no alpha. float[] setupVertices = vertexAttachment.getVertices(); for (int i = 0; i < vertexCount; i++) { @@ -1667,42 +1481,36 @@ public class Animation { deform[i] = prev + (nextVertices[i] - prev) * percent; } } - } else { - switch (blend) { - case setup -> { - if (vertexAttachment.getBones() == null) { // Unweighted vertex positions, with alpha. - float[] setupVertices = vertexAttachment.getVertices(); - for (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i], setup = setupVertices[i]; - deform[i] = setup + (prev + (nextVertices[i] - prev) * percent - setup) * alpha; - } - } else { // Weighted deform offsets, with alpha. - for (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - deform[i] = (prev + (nextVertices[i] - prev) * percent) * alpha; - } + } else if (fromSetup) { + if (vertexAttachment.getBones() == null) { // Unweighted vertex positions, with alpha. + float[] setupVertices = vertexAttachment.getVertices(); + for (int i = 0; i < vertexCount; i++) { + float prev = prevVertices[i], setup = setupVertices[i]; + deform[i] = setup + (prev + (nextVertices[i] - prev) * percent - setup) * alpha; } - } - case first, replace -> {// Vertex positions or deform offsets, with alpha. + } else { // Weighted deform offsets, with alpha. for (int i = 0; i < vertexCount; i++) { float prev = prevVertices[i]; - deform[i] += (prev + (nextVertices[i] - prev) * percent - deform[i]) * alpha; + deform[i] = (prev + (nextVertices[i] - prev) * percent) * alpha; } } - case add -> { - if (vertexAttachment.getBones() == null) { // Unweighted vertex positions, with alpha. - float[] setupVertices = vertexAttachment.getVertices(); - for (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - deform[i] += (prev + (nextVertices[i] - prev) * percent - setupVertices[i]) * alpha; - } - } else { // Weighted deform offsets, with alpha. - for (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - deform[i] += (prev + (nextVertices[i] - prev) * percent) * alpha; - } + } else if (add) { + if (vertexAttachment.getBones() == null) { // Unweighted vertex positions, with alpha. + float[] setupVertices = vertexAttachment.getVertices(); + for (int i = 0; i < vertexCount; i++) { + float prev = prevVertices[i]; + deform[i] += (prev + (nextVertices[i] - prev) * percent - setupVertices[i]) * alpha; + } + } else { // Weighted deform offsets, with alpha. + for (int i = 0; i < vertexCount; i++) { + float prev = prevVertices[i]; + deform[i] += (prev + (nextVertices[i] - prev) * percent) * alpha; } } + } else { // Vertex positions or deform offsets, with alpha. + for (int i = 0; i < vertexCount; i++) { + float prev = prevVertices[i]; + deform[i] += (prev + (nextVertices[i] - prev) * percent - deform[i]) * alpha; } } } @@ -1720,6 +1528,7 @@ public class Animation { super(frameCount, Property.sequence.ordinal() + "|" + slotIndex + "|" + ((HasSequence)attachment).getSequence().getId()); this.slotIndex = slotIndex; this.attachment = (HasSequence)attachment; + instant = true; } public int getFrameEntries () { @@ -1747,9 +1556,8 @@ public class Animation { frames[frame + DELAY] = delay; } - public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, - MixDirection direction, boolean appliedPose) { - + public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, boolean fromSetup, + boolean add, boolean out, boolean appliedPose) { Slot slot = skeleton.slots.items[slotIndex]; if (!slot.bone.active) return; SlotPose pose = appliedPose ? slot.applied : slot.pose; @@ -1757,14 +1565,14 @@ public class Animation { Attachment slotAttachment = pose.attachment; if (!(slotAttachment instanceof HasSequence hasSequence) || slotAttachment.getTimelineAttachment() != attachment) return; - if (direction == out) { - if (blend == setup) pose.setSequenceIndex(-1); + if (out) { + if (fromSetup) pose.setSequenceIndex(-1); return; } float[] frames = this.frames; if (time < frames[0]) { - if (blend == setup || blend == first) pose.setSequenceIndex(-1); + if (fromSetup) pose.setSequenceIndex(-1); return; } @@ -1807,6 +1615,7 @@ public class Animation { public EventTimeline (int frameCount) { super(frameCount, propertyIds); events = new Event[frameCount]; + instant = true; } public int getFrameCount () { @@ -1827,15 +1636,14 @@ 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, boolean appliedPose) { - + boolean fromSetup, boolean add, boolean out, boolean appliedPose) { if (firedEvents == null) return; float[] frames = this.frames; int frameCount = frames.length; if (lastTime > time) { // Apply after lastTime for looped animations. - apply(skeleton, lastTime, Integer.MAX_VALUE, firedEvents, alpha, blend, direction, appliedPose); + apply(null, lastTime, Integer.MAX_VALUE, firedEvents, 0, false, false, false, false); lastTime = -1f; } else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame. return; @@ -1867,6 +1675,7 @@ public class Animation { public DrawOrderTimeline (int frameCount) { super(frameCount, propertyIds); drawOrders = new int[frameCount][]; + instant = true; } public int getFrameCount () { @@ -1887,17 +1696,10 @@ public class Animation { drawOrders[frame] = drawOrder; } - public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, - MixDirection direction, boolean appliedPose) { - - if (direction == out) { - if (blend == setup) arraycopy(skeleton.slots.items, 0, skeleton.drawOrder.items, 0, skeleton.slots.size); - return; - } - - if (time < frames[0]) { - if (blend == setup || blend == first) - arraycopy(skeleton.slots.items, 0, skeleton.drawOrder.items, 0, skeleton.slots.size); + public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, boolean fromSetup, + boolean add, boolean out, boolean appliedPose) { + if (out || time < frames[0]) { + if (fromSetup) arraycopy(skeleton.slots.items, 0, skeleton.drawOrder.items, 0, skeleton.slots.size); return; } @@ -1928,6 +1730,7 @@ public class Animation { inFolder = new boolean[slotCount]; for (int i : slots) inFolder[i] = true; + instant = true; } static private String[] propertyIds (int[] slots) { @@ -1961,13 +1764,10 @@ public class Animation { drawOrders[frame] = drawOrder; } - public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, - MixDirection direction, boolean appliedPose) { - - if (direction == out) { - if (blend == setup) setup(skeleton); - } else if (time < frames[0]) { - if (blend == setup || blend == first) setup(skeleton); + public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, boolean fromSetup, + boolean add, boolean out, boolean appliedPose) { + if (out || time < frames[0]) { + if (fromSetup) setup(skeleton); } else { int[] order = drawOrders[search(frames, time)]; if (order == null) @@ -2045,32 +1845,22 @@ public class Animation { frames[frame + STRETCH] = stretch ? 1 : 0; } - public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, - MixDirection direction, boolean appliedPose) { - + public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, boolean fromSetup, + boolean add, boolean out, boolean appliedPose) { var constraint = (IkConstraint)skeleton.constraints.items[constraintIndex]; if (!constraint.active) return; IkConstraintPose pose = appliedPose ? constraint.applied : constraint.pose; float[] frames = this.frames; if (time < frames[0]) { - IkConstraintPose setup = constraint.data.setup; - switch (blend) { - case setup -> { + if (fromSetup) { + IkConstraintPose setup = constraint.data.setup; pose.mix = setup.mix; pose.softness = setup.softness; pose.bendDirection = setup.bendDirection; pose.compress = setup.compress; pose.stretch = setup.stretch; } - case first -> { - pose.mix += (setup.mix - pose.mix) * alpha; - pose.softness += (setup.softness - pose.softness) * alpha; - pose.bendDirection = setup.bendDirection; - pose.compress = setup.compress; - pose.stretch = setup.stretch; - } - } return; } @@ -2095,24 +1885,20 @@ public class Animation { } } - if (blend == setup) { - IkConstraintPose setup = constraint.data.setup; - pose.mix = setup.mix + (mix - setup.mix) * alpha; - pose.softness = setup.softness + (softness - setup.softness) * alpha; - if (direction == out) { - pose.bendDirection = setup.bendDirection; - pose.compress = setup.compress; - pose.stretch = setup.stretch; - return; + IkConstraintPose base = fromSetup ? constraint.data.setup : pose; + pose.mix = base.mix + (mix - base.mix) * alpha; + pose.softness = base.softness + (softness - base.softness) * alpha; + if (out) { + if (fromSetup) { + pose.bendDirection = base.bendDirection; + pose.compress = base.compress; + pose.stretch = base.stretch; } } else { - pose.mix += (mix - pose.mix) * alpha; - pose.softness += (softness - pose.softness) * alpha; - if (direction == out) return; + pose.bendDirection = (int)frames[i + BEND_DIRECTION]; + pose.compress = frames[i + COMPRESS] != 0; + pose.stretch = frames[i + STRETCH] != 0; } - pose.bendDirection = (int)frames[i + BEND_DIRECTION]; - pose.compress = frames[i + COMPRESS] != 0; - pose.stretch = frames[i + STRETCH] != 0; } } @@ -2128,6 +1914,7 @@ public class Animation { public TransformConstraintTimeline (int frameCount, int bezierCount, int constraintIndex) { super(frameCount, bezierCount, Property.transformConstraint.ordinal() + "|" + constraintIndex); this.constraintIndex = constraintIndex; + additive = true; } public int getFrameEntries () { @@ -2153,18 +1940,16 @@ public class Animation { frames[frame + SHEARY] = mixShearY; } - public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, - MixDirection direction, boolean appliedPose) { - + public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, boolean fromSetup, + boolean add, boolean out, boolean appliedPose) { var constraint = (TransformConstraint)skeleton.constraints.items[constraintIndex]; if (!constraint.active) return; TransformConstraintPose pose = appliedPose ? constraint.applied : constraint.pose; float[] frames = this.frames; if (time < frames[0]) { - TransformConstraintPose setup = constraint.data.setup; - switch (blend) { - case setup -> { + if (fromSetup) { + TransformConstraintPose setup = constraint.data.setup; pose.mixRotate = setup.mixRotate; pose.mixX = setup.mixX; pose.mixY = setup.mixY; @@ -2172,15 +1957,6 @@ public class Animation { pose.mixScaleY = setup.mixScaleY; pose.mixShearY = setup.mixShearY; } - case first -> { - pose.mixRotate += (setup.mixRotate - pose.mixRotate) * alpha; - pose.mixX += (setup.mixX - pose.mixX) * alpha; - pose.mixY += (setup.mixY - pose.mixY) * alpha; - pose.mixScaleX += (setup.mixScaleX - pose.mixScaleX) * alpha; - pose.mixScaleY += (setup.mixScaleY - pose.mixScaleY) * alpha; - pose.mixShearY += (setup.mixShearY - pose.mixShearY) * alpha; - } - } return; } @@ -2221,32 +1997,21 @@ public class Animation { } } - switch (blend) { - case setup -> { - TransformConstraintPose setup = constraint.data.setup; - pose.mixRotate = setup.mixRotate + (rotate - setup.mixRotate) * alpha; - pose.mixX = setup.mixX + (x - setup.mixX) * alpha; - pose.mixY = setup.mixY + (y - setup.mixY) * alpha; - pose.mixScaleX = setup.mixScaleX + (scaleX - setup.mixScaleX) * alpha; - pose.mixScaleY = setup.mixScaleY + (scaleY - setup.mixScaleY) * alpha; - pose.mixShearY = setup.mixShearY + (shearY - setup.mixShearY) * alpha; - } - case first, replace -> { - pose.mixRotate += (rotate - pose.mixRotate) * alpha; - pose.mixX += (x - pose.mixX) * alpha; - pose.mixY += (y - pose.mixY) * alpha; - pose.mixScaleX += (scaleX - pose.mixScaleX) * alpha; - pose.mixScaleY += (scaleY - pose.mixScaleY) * alpha; - pose.mixShearY += (shearY - pose.mixShearY) * alpha; - } - case add -> { - pose.mixRotate += rotate * alpha; - pose.mixX += x * alpha; - pose.mixY += y * alpha; - pose.mixScaleX += scaleX * alpha; - pose.mixScaleY += scaleY * alpha; - pose.mixShearY += shearY * alpha; - } + TransformConstraintPose base = fromSetup ? constraint.data.setup : pose; + if (add) { + pose.mixRotate = base.mixRotate + rotate * alpha; + pose.mixX = base.mixX + x * alpha; + pose.mixY = base.mixY + y * alpha; + pose.mixScaleX = base.mixScaleX + scaleX * alpha; + pose.mixScaleY = base.mixScaleY + scaleY * alpha; + pose.mixShearY = base.mixShearY + shearY * alpha; + } else { + pose.mixRotate = base.mixRotate + (rotate - base.mixRotate) * alpha; + pose.mixX = base.mixX + (x - base.mixX) * alpha; + pose.mixY = base.mixY + (y - base.mixY) * alpha; + pose.mixScaleX = base.mixScaleX + (scaleX - base.mixScaleX) * alpha; + pose.mixScaleY = base.mixScaleY + (scaleY - base.mixScaleY) * alpha; + pose.mixShearY = base.mixShearY + (shearY - base.mixShearY) * alpha; } } } @@ -2268,15 +2033,15 @@ public class Animation { static public class PathConstraintPositionTimeline extends ConstraintTimeline1 { public PathConstraintPositionTimeline (int frameCount, int bezierCount, int constraintIndex) { super(frameCount, bezierCount, constraintIndex, Property.pathConstraintPosition); + additive = true; } - public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, - MixDirection direction, boolean appliedPose) { - + public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, boolean fromSetup, + boolean add, boolean out, boolean appliedPose) { var constraint = (PathConstraint)skeleton.constraints.items[constraintIndex]; if (constraint.active) { PathConstraintPose pose = appliedPose ? constraint.applied : constraint.pose; - pose.position = getAbsoluteValue(time, alpha, blend, pose.position, constraint.data.setup.position); + pose.position = getAbsoluteValue(time, alpha, fromSetup, add, pose.position, constraint.data.setup.position); } } } @@ -2287,14 +2052,12 @@ public class Animation { super(frameCount, bezierCount, constraintIndex, Property.pathConstraintSpacing); } - public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, - MixDirection direction, boolean appliedPose) { - + public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, boolean fromSetup, + boolean add, boolean out, boolean appliedPose) { var constraint = (PathConstraint)skeleton.constraints.items[constraintIndex]; if (constraint.active) { PathConstraintPose pose = appliedPose ? constraint.applied : constraint.pose; - pose.spacing = getAbsoluteValue(time, alpha, blend == add ? replace : blend, pose.spacing, - constraint.data.setup.spacing); + pose.spacing = getAbsoluteValue(time, alpha, fromSetup, false, pose.spacing, constraint.data.setup.spacing); } } } @@ -2310,6 +2073,7 @@ public class Animation { public PathConstraintMixTimeline (int frameCount, int bezierCount, int constraintIndex) { super(frameCount, bezierCount, Property.pathConstraintMix.ordinal() + "|" + constraintIndex); this.constraintIndex = constraintIndex; + additive = true; } public int getFrameEntries () { @@ -2331,28 +2095,20 @@ public class Animation { frames[frame + Y] = mixY; } - public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, - MixDirection direction, boolean appliedPose) { - + public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, boolean fromSetup, + boolean add, boolean out, boolean appliedPose) { var constraint = (PathConstraint)skeleton.constraints.items[constraintIndex]; if (!constraint.active) return; PathConstraintPose pose = appliedPose ? constraint.applied : constraint.pose; float[] frames = this.frames; if (time < frames[0]) { - PathConstraintPose setup = constraint.data.setup; - switch (blend) { - case setup -> { + if (fromSetup) { + PathConstraintPose setup = constraint.data.setup; pose.mixRotate = setup.mixRotate; pose.mixX = setup.mixX; pose.mixY = setup.mixY; } - case first -> { - pose.mixRotate += (setup.mixRotate - pose.mixRotate) * alpha; - pose.mixX += (setup.mixX - pose.mixX) * alpha; - pose.mixY += (setup.mixY - pose.mixY) * alpha; - } - } return; } @@ -2381,32 +2137,23 @@ public class Animation { } } - if (blend == setup) { - PathConstraintPose setup = constraint.data.setup; - pose.mixRotate = setup.mixRotate + (rotate - setup.mixRotate) * alpha; - pose.mixX = setup.mixX + (x - setup.mixX) * alpha; - pose.mixY = setup.mixY + (y - setup.mixY) * alpha; - } else { - pose.mixRotate += (rotate - pose.mixRotate) * alpha; - pose.mixX += (x - pose.mixX) * alpha; - pose.mixY += (y - pose.mixY) * alpha; - } + PathConstraintPose base = fromSetup ? constraint.data.setup : pose; + pose.mixRotate = base.mixRotate + (rotate - base.mixRotate) * alpha; + pose.mixX = base.mixX + (x - base.mixX) * alpha; + pose.mixY = base.mixY + (y - base.mixY) * alpha; } } /** The base class for most {@link PhysicsConstraint} timelines. */ static abstract public class PhysicsConstraintTimeline extends ConstraintTimeline1 { - boolean additive; - /** @param constraintIndex -1 for all physics constraints in the skeleton. */ public PhysicsConstraintTimeline (int frameCount, int bezierCount, int constraintIndex, Property property) { super(frameCount, bezierCount, constraintIndex, property); } - public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, - MixDirection direction, boolean appliedPose) { - - if (blend == add && !additive) blend = replace; + public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, boolean fromSetup, + boolean add, boolean out, boolean appliedPose) { + if (add && !additive) add = false; if (constraintIndex == -1) { float value = time >= frames[0] ? getCurveValue(time) : 0; PhysicsConstraint[] constraints = skeleton.physics.items; @@ -2414,14 +2161,14 @@ public class Animation { PhysicsConstraint constraint = constraints[i]; if (constraint.active && global(constraint.data)) { PhysicsConstraintPose pose = appliedPose ? constraint.applied : constraint.pose; - set(pose, getAbsoluteValue(time, alpha, blend, get(pose), get(constraint.data.setup), value)); + set(pose, getAbsoluteValue(time, alpha, fromSetup, add, get(pose), get(constraint.data.setup), value)); } } } else { var constraint = (PhysicsConstraint)skeleton.constraints.items[constraintIndex]; if (constraint.active) { PhysicsConstraintPose pose = appliedPose ? constraint.applied : constraint.pose; - set(pose, getAbsoluteValue(time, alpha, blend, get(pose), get(constraint.data.setup))); + set(pose, getAbsoluteValue(time, alpha, fromSetup, add, get(pose), get(constraint.data.setup))); } } } @@ -2578,6 +2325,7 @@ public class Animation { public PhysicsConstraintResetTimeline (int frameCount, int constraintIndex) { super(frameCount, propertyIds); this.constraintIndex = constraintIndex; + instant = true; } /** The index of the physics constraint in {@link Skeleton#getConstraints()} that will be reset when this timeline is @@ -2597,9 +2345,8 @@ 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, boolean appliedPose) { - + public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, boolean fromSetup, + boolean add, boolean out, boolean appliedPose) { PhysicsConstraint constraint = null; if (constraintIndex != -1) { constraint = (PhysicsConstraint)skeleton.constraints.items[constraintIndex]; @@ -2609,7 +2356,7 @@ 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, appliedPose); + apply(skeleton, lastTime, Integer.MAX_VALUE, null, alpha, false, false, false, false); lastTime = -1f; } else if (lastTime >= frames[frames.length - 1]) // Last time is after last frame. return; @@ -2633,15 +2380,15 @@ public class Animation { static public class SliderTimeline extends ConstraintTimeline1 { public SliderTimeline (int frameCount, int bezierCount, int constraintIndex) { super(frameCount, bezierCount, constraintIndex, Property.sliderTime); + additive = true; } - public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, - MixDirection direction, boolean appliedPose) { - + public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, boolean fromSetup, + boolean add, boolean out, boolean appliedPose) { var constraint = (Slider)skeleton.constraints.items[constraintIndex]; if (constraint.active) { SliderPose pose = appliedPose ? constraint.applied : constraint.pose; - pose.time = getAbsoluteValue(time, alpha, blend, pose.time, constraint.data.setup.time); + pose.time = getAbsoluteValue(time, alpha, fromSetup, add, pose.time, constraint.data.setup.time); } } } @@ -2650,15 +2397,15 @@ public class Animation { static public class SliderMixTimeline extends ConstraintTimeline1 { public SliderMixTimeline (int frameCount, int bezierCount, int constraintIndex) { super(frameCount, bezierCount, constraintIndex, Property.sliderMix); + additive = true; } - public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, - MixDirection direction, boolean appliedPose) { - + public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, boolean fromSetup, + boolean add, boolean out, boolean appliedPose) { var constraint = (Slider)skeleton.constraints.items[constraintIndex]; if (constraint.active) { SliderPose pose = appliedPose ? constraint.applied : constraint.pose; - pose.mix = getAbsoluteValue(time, alpha, blend, pose.mix, constraint.data.setup.mix); + pose.mix = getAbsoluteValue(time, alpha, fromSetup, add, pose.mix, constraint.data.setup.mix); } } } 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 37147697f..2de58c990 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java @@ -41,9 +41,6 @@ import com.badlogic.gdx.utils.SnapshotArray; import com.esotericsoftware.spine.Animation.AttachmentTimeline; import com.esotericsoftware.spine.Animation.DrawOrderFolderTimeline; import com.esotericsoftware.spine.Animation.DrawOrderTimeline; -import com.esotericsoftware.spine.Animation.EventTimeline; -import com.esotericsoftware.spine.Animation.MixBlend; -import com.esotericsoftware.spine.Animation.MixDirection; import com.esotericsoftware.spine.Animation.RotateTimeline; import com.esotericsoftware.spine.Animation.Timeline; @@ -213,16 +210,12 @@ public class AnimationState { if (current == null || current.delay > 0) continue; applied = true; - // Track 0 animations aren't for layering, so never use current values before the first key. - MixBlend blend = i == 0 ? MixBlend.first : current.mixBlend; - // Apply mixing from entries first. float alpha = current.alpha; if (current.mixingFrom != null) alpha *= applyMixingFrom(current, skeleton); else if (current.trackTime >= current.trackEnd && current.next == null) // alpha = 0; // Set to setup pose the last time the entry will be applied. - boolean attachments = alpha >= current.alphaAttachmentThreshold; // Apply current entry. float animationLast = current.animationLast, animationTime = current.getAnimationTime(), applyTime = animationTime; @@ -233,33 +226,31 @@ public class AnimationState { } int timelineCount = current.animation.timelines.size; Timeline[] timelines = current.animation.timelines.items; - if ((i == 0 && alpha == 1) || blend == MixBlend.add) { - if (i == 0) attachments = true; + if (i == 0 && alpha == 1) { for (int ii = 0; ii < timelineCount; ii++) { Timeline timeline = timelines[ii]; if (timeline instanceof AttachmentTimeline attachmentTimeline) - applyAttachmentTimeline(attachmentTimeline, skeleton, applyTime, blend, false, attachments); + applyAttachmentTimeline(attachmentTimeline, skeleton, applyTime, true, false, true); else - timeline.apply(skeleton, animationLast, applyTime, applyEvents, alpha, blend, MixDirection.in, false); + timeline.apply(skeleton, animationLast, applyTime, applyEvents, alpha, true, false, false, false); } } else { int[] timelineMode = current.timelineMode.items; - - boolean shortestRotation = current.shortestRotation; + boolean attachments = alpha >= current.alphaAttachmentThreshold; + boolean add = current.additive, shortestRotation = add || current.shortestRotation; boolean firstFrame = !shortestRotation && current.timelinesRotation.size != timelineCount << 1; - if (firstFrame) current.timelinesRotation.setSize(timelineCount << 1); - float[] timelinesRotation = current.timelinesRotation.items; - + float[] timelinesRotation = firstFrame ? current.timelinesRotation.setSize(timelineCount << 1) + : current.timelinesRotation.items; for (int ii = 0; ii < timelineCount; ii++) { Timeline timeline = timelines[ii]; - MixBlend timelineBlend = timelineMode[ii] == SUBSEQUENT ? current.mixBlend : MixBlend.setup; + boolean fromSetup = timelineMode[ii] == FIRST; if (!shortestRotation && timeline instanceof RotateTimeline rotateTimeline) { - applyRotateTimeline(rotateTimeline, skeleton, applyTime, alpha, timelineBlend, timelinesRotation, ii << 1, + applyRotateTimeline(rotateTimeline, skeleton, applyTime, alpha, fromSetup, timelinesRotation, ii << 1, firstFrame); } else if (timeline instanceof AttachmentTimeline attachmentTimeline) - applyAttachmentTimeline(attachmentTimeline, skeleton, applyTime, blend, false, attachments); + applyAttachmentTimeline(attachmentTimeline, skeleton, applyTime, fromSetup, false, attachments); else - timeline.apply(skeleton, animationLast, applyTime, applyEvents, alpha, timelineBlend, MixDirection.in, false); + timeline.apply(skeleton, animationLast, applyTime, applyEvents, alpha, fromSetup, add, false, false); } } queueEvents(current, animationTime); @@ -309,62 +300,49 @@ public class AnimationState { else { if (mix < from.eventThreshold) events = this.events; } - - MixBlend blend = from.mixBlend; - if (blend == MixBlend.add) { - for (int i = 0; i < timelineCount; i++) - timelines[i].apply(skeleton, animationLast, applyTime, events, alphaMix, blend, MixDirection.out, false); - } else { - int[] timelineMode = from.timelineMode.items; - TrackEntry[] timelineHoldMix = from.timelineHoldMix.items; - - boolean shortestRotation = from.shortestRotation; - boolean firstFrame = !shortestRotation && from.timelinesRotation.size != timelineCount << 1; - if (firstFrame) from.timelinesRotation.setSize(timelineCount << 1); - float[] timelinesRotation = from.timelinesRotation.items; - - from.totalAlpha = 0; - for (int i = 0; i < timelineCount; i++) { - Timeline timeline = timelines[i]; - MixBlend timelineBlend; - float alpha; - switch (timelineMode[i]) { - case SUBSEQUENT -> { - if (!drawOrder && timeline instanceof DrawOrderTimeline) continue; - timelineBlend = blend; - alpha = alphaMix; - } - case FIRST -> { - timelineBlend = MixBlend.setup; - alpha = alphaMix; - } - case HOLD_SUBSEQUENT -> { - timelineBlend = blend; - alpha = alphaHold; - } - case HOLD_FIRST -> { - timelineBlend = MixBlend.setup; - alpha = alphaHold; - } - default -> { // HOLD_MIX - timelineBlend = MixBlend.setup; - TrackEntry holdMix = timelineHoldMix[i]; - alpha = alphaHold * Math.max(0, 1 - holdMix.mixTime / holdMix.mixDuration); - } - } - from.totalAlpha += alpha; - if (!shortestRotation && timeline instanceof RotateTimeline rotateTimeline) { - applyRotateTimeline(rotateTimeline, skeleton, applyTime, alpha, timelineBlend, timelinesRotation, i << 1, - firstFrame); - } else if (timeline instanceof AttachmentTimeline attachmentTimeline) - applyAttachmentTimeline(attachmentTimeline, skeleton, applyTime, timelineBlend, true, - attachments && alpha >= from.alphaAttachmentThreshold); - else { - MixDirection direction = MixDirection.out; - if (drawOrder && timeline instanceof DrawOrderTimeline && timelineBlend == MixBlend.setup) - direction = MixDirection.in; - timeline.apply(skeleton, animationLast, applyTime, events, alpha, timelineBlend, direction, false); - } + int[] timelineMode = from.timelineMode.items; + TrackEntry[] timelineHoldMix = from.timelineHoldMix.items; + boolean add = from.additive, shortestRotation = add || from.shortestRotation; + boolean firstFrame = !shortestRotation && from.timelinesRotation.size != timelineCount << 1; + float[] timelinesRotation = firstFrame ? from.timelinesRotation.setSize(timelineCount << 1) : from.timelinesRotation.items; + from.totalAlpha = 0; + for (int i = 0; i < timelineCount; i++) { + Timeline timeline = timelines[i]; + boolean fromSetup; + float alpha; + switch (timelineMode[i]) { + case SUBSEQUENT -> { + if (!drawOrder && timeline instanceof DrawOrderTimeline) continue; + fromSetup = false; + alpha = alphaMix; + } + case FIRST -> { + fromSetup = true; + alpha = alphaMix; + } + case HOLD_SUBSEQUENT -> { + fromSetup = false; + alpha = alphaHold; + } + case HOLD_FIRST -> { + fromSetup = true; + alpha = alphaHold; + } + default -> { // HOLD_MIX + fromSetup = true; + TrackEntry holdMix = timelineHoldMix[i]; + alpha = alphaHold * Math.max(0, 1 - holdMix.mixTime / holdMix.mixDuration); + } + } + from.totalAlpha += alpha; + if (!shortestRotation && timeline instanceof RotateTimeline rotateTimeline) { + applyRotateTimeline(rotateTimeline, skeleton, applyTime, alpha, fromSetup, timelinesRotation, i << 1, firstFrame); + } else if (timeline instanceof AttachmentTimeline attachmentTimeline) + applyAttachmentTimeline(attachmentTimeline, skeleton, applyTime, fromSetup, true, + attachments && alpha >= from.alphaAttachmentThreshold); + else { + boolean out = !drawOrder || !(timeline instanceof DrawOrderTimeline) || !fromSetup; + timeline.apply(skeleton, animationLast, applyTime, events, alpha, fromSetup, add, out, false); } } @@ -380,17 +358,14 @@ public class AnimationState { * @param attachments False when: 1) the attachment timeline is mixing out, 2) mix < attachmentThreshold, and 3) the timeline * is not the last timeline to set the slot's attachment. In that case the timeline is applied only so subsequent * timelines see any deform. */ - private void applyAttachmentTimeline (AttachmentTimeline timeline, Skeleton skeleton, float time, MixBlend blend, boolean out, - boolean attachments) { + private void applyAttachmentTimeline (AttachmentTimeline timeline, Skeleton skeleton, float time, boolean fromSetup, + boolean out, boolean attachments) { Slot slot = skeleton.slots.items[timeline.slotIndex]; if (!slot.bone.active) return; - if (out) { - if (blend == MixBlend.setup) setAttachment(skeleton, slot, slot.data.attachmentName, attachments); - } else if (time < timeline.frames[0]) { // Time is before first frame. - if (blend == MixBlend.setup || blend == MixBlend.first) - setAttachment(skeleton, slot, slot.data.attachmentName, attachments); + if (out || time < timeline.frames[0]) { + if (fromSetup) setAttachment(skeleton, slot, slot.data.attachmentName, attachments); } else setAttachment(skeleton, slot, timeline.attachmentNames[Timeline.search(timeline.frames, time)], attachments); @@ -405,13 +380,13 @@ public class AnimationState { /** Applies the rotate timeline, mixing with the current pose while keeping the same rotation direction chosen as the shortest * the first time the mixing was applied. */ - private void applyRotateTimeline (RotateTimeline timeline, Skeleton skeleton, float time, float alpha, MixBlend blend, + private void applyRotateTimeline (RotateTimeline timeline, Skeleton skeleton, float time, float alpha, boolean fromSetup, float[] timelinesRotation, int i, boolean firstFrame) { if (firstFrame) timelinesRotation[i] = 0; if (alpha == 1) { - timeline.apply(skeleton, 0, time, null, 1, blend, MixDirection.in, false); + timeline.apply(skeleton, 0, time, null, 1, fromSetup, false, false, false); return; } @@ -419,22 +394,12 @@ public class AnimationState { if (!bone.active) return; BoneLocal pose = bone.pose, setup = bone.data.setup; float[] frames = timeline.frames; - float r1, r2; if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case setup: - pose.rotation = setup.rotation; - // Fall through. - default: - return; - case first: - r1 = pose.rotation; - r2 = setup.rotation; - } - } else { - r1 = blend == MixBlend.setup ? setup.rotation : pose.rotation; - r2 = setup.rotation + timeline.getCurveValue(time); + if (fromSetup) pose.rotation = setup.rotation; + return; } + float r1 = fromSetup ? setup.rotation : pose.rotation; + float r2 = setup.rotation + timeline.getCurveValue(time); // Mix between rotations using the direction of the shortest route on the first frame. float total, diff = r2 - r1; @@ -727,6 +692,7 @@ public class AnimationState { entry.loop = loop; entry.holdPrevious = false; + entry.additive = false; entry.reverse = false; entry.shortestRotation = false; @@ -752,7 +718,6 @@ public class AnimationState { entry.mixDuration = last == null ? 0 : data.getMix(last.animation, animation); entry.interruptAlpha = 1; entry.totalAlpha = 0; - entry.mixBlend = MixBlend.replace; return entry; } @@ -770,7 +735,6 @@ public class AnimationState { animationsChanged = false; // Process in the order that animations are applied. - propertyIds.clear(2048); int n = tracks.size; TrackEntry[] tracks = this.tracks.items; for (int i = 0; i < n; i++) { @@ -779,44 +743,43 @@ public class AnimationState { while (entry.mixingFrom != null) // Move to last entry, then iterate in reverse. entry = entry.mixingFrom; do { - if (entry.mixingTo == null || entry.mixBlend != MixBlend.add) computeHold(entry); + computeHold(entry); entry = entry.mixingTo; } while (entry != null); } + propertyIds.clear(2048); } private void computeHold (TrackEntry entry) { - TrackEntry to = entry.mixingTo; Timeline[] timelines = entry.animation.timelines.items; int timelinesCount = entry.animation.timelines.size; int[] timelineMode = entry.timelineMode.setSize(timelinesCount); entry.timelineHoldMix.clear(); TrackEntry[] timelineHoldMix = entry.timelineHoldMix.setSize(timelinesCount); ObjectSet propertyIds = this.propertyIds; - - if (to != null && to.holdPrevious) { - for (int i = 0; i < timelinesCount; i++) { - boolean first = propertyIds.addAll(timelines[i].getPropertyIds()); - if (first && timelines[i] instanceof DrawOrderFolderTimeline && propertyIds.contains(DrawOrderTimeline.propertyID)) - first = false; // DrawOrderTimeline changed. - timelineMode[i] = first ? HOLD_FIRST : HOLD_SUBSEQUENT; - } - return; + boolean holdPrevious = false, add = entry.additive; + TrackEntry to = entry.mixingTo; + if (to != null) { + if (to.additive) + to = null; + else + holdPrevious = to.holdPrevious; } - outer: for (int i = 0; i < timelinesCount; i++) { Timeline timeline = timelines[i]; - String[] ids = timeline.getPropertyIds(); - if (!propertyIds.addAll(ids)) - timelineMode[i] = SUBSEQUENT; - else if (timeline instanceof DrawOrderFolderTimeline && propertyIds.contains(DrawOrderTimeline.propertyID)) - timelineMode[i] = SUBSEQUENT; // DrawOrderTimeline changed. - else if (to == null || timeline instanceof AttachmentTimeline || timeline instanceof DrawOrderTimeline - || timeline instanceof DrawOrderFolderTimeline || timeline instanceof EventTimeline - || !to.animation.hasTimeline(ids)) { + String[] ids = timeline.propertyIds; + boolean first = propertyIds.addAll(ids) + && !(timeline instanceof DrawOrderFolderTimeline && propertyIds.contains(DrawOrderTimeline.propertyID)); + if (add && timeline.additive) + timelineMode[i] = first ? FIRST : SUBSEQUENT; + else if (!first) + timelineMode[i] = holdPrevious ? HOLD_SUBSEQUENT : SUBSEQUENT; + else if (holdPrevious) + timelineMode[i] = HOLD_FIRST; + else if (to == null || timeline.instant || !to.animation.hasTimeline(ids)) timelineMode[i] = FIRST; - } else { + else { for (TrackEntry next = to.mixingTo; next != null; next = next.mixingTo) { if (next.animation.hasTimeline(ids)) continue; if (next.mixDuration > 0) { @@ -909,12 +872,11 @@ public class AnimationState { @Null TrackEntry previous, next, mixingFrom, mixingTo; @Null AnimationStateListener listener; int trackIndex; - boolean loop, holdPrevious, reverse, shortestRotation; + boolean loop, holdPrevious, additive, reverse, shortestRotation; float eventThreshold, mixAttachmentThreshold, alphaAttachmentThreshold, mixDrawOrderThreshold; float animationStart, animationEnd, animationLast, nextAnimationLast; float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale; float alpha, mixTime, mixDuration, interruptAlpha, totalAlpha; - MixBlend mixBlend = MixBlend.replace; final IntArray timelineMode = new IntArray(); final Array timelineHoldMix = new Array(true, 8, TrackEntry[]::new); @@ -1233,26 +1195,18 @@ public class AnimationState { * entry is looping, its next loop completion is used instead of its duration. */ public void setMixDuration (float mixDuration, float delay) { this.mixDuration = mixDuration; - if (delay <= 0) { - if (previous != null) - delay = Math.max(delay + previous.getTrackComplete() - mixDuration, 0); - else - delay = 0; - } + if (delay <= 0) delay = previous == null ? 0 : Math.max(delay + previous.getTrackComplete() - mixDuration, 0); this.delay = delay; } - /** Controls how properties keyed in the animation are mixed with lower tracks. Defaults to {@link MixBlend#replace}. - *

- * The mixBlend can be set for a new track entry only before {@link AnimationState#apply(Skeleton)} is next - * called. */ - public MixBlend getMixBlend () { - return mixBlend; + /** When true, timelines in this animation that support additive are added to the setup or current pose. Additive can be set + * for a new track entry only before {@link AnimationState#apply(Skeleton)} is next called. */ + public boolean getAdditive () { + return additive; } - public void setMixBlend (MixBlend mixBlend) { - if (mixBlend == null) throw new IllegalArgumentException("mixBlend cannot be null."); - this.mixBlend = mixBlend; + public void setAdditive (boolean additive) { + this.additive = additive; } /** The track entry for the previous animation when mixing from the previous animation to this animation, or null if no @@ -1302,10 +1256,10 @@ public class AnimationState { /** Resets the rotation directions for mixing this entry's rotate timelines. This can be useful to avoid bones rotating the * long way around when using {@link #getAlpha()} and starting animations on other tracks. *

- * Mixing with {@link MixBlend#replace} involves finding a rotation between two others, which has two possible solutions: - * the short way or the long way around. The two rotations likely change over time, so which direction is the short or long - * way also changes. If the short way was always chosen, bones would flip to the other side when that direction became the - * long way. TrackEntry chooses the short way the first time it is applied and remembers that direction. */ + * Mixing involves finding a rotation between two others, which has two possible solutions: the short way or the long way + * around. The two rotations likely change over time, so which direction is the short or long way also changes. If the short + * way was always chosen, bones would flip to the other side when that direction became the long way. TrackEntry chooses the + * short way the first time it is applied and remembers that direction. */ public void resetRotationDirections () { timelinesRotation.clear(); } 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 2fb9fb78a..c644c56df 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, boolean)}, + * {@link Timeline#apply(Skeleton, float, float, com.badlogic.gdx.utils.Array, float, boolean, boolean, boolean, 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/Slider.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Slider.java index b11df297c..3ae7fafd9 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Slider.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Slider.java @@ -30,8 +30,6 @@ package com.esotericsoftware.spine; import com.esotericsoftware.spine.Animation.ConstraintTimeline; -import com.esotericsoftware.spine.Animation.MixBlend; -import com.esotericsoftware.spine.Animation.MixDirection; import com.esotericsoftware.spine.Animation.PhysicsConstraintTimeline; import com.esotericsoftware.spine.Animation.SlotTimeline; import com.esotericsoftware.spine.Animation.Timeline; @@ -78,8 +76,7 @@ public class Slider extends Constraint { for (int i = 0, n = animation.bones.size; i < n; i++) bones[indices[i]].applied.modifyLocal(skeleton); - animation.apply(skeleton, p.time, p.time, data.loop, null, p.mix, data.additive ? MixBlend.add : MixBlend.replace, - MixDirection.in, true); + animation.apply(skeleton, p.time, p.time, data.loop, null, p.mix, false, data.additive, false, true); } void sort (Skeleton skeleton) { diff --git a/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java b/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java index ca8699358..5e2e0775f 100644 --- a/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java +++ b/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java @@ -50,7 +50,6 @@ import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Null; import com.badlogic.gdx.utils.viewport.ScreenViewport; -import com.esotericsoftware.spine.Animation.MixBlend; import com.esotericsoftware.spine.AnimationState.AnimationStateAdapter; import com.esotericsoftware.spine.AnimationState.TrackEntry; import com.esotericsoftware.spine.utils.TwoColorPolygonBatch; @@ -216,7 +215,7 @@ public class SkeletonViewer extends ApplicationAdapter { entry = state.setAnimation(track, ui.animationList.getSelected(), ui.loopCheckbox.isChecked()); entry.setHoldPrevious(track > 0 && ui.holdPrevCheckbox.isChecked()); } - entry.setMixBlend(track > 0 && ui.addCheckbox.isChecked() ? MixBlend.add : MixBlend.replace); + entry.setAdditive(track > 0 && ui.addCheckbox.isChecked()); entry.setReverse(ui.reverseCheckbox.isChecked()); entry.setAlpha(ui.alphaSlider.getValue()); } diff --git a/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewerUI.java b/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewerUI.java index 20ca36e36..6a098b179 100644 --- a/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewerUI.java +++ b/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewerUI.java @@ -67,7 +67,6 @@ import com.badlogic.gdx.utils.Align; import com.badlogic.gdx.utils.Null; import com.badlogic.gdx.utils.viewport.ScreenViewport; -import com.esotericsoftware.spine.Animation.MixBlend; import com.esotericsoftware.spine.AnimationState.TrackEntry; import java.awt.FileDialog; @@ -572,7 +571,7 @@ class SkeletonViewerUI { loopCheckbox.setChecked(current.getLoop()); reverseCheckbox.setChecked(current.getReverse()); if (track > 0) { - addCheckbox.setChecked(current.getMixBlend() == MixBlend.add); + addCheckbox.setChecked(current.getAdditive()); holdPrevCheckbox.setChecked(current.getHoldPrevious()); } }