From 95ce8a55471a56c0189165f9e88193caff6a48d3 Mon Sep 17 00:00:00 2001 From: Nathan Sweet Date: Fri, 6 Oct 2023 11:21:50 -0400 Subject: [PATCH] [libgdx] Binary, timelines, physics updates. * Refactored timelines for code reuse. * Added physics timelines. * Improved physics behavior. * Optimized binary format for ~7% size reduction. * Added physics to binary format. * Changed rotation normalization to ceil. * Added bone icon as nonessential data. --- .../com/esotericsoftware/spine/Animation.java | 568 ++++++++---------- .../spine/AnimationState.java | 2 +- .../com/esotericsoftware/spine/BoneData.java | 10 + .../spine/PhysicsConstraint.java | 235 +++++--- .../spine/PhysicsConstraintData.java | 116 ++-- .../spine/SkeletonBinary.java | 259 +++++--- .../esotericsoftware/spine/SkeletonJson.java | 6 + .../spine/SkeletonLoader.java | 3 - .../spine/TransformConstraint.java | 4 +- .../spine/utils/SpineUtils.java | 1 + 10 files changed, 661 insertions(+), 543 deletions(-) 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 cefdedf42..911d480c7 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java @@ -179,6 +179,8 @@ public class Animation { event, drawOrder, // ikConstraint, transformConstraint, // pathConstraintPosition, pathConstraintSpacing, pathConstraintMix, // + physicsConstraintInertia, physicsConstraintStrength, physicsConstraintDamping, physicsConstraintFriction, // + physicsConstraintMass, physicsConstraintWind, physicsConstraintGravity, physicsConstraintMix, // sequence } @@ -421,6 +423,82 @@ public class Animation { } return getBezierValue(time, i, VALUE, curveType - BEZIER); } + + public float getRelativeValue (float time, float alpha, MixBlend blend, float current, float setup) { + if (time < frames[0]) { + switch (blend) { + case setup: + return setup; + case first: + return current + (setup - current) * alpha; + } + return current; + } + float value = getCurveValue(time); + switch (blend) { + case setup: + return setup + value * alpha; + case first: + case replace: + value += setup - current; + } + return current + value * alpha; + } + + public float getAbsoluteValue (float time, float alpha, MixBlend blend, float current, float setup) { + if (time < frames[0]) { + switch (blend) { + case setup: + return setup; + case first: + return current + (setup - current) * alpha; + } + return current; + } + float value = getCurveValue(time); + if (blend == MixBlend.setup) return setup + (value - setup) * alpha; + return current + (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]) { + switch (blend) { + case setup: + return setup; + case first: + return current + (setup - current) * alpha; + } + return current; + } + float value = getCurveValue(time) * setup; + if (alpha == 1) { + if (blend == add) return current + value - setup; + return 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; + } } /** The base class for a {@link CurveTimeline} which sets two properties. */ @@ -467,31 +545,7 @@ public class Animation { MixDirection direction) { Bone bone = skeleton.bones.get(boneIndex); - if (!bone.active) return; - - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case setup: - bone.rotation = bone.data.rotation; - return; - case first: - bone.rotation += (bone.data.rotation - bone.rotation) * alpha; - } - return; - } - - float r = getCurveValue(time); - switch (blend) { - case setup: - bone.rotation = bone.data.rotation + r * alpha; - break; - case first: - case replace: - r += bone.data.rotation - bone.rotation; - // Fall through. - case add: - bone.rotation += r * alpha; - } + if (bone.active) bone.rotation = getRelativeValue(time, alpha, blend, bone.rotation, bone.data.rotation); } } @@ -517,7 +571,7 @@ public class Animation { if (!bone.active) return; float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. + if (time < frames[0]) { switch (blend) { case setup: bone.x = bone.data.x; @@ -584,32 +638,7 @@ public class Animation { MixDirection direction) { Bone bone = skeleton.bones.get(boneIndex); - if (!bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case setup: - bone.x = bone.data.x; - return; - case first: - bone.x += (bone.data.x - bone.x) * alpha; - } - return; - } - - float x = getCurveValue(time); - switch (blend) { - case setup: - bone.x = bone.data.x + x * alpha; - break; - case first: - case replace: - bone.x += (bone.data.x + x - bone.x) * alpha; - break; - case add: - bone.x += x * alpha; - } + if (bone.active) bone.x = getRelativeValue(time, alpha, blend, bone.x, bone.data.x); } } @@ -630,32 +659,7 @@ public class Animation { MixDirection direction) { Bone bone = skeleton.bones.get(boneIndex); - if (!bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case setup: - bone.y = bone.data.y; - return; - case first: - bone.y += (bone.data.y - bone.y) * alpha; - } - return; - } - - float y = getCurveValue(time); - switch (blend) { - case setup: - bone.y = bone.data.y + y * alpha; - break; - case first: - case replace: - bone.y += (bone.data.y + y - bone.y) * alpha; - break; - case add: - bone.y += y * alpha; - } + if (bone.active) bone.y = getRelativeValue(time, alpha, blend, bone.y, bone.data.y); } } @@ -681,7 +685,7 @@ public class Animation { if (!bone.active) return; float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. + if (time < frames[0]) { switch (blend) { case setup: bone.scaleX = bone.data.scaleX; @@ -787,59 +791,7 @@ public class Animation { MixDirection direction) { Bone bone = skeleton.bones.get(boneIndex); - if (!bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case setup: - bone.scaleX = bone.data.scaleX; - return; - case first: - bone.scaleX += (bone.data.scaleX - bone.scaleX) * alpha; - } - return; - } - - float x = getCurveValue(time) * bone.data.scaleX; - if (alpha == 1) { - if (blend == add) - bone.scaleX += x - bone.data.scaleX; - else - bone.scaleX = x; - } else { - // Mixing out uses sign of setup or current pose, else use sign of key. - float bx; - if (direction == out) { - switch (blend) { - case setup: - bx = bone.data.scaleX; - bone.scaleX = bx + (Math.abs(x) * Math.signum(bx) - bx) * alpha; - break; - case first: - case replace: - bx = bone.scaleX; - bone.scaleX = bx + (Math.abs(x) * Math.signum(bx) - bx) * alpha; - break; - case add: - bone.scaleX += (x - bone.data.scaleX) * alpha; - } - } else { - switch (blend) { - case setup: - bx = Math.abs(bone.data.scaleX) * Math.signum(x); - bone.scaleX = bx + (x - bx) * alpha; - break; - case first: - case replace: - bx = Math.abs(bone.scaleX) * Math.signum(x); - bone.scaleX = bx + (x - bx) * alpha; - break; - case add: - bone.scaleX += (x - bone.data.scaleX) * alpha; - } - } - } + if (bone.active) bone.scaleX = getScaleValue(time, alpha, blend, direction, bone.scaleX, bone.data.scaleX); } } @@ -860,59 +812,7 @@ public class Animation { MixDirection direction) { Bone bone = skeleton.bones.get(boneIndex); - if (!bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case setup: - bone.scaleY = bone.data.scaleY; - return; - case first: - bone.scaleY += (bone.data.scaleY - bone.scaleY) * alpha; - } - return; - } - - float y = getCurveValue(time) * bone.data.scaleY; - if (alpha == 1) { - if (blend == add) - bone.scaleY += y - bone.data.scaleY; - else - bone.scaleY = y; - } else { - // Mixing out uses sign of setup or current pose, else use sign of key. - float by; - if (direction == out) { - switch (blend) { - case setup: - by = bone.data.scaleY; - bone.scaleY = by + (Math.abs(y) * Math.signum(by) - by) * alpha; - break; - case first: - case replace: - by = bone.scaleY; - bone.scaleY = by + (Math.abs(y) * Math.signum(by) - by) * alpha; - break; - case add: - bone.scaleY += (y - bone.data.scaleY) * alpha; - } - } else { - switch (blend) { - case setup: - by = Math.abs(bone.data.scaleY) * Math.signum(y); - bone.scaleY = by + (y - by) * alpha; - break; - case first: - case replace: - by = Math.abs(bone.scaleY) * Math.signum(y); - bone.scaleY = by + (y - by) * alpha; - break; - case add: - bone.scaleY += (y - bone.data.scaleY) * alpha; - } - } - } + if (bone.active) bone.scaleY = getScaleValue(time, alpha, blend, direction, bone.scaleX, bone.data.scaleY); } } @@ -938,7 +838,7 @@ public class Animation { if (!bone.active) return; float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. + if (time < frames[0]) { switch (blend) { case setup: bone.shearX = bone.data.shearX; @@ -1005,32 +905,7 @@ public class Animation { MixDirection direction) { Bone bone = skeleton.bones.get(boneIndex); - if (!bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case setup: - bone.shearX = bone.data.shearX; - return; - case first: - bone.shearX += (bone.data.shearX - bone.shearX) * alpha; - } - return; - } - - float x = getCurveValue(time); - switch (blend) { - case setup: - bone.shearX = bone.data.shearX + x * alpha; - break; - case first: - case replace: - bone.shearX += (bone.data.shearX + x - bone.shearX) * alpha; - break; - case add: - bone.shearX += x * alpha; - } + if (bone.active) bone.shearX = getRelativeValue(time, alpha, blend, bone.shearX, bone.data.shearX); } } @@ -1051,32 +926,7 @@ public class Animation { MixDirection direction) { Bone bone = skeleton.bones.get(boneIndex); - if (!bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case setup: - bone.shearY = bone.data.shearY; - return; - case first: - bone.shearY += (bone.data.shearY - bone.shearY) * alpha; - } - return; - } - - float y = getCurveValue(time); - switch (blend) { - case setup: - bone.shearY = bone.data.shearY + y * alpha; - break; - case first: - case replace: - bone.shearY += (bone.data.shearY + y - bone.shearY) * alpha; - break; - case add: - bone.shearY += y * alpha; - } + if (bone.active) bone.shearY = getRelativeValue(time, alpha, blend, bone.shearX, bone.data.shearY); } } @@ -1122,7 +972,7 @@ public class Animation { float[] frames = this.frames; Color color = slot.color; - if (time < frames[0]) { // Time is before first frame. + if (time < frames[0]) { Color setup = slot.data.color; switch (blend) { case setup: @@ -1211,7 +1061,7 @@ public class Animation { float[] frames = this.frames; Color color = slot.color; - if (time < frames[0]) { // Time is before first frame. + if (time < frames[0]) { Color setup = slot.data.color; switch (blend) { case setup: @@ -1290,7 +1140,7 @@ public class Animation { float[] frames = this.frames; Color color = slot.color; - if (time < frames[0]) { // Time is before first frame. + if (time < frames[0]) { Color setup = slot.data.color; switch (blend) { case setup: @@ -1360,7 +1210,7 @@ public class Animation { float[] frames = this.frames; Color light = slot.color, dark = slot.darkColor; - if (time < frames[0]) { // Time is before first frame. + if (time < frames[0]) { Color setupLight = slot.data.color, setupDark = slot.data.darkColor; switch (blend) { case setup: @@ -1486,7 +1336,7 @@ public class Animation { float[] frames = this.frames; Color light = slot.color, dark = slot.darkColor; - if (time < frames[0]) { // Time is before first frame. + if (time < frames[0]) { Color setupLight = slot.data.color, setupDark = slot.data.darkColor; switch (blend) { case setup: @@ -1614,7 +1464,7 @@ public class Animation { return; } - if (time < this.frames[0]) { // Time is before first frame. + if (time < this.frames[0]) { if (blend == setup || blend == first) setAttachment(skeleton, slot, slot.data.attachmentName); return; } @@ -1737,7 +1587,7 @@ public class Animation { int vertexCount = vertices[0].length; float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. + if (time < frames[0]) { switch (blend) { case setup: deformArray.clear(); @@ -1945,7 +1795,7 @@ public class Animation { lastTime = -1f; } else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame. return; - if (time < frames[0]) return; // Time is before first frame. + if (time < frames[0]) return; int i; if (lastTime < frames[0]) @@ -2001,7 +1851,7 @@ public class Animation { return; } - if (time < frames[0]) { // Time is before first frame. + if (time < frames[0]) { if (blend == setup || blend == first) arraycopy(skeleton.slots.items, 0, skeleton.drawOrder.items, 0, skeleton.slots.size); return; @@ -2025,21 +1875,21 @@ public class Animation { static public final int ENTRIES = 6; static private final int MIX = 1, SOFTNESS = 2, BEND_DIRECTION = 3, COMPRESS = 4, STRETCH = 5; - final int ikConstraintIndex; + final int constraintIndex; public IkConstraintTimeline (int frameCount, int bezierCount, int ikConstraintIndex) { super(frameCount, bezierCount, Property.ikConstraint.ordinal() + "|" + ikConstraintIndex); - this.ikConstraintIndex = ikConstraintIndex; + constraintIndex = ikConstraintIndex; } public int getFrameEntries () { return ENTRIES; } - /** The index of the IK constraint slot in {@link Skeleton#getIkConstraints()} that will be changed when this timeline is + /** The index of the IK constraint in {@link Skeleton#getIkConstraints()} that will be changed when this timeline is * applied. */ public int getIkConstraintIndex () { - return ikConstraintIndex; + return constraintIndex; } /** Sets the time, mix, softness, bend direction, compress, and stretch for the specified frame. @@ -2060,11 +1910,11 @@ public class Animation { public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, MixDirection direction) { - IkConstraint constraint = skeleton.ikConstraints.get(ikConstraintIndex); + IkConstraint constraint = skeleton.ikConstraints.get(constraintIndex); if (!constraint.active) return; float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. + if (time < frames[0]) { switch (blend) { case setup: constraint.mix = constraint.data.mix; @@ -2134,21 +1984,21 @@ public class Animation { static public final int ENTRIES = 7; static private final int ROTATE = 1, X = 2, Y = 3, SCALEX = 4, SCALEY = 5, SHEARY = 6; - final int transformConstraintIndex; + final int constraintIndex; public TransformConstraintTimeline (int frameCount, int bezierCount, int transformConstraintIndex) { super(frameCount, bezierCount, Property.transformConstraint.ordinal() + "|" + transformConstraintIndex); - this.transformConstraintIndex = transformConstraintIndex; + constraintIndex = transformConstraintIndex; } public int getFrameEntries () { return ENTRIES; } - /** The index of the transform constraint slot in {@link Skeleton#getTransformConstraints()} that will be changed when this + /** The index of the transform constraint in {@link Skeleton#getTransformConstraints()} that will be changed when this * timeline is applied. */ public int getTransformConstraintIndex () { - return transformConstraintIndex; + return constraintIndex; } /** Sets the time, rotate mix, translate mix, scale mix, and shear mix for the specified frame. @@ -2169,11 +2019,11 @@ public class Animation { public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, MixDirection direction) { - TransformConstraint constraint = skeleton.transformConstraints.get(transformConstraintIndex); + TransformConstraint constraint = skeleton.transformConstraints.get(constraintIndex); if (!constraint.active) return; float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. + if (time < frames[0]) { TransformConstraintData data = constraint.data; switch (blend) { case setup: @@ -2252,105 +2102,73 @@ public class Animation { /** Changes a path constraint's {@link PathConstraint#getPosition()}. */ static public class PathConstraintPositionTimeline extends CurveTimeline1 { - final int pathConstraintIndex; + final int constraintIndex; public PathConstraintPositionTimeline (int frameCount, int bezierCount, int pathConstraintIndex) { super(frameCount, bezierCount, Property.pathConstraintPosition.ordinal() + "|" + pathConstraintIndex); - this.pathConstraintIndex = pathConstraintIndex; + constraintIndex = pathConstraintIndex; } - /** The index of the path constraint slot in {@link Skeleton#getPathConstraints()} that will be changed when this timeline - * is applied. */ + /** The index of the path constraint in {@link Skeleton#getPathConstraints()} that will be changed when this timeline is + * applied. */ public int getPathConstraintIndex () { - return pathConstraintIndex; + return constraintIndex; } public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, MixDirection direction) { - PathConstraint constraint = skeleton.pathConstraints.get(pathConstraintIndex); - if (!constraint.active) return; - - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case setup: - constraint.position = constraint.data.position; - return; - case first: - constraint.position += (constraint.data.position - constraint.position) * alpha; - } - return; - } - - float position = getCurveValue(time); - if (blend == setup) - constraint.position = constraint.data.position + (position - constraint.data.position) * alpha; - else - constraint.position += (position - constraint.position) * alpha; + PathConstraint constraint = skeleton.pathConstraints.get(constraintIndex); + if (constraint.active) + constraint.position = getAbsoluteValue(time, alpha, blend, constraint.position, constraint.data.position); } } /** Changes a path constraint's {@link PathConstraint#getSpacing()}. */ static public class PathConstraintSpacingTimeline extends CurveTimeline1 { - final int pathConstraintIndex; + final int constraintIndex; public PathConstraintSpacingTimeline (int frameCount, int bezierCount, int pathConstraintIndex) { super(frameCount, bezierCount, Property.pathConstraintSpacing.ordinal() + "|" + pathConstraintIndex); - this.pathConstraintIndex = pathConstraintIndex; + constraintIndex = pathConstraintIndex; } - /** The index of the path constraint slot in {@link Skeleton#getPathConstraints()} that will be changed when this timeline - * is applied. */ + /** The index of the path constraint in {@link Skeleton#getPathConstraints()} that will be changed when this timeline is + * applied. */ public int getPathConstraintIndex () { - return pathConstraintIndex; + return constraintIndex; } public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, MixDirection direction) { - PathConstraint constraint = skeleton.pathConstraints.get(pathConstraintIndex); - if (!constraint.active) return; - - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case setup: - constraint.spacing = constraint.data.spacing; - return; - case first: - constraint.spacing += (constraint.data.spacing - constraint.spacing) * alpha; - } - return; - } - - float spacing = getCurveValue(time); - if (blend == setup) - constraint.spacing = constraint.data.spacing + (spacing - constraint.data.spacing) * alpha; - else - constraint.spacing += (spacing - constraint.spacing) * alpha; + PathConstraint constraint = skeleton.pathConstraints.get(constraintIndex); + if (constraint.active) + constraint.spacing = getAbsoluteValue(time, alpha, blend, constraint.spacing, constraint.data.spacing); } } - /** Changes a transform constraint's {@link PathConstraint#getMixRotate()}, {@link PathConstraint#getMixX()}, and + /** Changes a path constraint's {@link PathConstraint#getMixRotate()}, {@link PathConstraint#getMixX()}, and * {@link PathConstraint#getMixY()}. */ static public class PathConstraintMixTimeline extends CurveTimeline { static public final int ENTRIES = 4; static private final int ROTATE = 1, X = 2, Y = 3; - final int pathConstraintIndex; + final int constraintIndex; public PathConstraintMixTimeline (int frameCount, int bezierCount, int pathConstraintIndex) { super(frameCount, bezierCount, Property.pathConstraintMix.ordinal() + "|" + pathConstraintIndex); - this.pathConstraintIndex = pathConstraintIndex; + constraintIndex = pathConstraintIndex; } public int getFrameEntries () { return ENTRIES; } - /** The index of the path constraint slot in {@link Skeleton#getPathConstraints()} that will be changed when this timeline - * is applied. */ + /** The index of the path constraint in {@link Skeleton#getPathConstraints()} that will be changed when this timeline is + * applied. */ public int getPathConstraintIndex () { - return pathConstraintIndex; + return constraintIndex; } /** Sets the time and color for the specified frame. @@ -2367,11 +2185,11 @@ public class Animation { public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, MixDirection direction) { - PathConstraint constraint = skeleton.pathConstraints.get(pathConstraintIndex); + PathConstraint constraint = skeleton.pathConstraints.get(constraintIndex); if (!constraint.active) return; float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. + if (time < frames[0]) { PathConstraintData data = constraint.data; switch (blend) { case setup: @@ -2424,6 +2242,124 @@ public class Animation { } } + /** The base class for {@link PhysicsConstraint} timelines. */ + static public abstract class PhysicsConstraintTimeline extends CurveTimeline1 { + final int constraintIndex; + + public PhysicsConstraintTimeline (int frameCount, int bezierCount, int physicsConstraintIndex, Property property) { + super(frameCount, bezierCount, property.ordinal() + "|" + physicsConstraintIndex); + constraintIndex = physicsConstraintIndex; + } + + /** The index of the physics constraint in {@link Skeleton#getPhysicsConstraints()} that will be changed when this timeline + * is applied. */ + public int getPhysicsConstraintIndex () { + return constraintIndex; + } + } + + /** Changes a physics constraint's {@link PhysicsConstraint#getInertia()}. */ + static public class PhysicsConstraintInertiaTimeline extends PhysicsConstraintTimeline { + public PhysicsConstraintInertiaTimeline (int frameCount, int bezierCount, int physicsConstraintIndex) { + super(frameCount, bezierCount, physicsConstraintIndex, Property.physicsConstraintInertia); + } + + public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, + MixDirection direction) { + + PhysicsConstraint constraint = skeleton.physicsConstraints.get(constraintIndex); + if (constraint.active) + constraint.inertia = getAbsoluteValue(time, alpha, blend, constraint.inertia, constraint.data.inertia); + } + } + + /** Changes a physics constraint's {@link PhysicsConstraint#getStrength()}. */ + static public class PhysicsConstraintStrengthTimeline extends PhysicsConstraintTimeline { + public PhysicsConstraintStrengthTimeline (int frameCount, int bezierCount, int physicsConstraintIndex) { + super(frameCount, bezierCount, physicsConstraintIndex, Property.physicsConstraintStrength); + } + + public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, + MixDirection direction) { + + PhysicsConstraint constraint = skeleton.physicsConstraints.get(constraintIndex); + if (constraint.active) + constraint.strength = getAbsoluteValue(time, alpha, blend, constraint.strength, constraint.data.strength); + } + } + + /** Changes a physics constraint's {@link PhysicsConstraint#getDamping()}. */ + static public class PhysicsConstraintDampingTimeline extends PhysicsConstraintTimeline { + public PhysicsConstraintDampingTimeline (int frameCount, int bezierCount, int physicsConstraintIndex) { + super(frameCount, bezierCount, physicsConstraintIndex, Property.physicsConstraintDamping); + } + + public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, + MixDirection direction) { + + PhysicsConstraint constraint = skeleton.physicsConstraints.get(constraintIndex); + if (constraint.active) + constraint.damping = getAbsoluteValue(time, alpha, blend, constraint.damping, constraint.data.damping); + } + } + + /** Changes a physics constraint's {@link PhysicsConstraint#getMass()}. */ + static public class PhysicsConstraintMassTimeline extends PhysicsConstraintTimeline { + public PhysicsConstraintMassTimeline (int frameCount, int bezierCount, int physicsConstraintIndex) { + super(frameCount, bezierCount, physicsConstraintIndex, Property.physicsConstraintMass); + } + + public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, + MixDirection direction) { + + PhysicsConstraint constraint = skeleton.physicsConstraints.get(constraintIndex); + if (constraint.active) constraint.mass = getAbsoluteValue(time, alpha, blend, constraint.mass, constraint.data.mass); + } + } + + /** Changes a physics constraint's {@link PhysicsConstraint#getWind()}. */ + static public class PhysicsConstraintWindTimeline extends PhysicsConstraintTimeline { + public PhysicsConstraintWindTimeline (int frameCount, int bezierCount, int physicsConstraintIndex) { + super(frameCount, bezierCount, physicsConstraintIndex, Property.physicsConstraintWind); + } + + public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, + MixDirection direction) { + + PhysicsConstraint constraint = skeleton.physicsConstraints.get(constraintIndex); + if (constraint.active) constraint.wind = getAbsoluteValue(time, alpha, blend, constraint.wind, constraint.data.wind); + } + } + + /** Changes a physics constraint's {@link PhysicsConstraint#getGravity()}. */ + static public class PhysicsConstraintGravityTimeline extends PhysicsConstraintTimeline { + public PhysicsConstraintGravityTimeline (int frameCount, int bezierCount, int physicsConstraintIndex) { + super(frameCount, bezierCount, physicsConstraintIndex, Property.physicsConstraintGravity); + } + + public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, + MixDirection direction) { + + PhysicsConstraint constraint = skeleton.physicsConstraints.get(constraintIndex); + if (constraint.active) + constraint.gravity = getAbsoluteValue(time, alpha, blend, constraint.gravity, constraint.data.gravity); + } + } + + /** Changes a physics constraint's {@link PhysicsConstraint#getMix()}. */ + static public class PhysicsConstraintMixTimeline extends PhysicsConstraintTimeline { + public PhysicsConstraintMixTimeline (int frameCount, int bezierCount, int physicsConstraintIndex) { + super(frameCount, bezierCount, physicsConstraintIndex, Property.physicsConstraintMix); + } + + public void apply (Skeleton skeleton, float lastTime, float time, @Null Array events, float alpha, MixBlend blend, + MixDirection direction) { + + PhysicsConstraint constraint = skeleton.physicsConstraints.get(constraintIndex); + if (constraint.active) constraint.mix = getAbsoluteValue(time, alpha, blend, constraint.mix, constraint.data.mix); + } + } + /** Changes a slot's {@link Slot#getSequenceIndex()} for an attachment's {@link Sequence}. */ static public class SequenceTimeline extends Timeline implements SlotTimeline { static public final int ENTRIES = 3; @@ -2475,7 +2411,7 @@ public class Animation { if (sequence == null) return; float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. + if (time < frames[0]) { if (blend == setup || blend == first) slot.setSequenceIndex(-1); return; } 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 33d947c1c..b21f4fc68 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java @@ -434,7 +434,7 @@ public class AnimationState { // Mix between rotations using the direction of the shortest route on the first frame. float total, diff = r2 - r1; - diff -= (16384 - (int)(16384.499999999996 - diff / 360)) * 360; + diff -= (float)Math.ceil(diff / 360 - 0.5f) * 360; if (diff == 0) total = timelinesRotation[i]; else { diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/BoneData.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/BoneData.java index 38f946f06..e4e95b980 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/BoneData.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/BoneData.java @@ -46,6 +46,7 @@ public class BoneData { // Nonessential. final Color color = new Color(0.61f, 0.61f, 0.61f, 1); // 9b9b9bff + @Null String icon; public BoneData (int index, String name, @Null BoneData parent) { if (index < 0) throw new IllegalArgumentException("index must be >= 0."); @@ -195,6 +196,15 @@ public class BoneData { return color; } + /** The bone icon as it was in Spine, or null if nonessential data was not exported. */ + public @Null String getIcon () { + return icon; + } + + public void setIcon (@Null String icon) { + this.icon = icon; + } + public String toString () { return name; } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PhysicsConstraint.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PhysicsConstraint.java index e2bf0c929..61938a9e2 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PhysicsConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PhysicsConstraint.java @@ -39,11 +39,10 @@ import com.esotericsoftware.spine.Skeleton.Physics; public class PhysicsConstraint implements Updatable { final PhysicsConstraintData data; public Bone bone; - - float mix; + float inertia, strength, damping, mass, wind, gravity, mix; boolean reset = true; - float beforeX, beforeY, afterX, afterY, tipX, tipY; + float ux, uy, cx, cy, tx, ty; float xOffset, xVelocity; float yOffset, yVelocity; float rotateOffset, rotateVelocity; @@ -59,8 +58,13 @@ public class PhysicsConstraint implements Updatable { if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); this.data = data; this.skeleton = skeleton; - bone = skeleton.bones.get(data.bone.index); + inertia = data.inertia; + strength = data.strength; + damping = data.damping; + mass = data.mass; + wind = data.wind; + gravity = data.gravity; mix = data.mix; } @@ -70,13 +74,18 @@ public class PhysicsConstraint implements Updatable { data = constraint.data; skeleton = constraint.skeleton; bone = constraint.bone; + inertia = constraint.inertia; + strength = constraint.strength; + damping = constraint.damping; + mass = constraint.mass; + wind = constraint.wind; + gravity = constraint.gravity; mix = constraint.mix; } public void reset () { remaining = 0; lastTime = skeleton.time; - reset = true; xOffset = 0; xVelocity = 0; @@ -90,8 +99,13 @@ public class PhysicsConstraint implements Updatable { public void setToSetupPose () { reset(); - PhysicsConstraintData data = this.data; + inertia = data.inertia; + strength = data.strength; + damping = data.damping; + mass = data.mass; + wind = data.wind; + gravity = data.gravity; mix = data.mix; } @@ -100,110 +114,126 @@ public class PhysicsConstraint implements Updatable { float mix = this.mix; if (mix == 0) return; - boolean x = data.x, y = data.y, rotateOrShear = data.rotate || data.shearX, scaleX = data.scaleX; + boolean x = data.x, y = data.y, rotateOrShearX = data.rotate || data.shearX, scaleX = data.scaleX; Bone bone = this.bone; + float l = bone.data.length; switch (physics) { case none: return; case reset: reset(); - break; + // Fall through. case update: remaining += Math.max(skeleton.time - lastTime, 0); lastTime = skeleton.time; - float length = bone.data.length, br = atan2(bone.c, bone.a); - float wind = data.wind, gravity = data.gravity, strength = data.strength, friction = data.friction, mass = data.mass; - float damping = data.damping, step = data.step; - boolean angle = rotateOrShear || scaleX; - float cos = 0, sin = 0; - while (remaining >= step) { - remaining -= step; - if (x) { - xVelocity += (wind * 500 - xOffset * strength - xVelocity * friction) * mass; - xOffset += xVelocity * step; - xVelocity *= damping; + float step = data.step; + if (remaining >= step) { + float bx = bone.worldX, by = bone.worldY, ca = 0, c = 0, s = 0; + if (reset) { + reset = false; + ux = bx; + uy = by; + } else { + float i = this.inertia; + if (x) { + xOffset += (ux - bx) * i; + ux = bx; + bone.worldX += xOffset * mix; + } + if (y) { + yOffset += (uy - by) * i; + uy = by; + bone.worldY += yOffset * mix; + } + if (rotateOrShearX) { + ca = atan2(bone.c, bone.a); + float dx = cx - bone.worldX, dy = cy - bone.worldY, r = atan2(dy + ty, dx + tx) - ca - rotateOffset * mix; + rotateOffset += (r - (float)Math.ceil(r * invPI2 - 0.5f) * PI2) * i; + r = rotateOffset * mix + ca; + c = cos(r); + s = sin(r); + if (scaleX) scaleOffset += (dx * c + dy * s) * i / l; + } else if (scaleX) { + float r = rotateOffset * mix + atan2(bone.c, bone.a); + c = cos(r); + s = sin(r); + scaleOffset += ((cx - bone.worldX) * c + (cy - bone.worldY) * s) * i / l; + } } - if (y) { - yVelocity += (-gravity * 500 - yOffset * strength - yVelocity * friction) * mass; - yOffset += yVelocity * step; - yVelocity *= damping; - } - if (angle) { - float r = br + rotateOffset * degRad; - cos = cos(r); - sin = sin(r); - if (rotateOrShear) { - rotateVelocity += (length * (-wind * sin - gravity * cos) - rotateOffset * strength - rotateVelocity * friction) - * mass; - rotateOffset += rotateVelocity * step; - rotateVelocity *= damping; + cx = bone.worldX; + cy = bone.worldY; + + float strength = this.strength, mass = this.mass * step, wind = this.wind, gravity = this.gravity; + float damping = (float)Math.pow(this.damping, 60 * step); + while (true) { + remaining -= step; + if (x) { + xVelocity += (100 * wind - xOffset * strength) * mass; + xOffset += xVelocity * step; + xVelocity *= damping; + } + if (y) { + yVelocity += (-100 * gravity - yOffset * strength) * mass; + yOffset += yVelocity * step; + yVelocity *= damping; } if (scaleX) { - scaleVelocity += (wind * cos - gravity * sin - scaleOffset * strength - scaleVelocity * friction) * mass; + scaleVelocity += (wind * c - gravity * s - scaleOffset * strength) * mass; scaleOffset += scaleVelocity * step; scaleVelocity *= damping; } + if (rotateOrShearX) { + rotateVelocity += (-0.01f * l * (wind * s + gravity * c) - rotateOffset * strength) * mass; + rotateOffset += rotateVelocity * step; + rotateVelocity *= damping; + if (remaining < step) break; + float r = rotateOffset * mix + ca; + c = cos(r); + s = sin(r); + } else if (remaining < step) // + break; } } - - float bx = bone.worldX, by = bone.worldY; - if (reset) - reset = false; - else { - float i = data.inertia; - if (x) { - xOffset += (beforeX - bx) * i; - bone.worldX += xOffset * mix; - } - if (y) { - yOffset += (beforeY - by) * i; - bone.worldY += yOffset * mix; - } - if (rotateOrShear) { - if (length == 0) - rotateOffset = 0; - else { - float r = (atan2(afterY - bone.worldY + tipY, afterX - bone.worldX + tipX) - br) * radDeg; - rotateOffset += (r - (16384 - (int)(16384.499999999996 - r / 360)) * 360) * i; - } - } - if (scaleX) { - if (length == 0) - scaleOffset = 0; - else { - float r = br + rotateOffset * degRad; - scaleOffset += ((afterX - bone.worldX) * cos(r) + (afterY - bone.worldY) * sin(r)) * i / length; - } - } - } - beforeX = bx; - beforeY = by; - afterX = bone.worldX; - afterY = bone.worldY; - tipX = length * bone.a; - tipY = length * bone.c; break; case pose: if (x) bone.worldX += xOffset * mix; if (y) bone.worldY += yOffset * mix; } - if (rotateOrShear) { - float r = rotateOffset * mix; - if (data.rotate) bone.rotateWorld(r); - if (data.shearX) { - float t = (float)Math.tan(r * 0.5f * degRad); - bone.b += bone.a * t; - bone.d += bone.c * t; + if (rotateOrShearX) { + float r = rotateOffset * mix, ra = bone.a, sin, cos; + if (data.rotate) { + if (data.shearX) { + r *= 0.5f; + sin = sin(r); + cos = cos(r); + bone.a = ra = cos * ra - sin * bone.c; + bone.c = sin * ra + cos * bone.c; + } else { + sin = sin(r); + cos = cos(r); + } + float rb = bone.b; + bone.b = cos * rb - sin * bone.d; + bone.d = sin * rb + cos * bone.d; + } else { + sin = sin(r); + cos = cos(r); } + bone.a = cos * ra - sin * bone.c; + bone.c = sin * ra + cos * bone.c; } if (scaleX) { float s = 1 + scaleOffset * mix; bone.a *= s; bone.c *= s; } + if (physics == Physics.update) { + tx = l * bone.a; + ty = l * bone.c; + } bone.updateAppliedTransform(); } @@ -216,6 +246,55 @@ public class PhysicsConstraint implements Updatable { this.bone = bone; } + public float getInertia () { + return inertia; + } + + public void setInertia (float inertia) { + this.inertia = inertia; + } + + public float getStrength () { + return strength; + } + + public void setStrength (float strength) { + this.strength = strength; + } + + public float getDamping () { + return damping; + } + + public void setDamping (float damping) { + this.damping = damping; + } + + /** The inverse of the mass. */ + public float getMass () { + return mass; + } + + public void setMass (float mass) { + this.mass = mass; + } + + public float getWind () { + return wind; + } + + public void setWind (float wind) { + this.wind = wind; + } + + public float getGravity () { + return gravity; + } + + public void setGravity (float gravity) { + this.gravity = gravity; + } + /** A percentage (0-1) that controls the mix between the constrained and unconstrained poses. */ public float getMix () { return mix; diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PhysicsConstraintData.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PhysicsConstraintData.java index 710e4f01b..410c8d689 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PhysicsConstraintData.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PhysicsConstraintData.java @@ -34,9 +34,8 @@ package com.esotericsoftware.spine; * See Physics constraints in the Spine User Guide. */ public class PhysicsConstraintData extends ConstraintData { BoneData bone; - float step = 1 / 60f, mass = 1; - float strength, friction, damping, inertia, wind, gravity, mix; boolean x, y, rotate, scaleX, shearX; + float step, inertia, strength, damping, friction, mass, wind, gravity, mix; public PhysicsConstraintData (String name) { super(name); @@ -59,62 +58,6 @@ public class PhysicsConstraintData extends ConstraintData { this.step = step; } - public float getMass () { - return mass; - } - - public void setMass (float mass) { - this.mass = mass; - } - - public float getStrength () { - return strength; - } - - public void setStrength (float strength) { - this.strength = strength; - } - - public float getFriction () { - return friction; - } - - public void setFriction (float friction) { - this.friction = friction; - } - - public float getDamping () { - return damping; - } - - public void setDamping (float damping) { - this.damping = damping; - } - - public float getInertia () { - return inertia; - } - - public void setInertia (float inertia) { - this.inertia = inertia; - } - - public float getWind () { - return wind; - } - - public void setWind (float wind) { - this.wind = wind; - } - - public float getGravity () { - return gravity; - } - - public void setGravity (float gravity) { - this.gravity = gravity; - } - public boolean getX () { return x; } @@ -155,6 +98,63 @@ public class PhysicsConstraintData extends ConstraintData { this.shearX = shearX; } + public float getInertia () { + return inertia; + } + + public void setInertia (float inertia) { + this.inertia = inertia; + } + + public float getStrength () { + return strength; + } + + public void setStrength (float strength) { + this.strength = strength; + } + + public float getDamping () { + return damping; + } + + public void setDamping (float damping) { + this.damping = damping; + } + + public float getFriction () { + return friction; + } + + public void setFriction (float friction) { + this.friction = friction; + } + + /** The inverse of the mass. */ + public float getMass () { + return mass; + } + + public void setMass (float mass) { + this.mass = mass; + } + + public float getWind () { + return wind; + } + + public void setWind (float wind) { + this.wind = wind; + } + + public float getGravity () { + return gravity; + } + + public void setGravity (float gravity) { + this.gravity = gravity; + } + /** A percentage (0-1) that controls the mix between the constrained and unconstrained poses. */ public float getMix () { return mix; diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java index 7b9908bb1..c56fe2b01 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java @@ -55,6 +55,13 @@ import com.esotericsoftware.spine.Animation.IkConstraintTimeline; import com.esotericsoftware.spine.Animation.PathConstraintMixTimeline; import com.esotericsoftware.spine.Animation.PathConstraintPositionTimeline; import com.esotericsoftware.spine.Animation.PathConstraintSpacingTimeline; +import com.esotericsoftware.spine.Animation.PhysicsConstraintDampingTimeline; +import com.esotericsoftware.spine.Animation.PhysicsConstraintGravityTimeline; +import com.esotericsoftware.spine.Animation.PhysicsConstraintInertiaTimeline; +import com.esotericsoftware.spine.Animation.PhysicsConstraintMassTimeline; +import com.esotericsoftware.spine.Animation.PhysicsConstraintMixTimeline; +import com.esotericsoftware.spine.Animation.PhysicsConstraintStrengthTimeline; +import com.esotericsoftware.spine.Animation.PhysicsConstraintWindTimeline; import com.esotericsoftware.spine.Animation.RGB2Timeline; import com.esotericsoftware.spine.Animation.RGBA2Timeline; import com.esotericsoftware.spine.Animation.RGBATimeline; @@ -76,7 +83,6 @@ import com.esotericsoftware.spine.BoneData.TransformMode; import com.esotericsoftware.spine.PathConstraintData.PositionMode; import com.esotericsoftware.spine.PathConstraintData.RotateMode; import com.esotericsoftware.spine.PathConstraintData.SpacingMode; -import com.esotericsoftware.spine.SkeletonJson.LinkedMesh; import com.esotericsoftware.spine.attachments.Attachment; import com.esotericsoftware.spine.attachments.AttachmentLoader; import com.esotericsoftware.spine.attachments.AttachmentType; @@ -121,10 +127,20 @@ public class SkeletonBinary extends SkeletonLoader { static public final int PATH_SPACING = 1; static public final int PATH_MIX = 2; + static public final int PHYSICS_INERTIA = 0; + static public final int PHYSICS_STRENGTH = 1; + static public final int PHYSICS_DAMPING = 2; + static public final int PHYSICS_MASS = 4; + static public final int PHYSICS_WIND = 5; + static public final int PHYSICS_GRAVITY = 6; + static public final int PHYSICS_MIX = 7; + static public final int CURVE_LINEAR = 0; static public final int CURVE_STEPPED = 1; static public final int CURVE_BEZIER = 2; + private final Array linkedMeshes = new Array(); + public SkeletonBinary (AttachmentLoader attachmentLoader) { super(attachmentLoader); } @@ -192,7 +208,10 @@ public class SkeletonBinary extends SkeletonLoader { data.length = input.readFloat() * scale; data.transformMode = TransformMode.values[input.readInt(true)]; data.skinRequired = input.readBoolean(); - if (nonessential) Color.rgba8888ToColor(data.color, input.readInt()); + if (nonessential) { + Color.rgba8888ToColor(data.color, input.readInt()); + data.icon = input.readString(); + } bones[i] = data; } @@ -217,17 +236,18 @@ public class SkeletonBinary extends SkeletonLoader { for (int i = 0, nn; i < n; i++) { IkConstraintData data = new IkConstraintData(input.readString()); data.order = input.readInt(true); - data.skinRequired = input.readBoolean(); Object[] constraintBones = data.bones.setSize(nn = input.readInt(true)); for (int ii = 0; ii < nn; ii++) constraintBones[ii] = bones[input.readInt(true)]; data.target = (BoneData)bones[input.readInt(true)]; data.mix = input.readFloat(); data.softness = input.readFloat() * scale; - data.bendDirection = input.readByte(); - data.compress = input.readBoolean(); - data.stretch = input.readBoolean(); - data.uniform = input.readBoolean(); + int flags = input.read(); + data.skinRequired = (flags & 1) != 0; + data.bendDirection = (flags & 2) != 0 ? 1 : -1; + data.compress = (flags & 4) != 0; + data.stretch = (flags & 8) != 0; + data.uniform = (flags & 16) != 0; o[i] = data; } @@ -236,13 +256,14 @@ public class SkeletonBinary extends SkeletonLoader { for (int i = 0, nn; i < n; i++) { TransformConstraintData data = new TransformConstraintData(input.readString()); data.order = input.readInt(true); - data.skinRequired = input.readBoolean(); Object[] constraintBones = data.bones.setSize(nn = input.readInt(true)); for (int ii = 0; ii < nn; ii++) constraintBones[ii] = bones[input.readInt(true)]; data.target = (BoneData)bones[input.readInt(true)]; - data.local = input.readBoolean(); - data.relative = input.readBoolean(); + int flags = input.read(); + data.skinRequired = (flags & 1) != 0; + data.local = (flags & 2) != 0; + data.relative = (flags & 4) != 0; data.offsetRotation = input.readFloat(); data.offsetX = input.readFloat() * scale; data.offsetY = input.readFloat() * scale; @@ -282,6 +303,31 @@ public class SkeletonBinary extends SkeletonLoader { o[i] = data; } + // Physics constraints. + o = skeletonData.physicsConstraints.setSize(n = input.readInt(true)); + for (int i = 0; i < n; i++) { + PhysicsConstraintData data = new PhysicsConstraintData(input.readString()); + data.order = input.readInt(true); + data.bone = (BoneData)bones[input.readInt(true)]; + int flags = input.read(); + data.skinRequired = (flags & 1) != 0; + data.x = (flags & 2) != 0; + data.y = (flags & 4) != 0; + data.rotate = (flags & 8) != 0; + data.scaleX = (flags & 16) != 0; + data.shearX = (flags & 32) != 0; + data.step = input.readFloat(); + data.inertia = input.readFloat(); + data.strength = input.readFloat(); + data.damping = input.readFloat(); + data.friction = input.readFloat(); + data.mass = input.readFloat(); + data.wind = input.readFloat(); + data.gravity = input.readFloat(); + data.mix = input.readFloat(); + o[i] = data; + } + // Default skin. Skin defaultSkin = readSkin(input, skeletonData, true, nonessential); if (defaultSkin != null) { @@ -302,8 +348,7 @@ public class SkeletonBinary extends SkeletonLoader { Object[] items = linkedMeshes.items; for (int i = 0; i < n; i++) { LinkedMesh linkedMesh = (LinkedMesh)items[i]; - Skin skin = linkedMesh.skin == null ? skeletonData.getDefaultSkin() : skeletonData.findSkin(linkedMesh.skin); - if (skin == null) throw new SerializationException("Skin not found: " + linkedMesh.skin); + Skin skin = skeletonData.skins.get(linkedMesh.skinIndex); Attachment parent = skin.getAttachment(linkedMesh.slotIndex, linkedMesh.parent); if (parent == null) throw new SerializationException("Parent mesh not found: " + linkedMesh.parent); linkedMesh.mesh.setTimelineAttachment(linkedMesh.inheritTimelines ? (VertexAttachment)parent : linkedMesh.mesh); @@ -315,7 +360,7 @@ public class SkeletonBinary extends SkeletonLoader { // Events. o = skeletonData.events.setSize(n = input.readInt(true)); for (int i = 0; i < n; i++) { - EventData data = new EventData(input.readStringRef()); + EventData data = new EventData(input.readString()); data.intValue = input.readInt(false); data.floatValue = input.readFloat(); data.stringValue = input.readString(); @@ -353,7 +398,7 @@ public class SkeletonBinary extends SkeletonLoader { if (slotCount == 0) return null; skin = new Skin("default"); } else { - skin = new Skin(input.readStringRef()); + skin = new Skin(input.readString()); Object[] bones = skin.bones.setSize(input.readInt(true)), items = skeletonData.bones.items; for (int i = 0, n = skin.bones.size; i < n; i++) bones[i] = items[input.readInt(true)]; @@ -365,6 +410,9 @@ public class SkeletonBinary extends SkeletonLoader { for (int i = 0, n = input.readInt(true); i < n; i++) skin.constraints.add((ConstraintData)items[input.readInt(true)]); items = skeletonData.pathConstraints.items; + for (int i = 0, n = input.readInt(true); i < n; i++) + skin.constraints.add((ConstraintData)items[input.readInt(true)]); + items = skeletonData.pathConstraints.items; for (int i = 0, n = input.readInt(true); i < n; i++) skin.constraints.add((ConstraintData)items[input.readInt(true)]); skin.constraints.shrink(); @@ -387,12 +435,13 @@ public class SkeletonBinary extends SkeletonLoader { String attachmentName, boolean nonessential) throws IOException { float scale = this.scale; - String name = input.readStringRef(); - if (name == null) name = attachmentName; - - switch (AttachmentType.values[input.readByte()]) { + int flags = input.readByte(); + String name = (flags & 8) != 0 ? input.readStringRef() : attachmentName; + switch (AttachmentType.values[flags & 0b111]) { case region: { - String path = input.readStringRef(); + String path = (flags & 16) != 0 ? input.readStringRef() : null; + int color = (flags & 32) != 0 ? input.readInt() : 0xffffffff; + Sequence sequence = (flags & 64) != 0 ? readSequence(input) : null; float rotation = input.readFloat(); float x = input.readFloat(); float y = input.readFloat(); @@ -400,8 +449,6 @@ public class SkeletonBinary extends SkeletonLoader { float scaleY = input.readFloat(); float width = input.readFloat(); float height = input.readFloat(); - int color = input.readInt(); - Sequence sequence = readSequence(input); if (path == null) path = name; RegionAttachment region = attachmentLoader.newRegionAttachment(skin, name, path, sequence); @@ -420,43 +467,41 @@ public class SkeletonBinary extends SkeletonLoader { return region; } case boundingbox: { - int vertexCount = input.readInt(true); - Vertices vertices = readVertices(input, vertexCount); + Vertices vertices = readVertices(input, (flags & 16) != 0); int color = nonessential ? input.readInt() : 0; BoundingBoxAttachment box = attachmentLoader.newBoundingBoxAttachment(skin, name); if (box == null) return null; - box.setWorldVerticesLength(vertexCount << 1); + box.setWorldVerticesLength(vertices.length); box.setVertices(vertices.vertices); box.setBones(vertices.bones); if (nonessential) Color.rgba8888ToColor(box.getColor(), color); return box; } case mesh: { - String path = input.readStringRef(); - int color = input.readInt(); - int vertexCount = input.readInt(true); - float[] uvs = readFloatArray(input, vertexCount << 1, 1); - short[] triangles = readShortArray(input); - Vertices vertices = readVertices(input, vertexCount); + String path = (flags & 16) != 0 ? input.readStringRef() : name; + int color = (flags & 32) != 0 ? input.readInt() : 0xffffffff; + Sequence sequence = (flags & 64) != 0 ? readSequence(input) : null; int hullLength = input.readInt(true); - Sequence sequence = readSequence(input); + Vertices vertices = readVertices(input, (flags & 128) != 0); + float[] uvs = readFloatArray(input, vertices.length, 1); + short[] triangles = readShortArray(input, (vertices.length - hullLength - 2) * 3); + short[] edges = null; float width = 0, height = 0; if (nonessential) { - edges = readShortArray(input); + edges = readShortArray(input, input.readInt(true)); width = input.readFloat(); height = input.readFloat(); } - if (path == null) path = name; MeshAttachment mesh = attachmentLoader.newMeshAttachment(skin, name, path, sequence); if (mesh == null) return null; mesh.setPath(path); Color.rgba8888ToColor(mesh.getColor(), color); mesh.setBones(vertices.bones); mesh.setVertices(vertices.vertices); - mesh.setWorldVerticesLength(vertexCount << 1); + mesh.setWorldVerticesLength(vertices.length); mesh.setTriangles(triangles); mesh.setRegionUVs(uvs); if (sequence == null) mesh.updateRegion(); @@ -470,19 +515,18 @@ public class SkeletonBinary extends SkeletonLoader { return mesh; } case linkedmesh: { - String path = input.readStringRef(); - int color = input.readInt(); - String skinName = input.readStringRef(); + String path = (flags & 16) != 0 ? input.readStringRef() : name; + int color = (flags & 32) != 0 ? input.readInt() : 0xffffffff; + Sequence sequence = (flags & 64) != 0 ? readSequence(input) : null; + boolean inheritTimelines = (flags & 128) != 0; + int skinIndex = input.readInt(true); String parent = input.readStringRef(); - boolean inheritTimelines = input.readBoolean(); - Sequence sequence = readSequence(input); float width = 0, height = 0; if (nonessential) { width = input.readFloat(); height = input.readFloat(); } - if (path == null) path = name; MeshAttachment mesh = attachmentLoader.newMeshAttachment(skin, name, path, sequence); if (mesh == null) return null; mesh.setPath(path); @@ -492,15 +536,14 @@ public class SkeletonBinary extends SkeletonLoader { mesh.setWidth(width * scale); mesh.setHeight(height * scale); } - linkedMeshes.add(new LinkedMesh(mesh, skinName, slotIndex, parent, inheritTimelines)); + linkedMeshes.add(new LinkedMesh(mesh, skinIndex, slotIndex, parent, inheritTimelines)); return mesh; } case path: { - boolean closed = input.readBoolean(); - boolean constantSpeed = input.readBoolean(); - int vertexCount = input.readInt(true); - Vertices vertices = readVertices(input, vertexCount); - float[] lengths = new float[vertexCount / 3]; + boolean closed = (flags & 16) != 0; + boolean constantSpeed = (flags & 32) != 0; + Vertices vertices = readVertices(input, (flags & 64) != 0); + float[] lengths = new float[vertices.length / 6]; for (int i = 0, n = lengths.length; i < n; i++) lengths[i] = input.readFloat() * scale; int color = nonessential ? input.readInt() : 0; @@ -509,7 +552,7 @@ public class SkeletonBinary extends SkeletonLoader { if (path == null) return null; path.setClosed(closed); path.setConstantSpeed(constantSpeed); - path.setWorldVerticesLength(vertexCount << 1); + path.setWorldVerticesLength(vertices.length); path.setVertices(vertices.vertices); path.setBones(vertices.bones); path.setLengths(lengths); @@ -532,14 +575,13 @@ public class SkeletonBinary extends SkeletonLoader { } case clipping: int endSlotIndex = input.readInt(true); - int vertexCount = input.readInt(true); - Vertices vertices = readVertices(input, vertexCount); + Vertices vertices = readVertices(input, (flags & 16) != 0); int color = nonessential ? input.readInt() : 0; ClippingAttachment clip = attachmentLoader.newClippingAttachment(skin, name); if (clip == null) return null; clip.setEndSlot(skeletonData.slots.get(endSlotIndex)); - clip.setWorldVerticesLength(vertexCount << 1); + clip.setWorldVerticesLength(vertices.length); clip.setVertices(vertices.vertices); clip.setBones(vertices.bones); if (nonessential) Color.rgba8888ToColor(clip.getColor(), color); @@ -549,7 +591,6 @@ public class SkeletonBinary extends SkeletonLoader { } private Sequence readSequence (SkeletonInput input) throws IOException { - if (!input.readBoolean()) return null; Sequence sequence = new Sequence(input.readInt(true)); sequence.setStart(input.readInt(true)); sequence.setDigits(input.readInt(true)); @@ -557,16 +598,17 @@ public class SkeletonBinary extends SkeletonLoader { return sequence; } - private Vertices readVertices (SkeletonInput input, int vertexCount) throws IOException { + private Vertices readVertices (SkeletonInput input, boolean weighted) throws IOException { float scale = this.scale; - int verticesLength = vertexCount << 1; + int vertexCount = input.readInt(true); Vertices vertices = new Vertices(); - if (!input.readBoolean()) { - vertices.vertices = readFloatArray(input, verticesLength, scale); + vertices.length = vertexCount << 1; + if (!weighted) { + vertices.vertices = readFloatArray(input, vertices.length, scale); return vertices; } - FloatArray weights = new FloatArray(verticesLength * 3 * 3); - IntArray bonesArray = new IntArray(verticesLength * 3); + FloatArray weights = new FloatArray(vertices.length * 3 * 3); + IntArray bonesArray = new IntArray(vertices.length * 3); for (int i = 0; i < vertexCount; i++) { int boneCount = input.readInt(true); bonesArray.add(boneCount); @@ -594,11 +636,10 @@ public class SkeletonBinary extends SkeletonLoader { return array; } - private short[] readShortArray (SkeletonInput input) throws IOException { - int n = input.readInt(true); + private short[] readShortArray (SkeletonInput input, int n) throws IOException { short[] array = new short[n]; for (int i = 0; i < n; i++) - array[i] = input.readShort(); + array[i] = (short)input.readInt(true); return array; } @@ -777,34 +818,34 @@ public class SkeletonBinary extends SkeletonLoader { int type = input.readByte(), frameCount = input.readInt(true), bezierCount = input.readInt(true); switch (type) { case BONE_ROTATE: - timelines.add(readTimeline(input, new RotateTimeline(frameCount, bezierCount, boneIndex), 1)); + readTimeline(input, timelines, new RotateTimeline(frameCount, bezierCount, boneIndex), 1); break; case BONE_TRANSLATE: - timelines.add(readTimeline(input, new TranslateTimeline(frameCount, bezierCount, boneIndex), scale)); + readTimeline(input, timelines, new TranslateTimeline(frameCount, bezierCount, boneIndex), scale); break; case BONE_TRANSLATEX: - timelines.add(readTimeline(input, new TranslateXTimeline(frameCount, bezierCount, boneIndex), scale)); + readTimeline(input, timelines, new TranslateXTimeline(frameCount, bezierCount, boneIndex), scale); break; case BONE_TRANSLATEY: - timelines.add(readTimeline(input, new TranslateYTimeline(frameCount, bezierCount, boneIndex), scale)); + readTimeline(input, timelines, new TranslateYTimeline(frameCount, bezierCount, boneIndex), scale); break; case BONE_SCALE: - timelines.add(readTimeline(input, new ScaleTimeline(frameCount, bezierCount, boneIndex), 1)); + readTimeline(input, timelines, new ScaleTimeline(frameCount, bezierCount, boneIndex), 1); break; case BONE_SCALEX: - timelines.add(readTimeline(input, new ScaleXTimeline(frameCount, bezierCount, boneIndex), 1)); + readTimeline(input, timelines, new ScaleXTimeline(frameCount, bezierCount, boneIndex), 1); break; case BONE_SCALEY: - timelines.add(readTimeline(input, new ScaleYTimeline(frameCount, bezierCount, boneIndex), 1)); + readTimeline(input, timelines, new ScaleYTimeline(frameCount, bezierCount, boneIndex), 1); break; case BONE_SHEAR: - timelines.add(readTimeline(input, new ShearTimeline(frameCount, bezierCount, boneIndex), 1)); + readTimeline(input, timelines, new ShearTimeline(frameCount, bezierCount, boneIndex), 1); break; case BONE_SHEARX: - timelines.add(readTimeline(input, new ShearXTimeline(frameCount, bezierCount, boneIndex), 1)); + readTimeline(input, timelines, new ShearXTimeline(frameCount, bezierCount, boneIndex), 1); break; case BONE_SHEARY: - timelines.add(readTimeline(input, new ShearYTimeline(frameCount, bezierCount, boneIndex), 1)); + readTimeline(input, timelines, new ShearYTimeline(frameCount, bezierCount, boneIndex), 1); } } } @@ -815,7 +856,8 @@ public class SkeletonBinary extends SkeletonLoader { IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount, input.readInt(true), index); float time = input.readFloat(), mix = input.readFloat(), softness = input.readFloat() * scale; for (int frame = 0, bezier = 0;; frame++) { - timeline.setFrame(frame, time, mix, softness, input.readByte(), input.readBoolean(), input.readBoolean()); + int flags = input.read(); + timeline.setFrame(frame, time, mix, softness, input.readByte(), (flags & 1) != 0, (flags & 2) != 0); if (frame == frameLast) break; float time2 = input.readFloat(), mix2 = input.readFloat(), softness2 = input.readFloat() * scale; switch (input.readByte()) { @@ -872,20 +914,18 @@ public class SkeletonBinary extends SkeletonLoader { int index = input.readInt(true); PathConstraintData data = skeletonData.pathConstraints.get(index); for (int ii = 0, nn = input.readInt(true); ii < nn; ii++) { - switch (input.readByte()) { + int type = input.readByte(), frameCount = input.readInt(true), bezierCount = input.readInt(true); + switch (type) { case PATH_POSITION: - timelines - .add(readTimeline(input, new PathConstraintPositionTimeline(input.readInt(true), input.readInt(true), index), - data.positionMode == PositionMode.fixed ? scale : 1)); + readTimeline(input, timelines, new PathConstraintPositionTimeline(frameCount, bezierCount, index), + data.positionMode == PositionMode.fixed ? scale : 1); break; case PATH_SPACING: - timelines - .add(readTimeline(input, new PathConstraintSpacingTimeline(input.readInt(true), input.readInt(true), index), - data.spacingMode == SpacingMode.length || data.spacingMode == SpacingMode.fixed ? scale : 1)); + readTimeline(input, timelines, new PathConstraintSpacingTimeline(frameCount, bezierCount, index), + data.spacingMode == SpacingMode.length || data.spacingMode == SpacingMode.fixed ? scale : 1); break; case PATH_MIX: - PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(input.readInt(true), input.readInt(true), - index); + PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(frameCount, bezierCount, index); float time = input.readFloat(), mixRotate = input.readFloat(), mixX = input.readFloat(), mixY = input.readFloat(); for (int frame = 0, bezier = 0, frameLast = timeline.getFrameCount() - 1;; frame++) { timeline.setFrame(frame, time, mixRotate, mixX, mixY); @@ -911,6 +951,36 @@ public class SkeletonBinary extends SkeletonLoader { } } + // Physics timelines. + for (int i = 0, n = input.readInt(true); i < n; i++) { + int index = input.readInt(true); + for (int ii = 0, nn = input.readInt(true); ii < nn; ii++) { + int type = input.readByte(), frameCount = input.readInt(true), bezierCount = input.readInt(true); + switch (type) { + case PHYSICS_INERTIA: + readTimeline(input, timelines, new PhysicsConstraintInertiaTimeline(frameCount, bezierCount, index), 1); + break; + case PHYSICS_STRENGTH: + readTimeline(input, timelines, new PhysicsConstraintStrengthTimeline(frameCount, bezierCount, index), 1); + break; + case PHYSICS_DAMPING: + readTimeline(input, timelines, new PhysicsConstraintDampingTimeline(frameCount, bezierCount, index), 1); + break; + case PHYSICS_MASS: + readTimeline(input, timelines, new PhysicsConstraintMassTimeline(frameCount, bezierCount, index), 1); + break; + case PHYSICS_WIND: + readTimeline(input, timelines, new PhysicsConstraintWindTimeline(frameCount, bezierCount, index), 1); + break; + case PHYSICS_GRAVITY: + readTimeline(input, timelines, new PhysicsConstraintGravityTimeline(frameCount, bezierCount, index), 1); + break; + case PHYSICS_MIX: + readTimeline(input, timelines, new PhysicsConstraintMixTimeline(frameCount, bezierCount, index), 1); + } + } + } + // Attachment timelines. for (int i = 0, n = input.readInt(true); i < n; i++) { Skin skin = skeletonData.skins.get(input.readInt(true)); @@ -1024,7 +1094,8 @@ public class SkeletonBinary extends SkeletonLoader { Event event = new Event(time, eventData); event.intValue = input.readInt(false); event.floatValue = input.readFloat(); - event.stringValue = input.readBoolean() ? input.readString() : eventData.stringValue; + event.stringValue = input.readString(); + if (event.stringValue == null) event.stringValue = eventData.stringValue; if (event.getData().audioPath != null) { event.volume = input.readFloat(); event.balance = input.readFloat(); @@ -1041,7 +1112,8 @@ public class SkeletonBinary extends SkeletonLoader { return new Animation(name, timelines, duration); } - private Timeline readTimeline (SkeletonInput input, CurveTimeline1 timeline, float scale) throws IOException { + private void readTimeline (SkeletonInput input, Array timelines, CurveTimeline1 timeline, float scale) + throws IOException { float time = input.readFloat(), value = input.readFloat() * scale; for (int frame = 0, bezier = 0, frameLast = timeline.getFrameCount() - 1;; frame++) { timeline.setFrame(frame, time, value); @@ -1057,10 +1129,11 @@ public class SkeletonBinary extends SkeletonLoader { time = time2; value = value2; } - return timeline; + timelines.add(timeline); } - private Timeline readTimeline (SkeletonInput input, CurveTimeline2 timeline, float scale) throws IOException { + private void readTimeline (SkeletonInput input, Array timelines, CurveTimeline2 timeline, float scale) + throws IOException { float time = input.readFloat(), value1 = input.readFloat() * scale, value2 = input.readFloat() * scale; for (int frame = 0, bezier = 0, frameLast = timeline.getFrameCount() - 1;; frame++) { timeline.setFrame(frame, time, value1, value2); @@ -1078,7 +1151,7 @@ public class SkeletonBinary extends SkeletonLoader { value1 = nvalue1; value2 = nvalue2; } - return timeline; + timelines.add(timeline); } void setBezier (SkeletonInput input, CurveTimeline timeline, int bezier, int frame, int value, float time1, float time2, @@ -1088,6 +1161,7 @@ public class SkeletonBinary extends SkeletonLoader { } static class Vertices { + int length; int[] bones; float[] vertices; } @@ -1143,4 +1217,19 @@ public class SkeletonBinary extends SkeletonLoader { return new String(chars, 0, charCount); } } + + static class LinkedMesh { + String parent; + int skinIndex, slotIndex; + MeshAttachment mesh; + boolean inheritTimelines; + + public LinkedMesh (MeshAttachment mesh, int skinIndex, int slotIndex, String parent, boolean inheritTimelines) { + this.mesh = mesh; + this.skinIndex = skinIndex; + this.slotIndex = slotIndex; + this.parent = parent; + this.inheritTimelines = inheritTimelines; + } + } } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java index 99db77e24..00e4535dc 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java @@ -98,6 +98,8 @@ import com.esotericsoftware.spine.attachments.VertexAttachment; * JSON and binary data in the Spine * Runtimes Guide. */ public class SkeletonJson extends SkeletonLoader { + private final Array linkedMeshes = new Array(); + public SkeletonJson (AttachmentLoader attachmentLoader) { super(attachmentLoader); } @@ -161,6 +163,8 @@ public class SkeletonJson extends SkeletonLoader { String color = boneMap.getString("color", null); if (color != null) Color.valueOf(color, data.getColor()); + data.icon = boneMap.getString("icon", null); + skeletonData.bones.add(data); } @@ -877,6 +881,8 @@ public class SkeletonJson extends SkeletonLoader { } } + // BOZO - Physics timelines. + // Attachment timelines. for (JsonValue attachmentsMap = map.getChild("attachments"); attachmentsMap != null; attachmentsMap = attachmentsMap.next) { Skin skin = skeletonData.findSkin(attachmentsMap.name); diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonLoader.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonLoader.java index 46a61deba..c204aa834 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonLoader.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonLoader.java @@ -33,9 +33,7 @@ import java.io.InputStream; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.graphics.g2d.TextureAtlas; -import com.badlogic.gdx.utils.Array; -import com.esotericsoftware.spine.SkeletonJson.LinkedMesh; import com.esotericsoftware.spine.attachments.AtlasAttachmentLoader; import com.esotericsoftware.spine.attachments.AttachmentLoader; @@ -46,7 +44,6 @@ import com.esotericsoftware.spine.attachments.AttachmentLoader; abstract public class SkeletonLoader { final AttachmentLoader attachmentLoader; float scale = 1; - final Array linkedMeshes = new Array(); /** Creates a skeleton loader that loads attachments using an {@link AtlasAttachmentLoader} with the specified atlas. */ public SkeletonLoader (TextureAtlas atlas) { diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraint.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraint.java index b7354a2d4..6d498500b 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraint.java @@ -251,7 +251,7 @@ public class TransformConstraint implements Updatable { float rotation = bone.arotation; if (mixRotate != 0) { float r = target.arotation - rotation + data.offsetRotation; - r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; + r -= (float)Math.ceil(r / 360 - 0.5f) * 360; rotation += r * mixRotate; } @@ -268,7 +268,7 @@ public class TransformConstraint implements Updatable { float shearY = bone.ashearY; if (mixShearY != 0) { float r = target.ashearY - shearY + data.offsetShearY; - r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; + r -= (float)Math.ceil(r / 360 - 0.5f) * 360; shearY += r * mixShearY; } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/utils/SpineUtils.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/utils/SpineUtils.java index 626ace4ed..6ecaf08e8 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/utils/SpineUtils.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/utils/SpineUtils.java @@ -32,6 +32,7 @@ package com.esotericsoftware.spine.utils; public class SpineUtils { static public final float PI = 3.1415927f; static public final float PI2 = PI * 2; + static public final float invPI2 = 1 / PI2; static public final float radiansToDegrees = 180f / PI; static public final float radDeg = radiansToDegrees; static public final float degreesToRadians = PI / 180;