[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.
This commit is contained in:
Nathan Sweet 2023-10-06 11:21:50 -04:00
parent 55bb8fc65a
commit 95ce8a5547
10 changed files with 661 additions and 543 deletions

View File

@ -179,6 +179,8 @@ public class Animation {
event, drawOrder, // event, drawOrder, //
ikConstraint, transformConstraint, // ikConstraint, transformConstraint, //
pathConstraintPosition, pathConstraintSpacing, pathConstraintMix, // pathConstraintPosition, pathConstraintSpacing, pathConstraintMix, //
physicsConstraintInertia, physicsConstraintStrength, physicsConstraintDamping, physicsConstraintFriction, //
physicsConstraintMass, physicsConstraintWind, physicsConstraintGravity, physicsConstraintMix, //
sequence sequence
} }
@ -421,6 +423,82 @@ public class Animation {
} }
return getBezierValue(time, i, VALUE, curveType - BEZIER); 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. */ /** The base class for a {@link CurveTimeline} which sets two properties. */
@ -467,31 +545,7 @@ public class Animation {
MixDirection direction) { MixDirection direction) {
Bone bone = skeleton.bones.get(boneIndex); Bone bone = skeleton.bones.get(boneIndex);
if (!bone.active) return; if (bone.active) bone.rotation = getRelativeValue(time, alpha, blend, bone.rotation, bone.data.rotation);
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;
}
} }
} }
@ -517,7 +571,7 @@ public class Animation {
if (!bone.active) return; if (!bone.active) return;
float[] frames = this.frames; float[] frames = this.frames;
if (time < frames[0]) { // Time is before first frame. if (time < frames[0]) {
switch (blend) { switch (blend) {
case setup: case setup:
bone.x = bone.data.x; bone.x = bone.data.x;
@ -584,32 +638,7 @@ public class Animation {
MixDirection direction) { MixDirection direction) {
Bone bone = skeleton.bones.get(boneIndex); Bone bone = skeleton.bones.get(boneIndex);
if (!bone.active) return; if (bone.active) bone.x = getRelativeValue(time, alpha, blend, bone.x, bone.data.x);
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;
}
} }
} }
@ -630,32 +659,7 @@ public class Animation {
MixDirection direction) { MixDirection direction) {
Bone bone = skeleton.bones.get(boneIndex); Bone bone = skeleton.bones.get(boneIndex);
if (!bone.active) return; if (bone.active) bone.y = getRelativeValue(time, alpha, blend, bone.y, bone.data.y);
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;
}
} }
} }
@ -681,7 +685,7 @@ public class Animation {
if (!bone.active) return; if (!bone.active) return;
float[] frames = this.frames; float[] frames = this.frames;
if (time < frames[0]) { // Time is before first frame. if (time < frames[0]) {
switch (blend) { switch (blend) {
case setup: case setup:
bone.scaleX = bone.data.scaleX; bone.scaleX = bone.data.scaleX;
@ -787,59 +791,7 @@ public class Animation {
MixDirection direction) { MixDirection direction) {
Bone bone = skeleton.bones.get(boneIndex); Bone bone = skeleton.bones.get(boneIndex);
if (!bone.active) return; if (bone.active) bone.scaleX = getScaleValue(time, alpha, blend, direction, bone.scaleX, bone.data.scaleX);
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;
}
}
}
} }
} }
@ -860,59 +812,7 @@ public class Animation {
MixDirection direction) { MixDirection direction) {
Bone bone = skeleton.bones.get(boneIndex); Bone bone = skeleton.bones.get(boneIndex);
if (!bone.active) return; if (bone.active) bone.scaleY = getScaleValue(time, alpha, blend, direction, bone.scaleX, bone.data.scaleY);
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;
}
}
}
} }
} }
@ -938,7 +838,7 @@ public class Animation {
if (!bone.active) return; if (!bone.active) return;
float[] frames = this.frames; float[] frames = this.frames;
if (time < frames[0]) { // Time is before first frame. if (time < frames[0]) {
switch (blend) { switch (blend) {
case setup: case setup:
bone.shearX = bone.data.shearX; bone.shearX = bone.data.shearX;
@ -1005,32 +905,7 @@ public class Animation {
MixDirection direction) { MixDirection direction) {
Bone bone = skeleton.bones.get(boneIndex); Bone bone = skeleton.bones.get(boneIndex);
if (!bone.active) return; if (bone.active) bone.shearX = getRelativeValue(time, alpha, blend, bone.shearX, bone.data.shearX);
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;
}
} }
} }
@ -1051,32 +926,7 @@ public class Animation {
MixDirection direction) { MixDirection direction) {
Bone bone = skeleton.bones.get(boneIndex); Bone bone = skeleton.bones.get(boneIndex);
if (!bone.active) return; if (bone.active) bone.shearY = getRelativeValue(time, alpha, blend, bone.shearX, bone.data.shearY);
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;
}
} }
} }
@ -1122,7 +972,7 @@ public class Animation {
float[] frames = this.frames; float[] frames = this.frames;
Color color = slot.color; Color color = slot.color;
if (time < frames[0]) { // Time is before first frame. if (time < frames[0]) {
Color setup = slot.data.color; Color setup = slot.data.color;
switch (blend) { switch (blend) {
case setup: case setup:
@ -1211,7 +1061,7 @@ public class Animation {
float[] frames = this.frames; float[] frames = this.frames;
Color color = slot.color; Color color = slot.color;
if (time < frames[0]) { // Time is before first frame. if (time < frames[0]) {
Color setup = slot.data.color; Color setup = slot.data.color;
switch (blend) { switch (blend) {
case setup: case setup:
@ -1290,7 +1140,7 @@ public class Animation {
float[] frames = this.frames; float[] frames = this.frames;
Color color = slot.color; Color color = slot.color;
if (time < frames[0]) { // Time is before first frame. if (time < frames[0]) {
Color setup = slot.data.color; Color setup = slot.data.color;
switch (blend) { switch (blend) {
case setup: case setup:
@ -1360,7 +1210,7 @@ public class Animation {
float[] frames = this.frames; float[] frames = this.frames;
Color light = slot.color, dark = slot.darkColor; 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; Color setupLight = slot.data.color, setupDark = slot.data.darkColor;
switch (blend) { switch (blend) {
case setup: case setup:
@ -1486,7 +1336,7 @@ public class Animation {
float[] frames = this.frames; float[] frames = this.frames;
Color light = slot.color, dark = slot.darkColor; 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; Color setupLight = slot.data.color, setupDark = slot.data.darkColor;
switch (blend) { switch (blend) {
case setup: case setup:
@ -1614,7 +1464,7 @@ public class Animation {
return; 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); if (blend == setup || blend == first) setAttachment(skeleton, slot, slot.data.attachmentName);
return; return;
} }
@ -1737,7 +1587,7 @@ public class Animation {
int vertexCount = vertices[0].length; int vertexCount = vertices[0].length;
float[] frames = this.frames; float[] frames = this.frames;
if (time < frames[0]) { // Time is before first frame. if (time < frames[0]) {
switch (blend) { switch (blend) {
case setup: case setup:
deformArray.clear(); deformArray.clear();
@ -1945,7 +1795,7 @@ public class Animation {
lastTime = -1f; lastTime = -1f;
} else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame. } else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame.
return; return;
if (time < frames[0]) return; // Time is before first frame. if (time < frames[0]) return;
int i; int i;
if (lastTime < frames[0]) if (lastTime < frames[0])
@ -2001,7 +1851,7 @@ public class Animation {
return; return;
} }
if (time < frames[0]) { // Time is before first frame. if (time < frames[0]) {
if (blend == setup || blend == first) if (blend == setup || blend == first)
arraycopy(skeleton.slots.items, 0, skeleton.drawOrder.items, 0, skeleton.slots.size); arraycopy(skeleton.slots.items, 0, skeleton.drawOrder.items, 0, skeleton.slots.size);
return; return;
@ -2025,21 +1875,21 @@ public class Animation {
static public final int ENTRIES = 6; static public final int ENTRIES = 6;
static private final int MIX = 1, SOFTNESS = 2, BEND_DIRECTION = 3, COMPRESS = 4, STRETCH = 5; 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) { public IkConstraintTimeline (int frameCount, int bezierCount, int ikConstraintIndex) {
super(frameCount, bezierCount, Property.ikConstraint.ordinal() + "|" + ikConstraintIndex); super(frameCount, bezierCount, Property.ikConstraint.ordinal() + "|" + ikConstraintIndex);
this.ikConstraintIndex = ikConstraintIndex; constraintIndex = ikConstraintIndex;
} }
public int getFrameEntries () { public int getFrameEntries () {
return ENTRIES; 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. */ * applied. */
public int getIkConstraintIndex () { public int getIkConstraintIndex () {
return ikConstraintIndex; return constraintIndex;
} }
/** Sets the time, mix, softness, bend direction, compress, and stretch for the specified frame. /** 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<Event> events, float alpha, MixBlend blend, public void apply (Skeleton skeleton, float lastTime, float time, @Null Array<Event> events, float alpha, MixBlend blend,
MixDirection direction) { MixDirection direction) {
IkConstraint constraint = skeleton.ikConstraints.get(ikConstraintIndex); IkConstraint constraint = skeleton.ikConstraints.get(constraintIndex);
if (!constraint.active) return; if (!constraint.active) return;
float[] frames = this.frames; float[] frames = this.frames;
if (time < frames[0]) { // Time is before first frame. if (time < frames[0]) {
switch (blend) { switch (blend) {
case setup: case setup:
constraint.mix = constraint.data.mix; constraint.mix = constraint.data.mix;
@ -2134,21 +1984,21 @@ public class Animation {
static public final int ENTRIES = 7; static public final int ENTRIES = 7;
static private final int ROTATE = 1, X = 2, Y = 3, SCALEX = 4, SCALEY = 5, SHEARY = 6; 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) { public TransformConstraintTimeline (int frameCount, int bezierCount, int transformConstraintIndex) {
super(frameCount, bezierCount, Property.transformConstraint.ordinal() + "|" + transformConstraintIndex); super(frameCount, bezierCount, Property.transformConstraint.ordinal() + "|" + transformConstraintIndex);
this.transformConstraintIndex = transformConstraintIndex; constraintIndex = transformConstraintIndex;
} }
public int getFrameEntries () { public int getFrameEntries () {
return ENTRIES; 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. */ * timeline is applied. */
public int getTransformConstraintIndex () { public int getTransformConstraintIndex () {
return transformConstraintIndex; return constraintIndex;
} }
/** Sets the time, rotate mix, translate mix, scale mix, and shear mix for the specified frame. /** 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<Event> events, float alpha, MixBlend blend, public void apply (Skeleton skeleton, float lastTime, float time, @Null Array<Event> events, float alpha, MixBlend blend,
MixDirection direction) { MixDirection direction) {
TransformConstraint constraint = skeleton.transformConstraints.get(transformConstraintIndex); TransformConstraint constraint = skeleton.transformConstraints.get(constraintIndex);
if (!constraint.active) return; if (!constraint.active) return;
float[] frames = this.frames; float[] frames = this.frames;
if (time < frames[0]) { // Time is before first frame. if (time < frames[0]) {
TransformConstraintData data = constraint.data; TransformConstraintData data = constraint.data;
switch (blend) { switch (blend) {
case setup: case setup:
@ -2252,105 +2102,73 @@ public class Animation {
/** Changes a path constraint's {@link PathConstraint#getPosition()}. */ /** Changes a path constraint's {@link PathConstraint#getPosition()}. */
static public class PathConstraintPositionTimeline extends CurveTimeline1 { static public class PathConstraintPositionTimeline extends CurveTimeline1 {
final int pathConstraintIndex; final int constraintIndex;
public PathConstraintPositionTimeline (int frameCount, int bezierCount, int pathConstraintIndex) { public PathConstraintPositionTimeline (int frameCount, int bezierCount, int pathConstraintIndex) {
super(frameCount, bezierCount, Property.pathConstraintPosition.ordinal() + "|" + 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 /** The index of the path constraint in {@link Skeleton#getPathConstraints()} that will be changed when this timeline is
* is applied. */ * applied. */
public int getPathConstraintIndex () { public int getPathConstraintIndex () {
return pathConstraintIndex; return constraintIndex;
} }
public void apply (Skeleton skeleton, float lastTime, float time, @Null Array<Event> events, float alpha, MixBlend blend, public void apply (Skeleton skeleton, float lastTime, float time, @Null Array<Event> events, float alpha, MixBlend blend,
MixDirection direction) { MixDirection direction) {
PathConstraint constraint = skeleton.pathConstraints.get(pathConstraintIndex); PathConstraint constraint = skeleton.pathConstraints.get(constraintIndex);
if (!constraint.active) return; if (constraint.active)
constraint.position = getAbsoluteValue(time, alpha, blend, constraint.position, constraint.data.position);
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;
} }
} }
/** Changes a path constraint's {@link PathConstraint#getSpacing()}. */ /** Changes a path constraint's {@link PathConstraint#getSpacing()}. */
static public class PathConstraintSpacingTimeline extends CurveTimeline1 { static public class PathConstraintSpacingTimeline extends CurveTimeline1 {
final int pathConstraintIndex; final int constraintIndex;
public PathConstraintSpacingTimeline (int frameCount, int bezierCount, int pathConstraintIndex) { public PathConstraintSpacingTimeline (int frameCount, int bezierCount, int pathConstraintIndex) {
super(frameCount, bezierCount, Property.pathConstraintSpacing.ordinal() + "|" + 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 /** The index of the path constraint in {@link Skeleton#getPathConstraints()} that will be changed when this timeline is
* is applied. */ * applied. */
public int getPathConstraintIndex () { public int getPathConstraintIndex () {
return pathConstraintIndex; return constraintIndex;
} }
public void apply (Skeleton skeleton, float lastTime, float time, @Null Array<Event> events, float alpha, MixBlend blend, public void apply (Skeleton skeleton, float lastTime, float time, @Null Array<Event> events, float alpha, MixBlend blend,
MixDirection direction) { MixDirection direction) {
PathConstraint constraint = skeleton.pathConstraints.get(pathConstraintIndex); PathConstraint constraint = skeleton.pathConstraints.get(constraintIndex);
if (!constraint.active) return; if (constraint.active)
constraint.spacing = getAbsoluteValue(time, alpha, blend, constraint.spacing, constraint.data.spacing);
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;
} }
} }
/** 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()}. */ * {@link PathConstraint#getMixY()}. */
static public class PathConstraintMixTimeline extends CurveTimeline { static public class PathConstraintMixTimeline extends CurveTimeline {
static public final int ENTRIES = 4; static public final int ENTRIES = 4;
static private final int ROTATE = 1, X = 2, Y = 3; static private final int ROTATE = 1, X = 2, Y = 3;
final int pathConstraintIndex; final int constraintIndex;
public PathConstraintMixTimeline (int frameCount, int bezierCount, int pathConstraintIndex) { public PathConstraintMixTimeline (int frameCount, int bezierCount, int pathConstraintIndex) {
super(frameCount, bezierCount, Property.pathConstraintMix.ordinal() + "|" + pathConstraintIndex); super(frameCount, bezierCount, Property.pathConstraintMix.ordinal() + "|" + pathConstraintIndex);
this.pathConstraintIndex = pathConstraintIndex; constraintIndex = pathConstraintIndex;
} }
public int getFrameEntries () { public int getFrameEntries () {
return ENTRIES; return ENTRIES;
} }
/** The index of the path constraint slot in {@link Skeleton#getPathConstraints()} that will be changed when this timeline /** The index of the path constraint in {@link Skeleton#getPathConstraints()} that will be changed when this timeline is
* is applied. */ * applied. */
public int getPathConstraintIndex () { public int getPathConstraintIndex () {
return pathConstraintIndex; return constraintIndex;
} }
/** Sets the time and color for the specified frame. /** 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<Event> events, float alpha, MixBlend blend, public void apply (Skeleton skeleton, float lastTime, float time, @Null Array<Event> events, float alpha, MixBlend blend,
MixDirection direction) { MixDirection direction) {
PathConstraint constraint = skeleton.pathConstraints.get(pathConstraintIndex); PathConstraint constraint = skeleton.pathConstraints.get(constraintIndex);
if (!constraint.active) return; if (!constraint.active) return;
float[] frames = this.frames; float[] frames = this.frames;
if (time < frames[0]) { // Time is before first frame. if (time < frames[0]) {
PathConstraintData data = constraint.data; PathConstraintData data = constraint.data;
switch (blend) { switch (blend) {
case setup: 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<Event> 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<Event> 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<Event> 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<Event> 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<Event> 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<Event> 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<Event> 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}. */ /** Changes a slot's {@link Slot#getSequenceIndex()} for an attachment's {@link Sequence}. */
static public class SequenceTimeline extends Timeline implements SlotTimeline { static public class SequenceTimeline extends Timeline implements SlotTimeline {
static public final int ENTRIES = 3; static public final int ENTRIES = 3;
@ -2475,7 +2411,7 @@ public class Animation {
if (sequence == null) return; if (sequence == null) return;
float[] frames = this.frames; 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); if (blend == setup || blend == first) slot.setSequenceIndex(-1);
return; return;
} }

View File

@ -434,7 +434,7 @@ public class AnimationState {
// Mix between rotations using the direction of the shortest route on the first frame. // Mix between rotations using the direction of the shortest route on the first frame.
float total, diff = r2 - r1; 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) if (diff == 0)
total = timelinesRotation[i]; total = timelinesRotation[i];
else { else {

View File

@ -46,6 +46,7 @@ public class BoneData {
// Nonessential. // Nonessential.
final Color color = new Color(0.61f, 0.61f, 0.61f, 1); // 9b9b9bff 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) { public BoneData (int index, String name, @Null BoneData parent) {
if (index < 0) throw new IllegalArgumentException("index must be >= 0."); if (index < 0) throw new IllegalArgumentException("index must be >= 0.");
@ -195,6 +196,15 @@ public class BoneData {
return color; 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 () { public String toString () {
return name; return name;
} }

View File

@ -39,11 +39,10 @@ import com.esotericsoftware.spine.Skeleton.Physics;
public class PhysicsConstraint implements Updatable { public class PhysicsConstraint implements Updatable {
final PhysicsConstraintData data; final PhysicsConstraintData data;
public Bone bone; public Bone bone;
float inertia, strength, damping, mass, wind, gravity, mix;
float mix;
boolean reset = true; boolean reset = true;
float beforeX, beforeY, afterX, afterY, tipX, tipY; float ux, uy, cx, cy, tx, ty;
float xOffset, xVelocity; float xOffset, xVelocity;
float yOffset, yVelocity; float yOffset, yVelocity;
float rotateOffset, rotateVelocity; float rotateOffset, rotateVelocity;
@ -59,8 +58,13 @@ public class PhysicsConstraint implements Updatable {
if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null.");
this.data = data; this.data = data;
this.skeleton = skeleton; this.skeleton = skeleton;
bone = skeleton.bones.get(data.bone.index); 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; mix = data.mix;
} }
@ -70,13 +74,18 @@ public class PhysicsConstraint implements Updatable {
data = constraint.data; data = constraint.data;
skeleton = constraint.skeleton; skeleton = constraint.skeleton;
bone = constraint.bone; bone = constraint.bone;
inertia = constraint.inertia;
strength = constraint.strength;
damping = constraint.damping;
mass = constraint.mass;
wind = constraint.wind;
gravity = constraint.gravity;
mix = constraint.mix; mix = constraint.mix;
} }
public void reset () { public void reset () {
remaining = 0; remaining = 0;
lastTime = skeleton.time; lastTime = skeleton.time;
reset = true; reset = true;
xOffset = 0; xOffset = 0;
xVelocity = 0; xVelocity = 0;
@ -90,8 +99,13 @@ public class PhysicsConstraint implements Updatable {
public void setToSetupPose () { public void setToSetupPose () {
reset(); reset();
PhysicsConstraintData data = this.data; 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; mix = data.mix;
} }
@ -100,110 +114,126 @@ public class PhysicsConstraint implements Updatable {
float mix = this.mix; float mix = this.mix;
if (mix == 0) return; 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; Bone bone = this.bone;
float l = bone.data.length;
switch (physics) { switch (physics) {
case none: case none:
return; return;
case reset: case reset:
reset(); reset();
break; // Fall through.
case update: case update:
remaining += Math.max(skeleton.time - lastTime, 0); remaining += Math.max(skeleton.time - lastTime, 0);
lastTime = skeleton.time; lastTime = skeleton.time;
float length = bone.data.length, br = atan2(bone.c, bone.a); float step = data.step;
float wind = data.wind, gravity = data.gravity, strength = data.strength, friction = data.friction, mass = data.mass; if (remaining >= step) {
float damping = data.damping, step = data.step; float bx = bone.worldX, by = bone.worldY, ca = 0, c = 0, s = 0;
boolean angle = rotateOrShear || scaleX; if (reset) {
float cos = 0, sin = 0; reset = false;
while (remaining >= step) { 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;
}
}
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; remaining -= step;
if (x) { if (x) {
xVelocity += (wind * 500 - xOffset * strength - xVelocity * friction) * mass; xVelocity += (100 * wind - xOffset * strength) * mass;
xOffset += xVelocity * step; xOffset += xVelocity * step;
xVelocity *= damping; xVelocity *= damping;
} }
if (y) { if (y) {
yVelocity += (-gravity * 500 - yOffset * strength - yVelocity * friction) * mass; yVelocity += (-100 * gravity - yOffset * strength) * mass;
yOffset += yVelocity * step; yOffset += yVelocity * step;
yVelocity *= damping; 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;
}
if (scaleX) { if (scaleX) {
scaleVelocity += (wind * cos - gravity * sin - scaleOffset * strength - scaleVelocity * friction) * mass; scaleVelocity += (wind * c - gravity * s - scaleOffset * strength) * mass;
scaleOffset += scaleVelocity * step; scaleOffset += scaleVelocity * step;
scaleVelocity *= damping; 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; break;
case pose: case pose:
if (x) bone.worldX += xOffset * mix; if (x) bone.worldX += xOffset * mix;
if (y) bone.worldY += yOffset * mix; if (y) bone.worldY += yOffset * mix;
} }
if (rotateOrShear) { if (rotateOrShearX) {
float r = rotateOffset * mix; float r = rotateOffset * mix, ra = bone.a, sin, cos;
if (data.rotate) bone.rotateWorld(r); if (data.rotate) {
if (data.shearX) { if (data.shearX) {
float t = (float)Math.tan(r * 0.5f * degRad); r *= 0.5f;
bone.b += bone.a * t; sin = sin(r);
bone.d += bone.c * t; 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) { if (scaleX) {
float s = 1 + scaleOffset * mix; float s = 1 + scaleOffset * mix;
bone.a *= s; bone.a *= s;
bone.c *= s; bone.c *= s;
} }
if (physics == Physics.update) {
tx = l * bone.a;
ty = l * bone.c;
}
bone.updateAppliedTransform(); bone.updateAppliedTransform();
} }
@ -216,6 +246,55 @@ public class PhysicsConstraint implements Updatable {
this.bone = bone; 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. */ /** A percentage (0-1) that controls the mix between the constrained and unconstrained poses. */
public float getMix () { public float getMix () {
return mix; return mix;

View File

@ -34,9 +34,8 @@ package com.esotericsoftware.spine;
* See <a href="http://esotericsoftware.com/spine-physics-constraints">Physics constraints</a> in the Spine User Guide. */ * See <a href="http://esotericsoftware.com/spine-physics-constraints">Physics constraints</a> in the Spine User Guide. */
public class PhysicsConstraintData extends ConstraintData { public class PhysicsConstraintData extends ConstraintData {
BoneData bone; BoneData bone;
float step = 1 / 60f, mass = 1;
float strength, friction, damping, inertia, wind, gravity, mix;
boolean x, y, rotate, scaleX, shearX; boolean x, y, rotate, scaleX, shearX;
float step, inertia, strength, damping, friction, mass, wind, gravity, mix;
public PhysicsConstraintData (String name) { public PhysicsConstraintData (String name) {
super(name); super(name);
@ -59,62 +58,6 @@ public class PhysicsConstraintData extends ConstraintData {
this.step = step; 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 () { public boolean getX () {
return x; return x;
} }
@ -155,6 +98,63 @@ public class PhysicsConstraintData extends ConstraintData {
this.shearX = shearX; 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. */ /** A percentage (0-1) that controls the mix between the constrained and unconstrained poses. */
public float getMix () { public float getMix () {
return mix; return mix;

View File

@ -55,6 +55,13 @@ import com.esotericsoftware.spine.Animation.IkConstraintTimeline;
import com.esotericsoftware.spine.Animation.PathConstraintMixTimeline; import com.esotericsoftware.spine.Animation.PathConstraintMixTimeline;
import com.esotericsoftware.spine.Animation.PathConstraintPositionTimeline; import com.esotericsoftware.spine.Animation.PathConstraintPositionTimeline;
import com.esotericsoftware.spine.Animation.PathConstraintSpacingTimeline; 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.RGB2Timeline;
import com.esotericsoftware.spine.Animation.RGBA2Timeline; import com.esotericsoftware.spine.Animation.RGBA2Timeline;
import com.esotericsoftware.spine.Animation.RGBATimeline; 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.PositionMode;
import com.esotericsoftware.spine.PathConstraintData.RotateMode; import com.esotericsoftware.spine.PathConstraintData.RotateMode;
import com.esotericsoftware.spine.PathConstraintData.SpacingMode; import com.esotericsoftware.spine.PathConstraintData.SpacingMode;
import com.esotericsoftware.spine.SkeletonJson.LinkedMesh;
import com.esotericsoftware.spine.attachments.Attachment; import com.esotericsoftware.spine.attachments.Attachment;
import com.esotericsoftware.spine.attachments.AttachmentLoader; import com.esotericsoftware.spine.attachments.AttachmentLoader;
import com.esotericsoftware.spine.attachments.AttachmentType; 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_SPACING = 1;
static public final int PATH_MIX = 2; 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_LINEAR = 0;
static public final int CURVE_STEPPED = 1; static public final int CURVE_STEPPED = 1;
static public final int CURVE_BEZIER = 2; static public final int CURVE_BEZIER = 2;
private final Array<LinkedMesh> linkedMeshes = new Array();
public SkeletonBinary (AttachmentLoader attachmentLoader) { public SkeletonBinary (AttachmentLoader attachmentLoader) {
super(attachmentLoader); super(attachmentLoader);
} }
@ -192,7 +208,10 @@ public class SkeletonBinary extends SkeletonLoader {
data.length = input.readFloat() * scale; data.length = input.readFloat() * scale;
data.transformMode = TransformMode.values[input.readInt(true)]; data.transformMode = TransformMode.values[input.readInt(true)];
data.skinRequired = input.readBoolean(); 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; bones[i] = data;
} }
@ -217,17 +236,18 @@ public class SkeletonBinary extends SkeletonLoader {
for (int i = 0, nn; i < n; i++) { for (int i = 0, nn; i < n; i++) {
IkConstraintData data = new IkConstraintData(input.readString()); IkConstraintData data = new IkConstraintData(input.readString());
data.order = input.readInt(true); data.order = input.readInt(true);
data.skinRequired = input.readBoolean();
Object[] constraintBones = data.bones.setSize(nn = input.readInt(true)); Object[] constraintBones = data.bones.setSize(nn = input.readInt(true));
for (int ii = 0; ii < nn; ii++) for (int ii = 0; ii < nn; ii++)
constraintBones[ii] = bones[input.readInt(true)]; constraintBones[ii] = bones[input.readInt(true)];
data.target = (BoneData)bones[input.readInt(true)]; data.target = (BoneData)bones[input.readInt(true)];
data.mix = input.readFloat(); data.mix = input.readFloat();
data.softness = input.readFloat() * scale; data.softness = input.readFloat() * scale;
data.bendDirection = input.readByte(); int flags = input.read();
data.compress = input.readBoolean(); data.skinRequired = (flags & 1) != 0;
data.stretch = input.readBoolean(); data.bendDirection = (flags & 2) != 0 ? 1 : -1;
data.uniform = input.readBoolean(); data.compress = (flags & 4) != 0;
data.stretch = (flags & 8) != 0;
data.uniform = (flags & 16) != 0;
o[i] = data; o[i] = data;
} }
@ -236,13 +256,14 @@ public class SkeletonBinary extends SkeletonLoader {
for (int i = 0, nn; i < n; i++) { for (int i = 0, nn; i < n; i++) {
TransformConstraintData data = new TransformConstraintData(input.readString()); TransformConstraintData data = new TransformConstraintData(input.readString());
data.order = input.readInt(true); data.order = input.readInt(true);
data.skinRequired = input.readBoolean();
Object[] constraintBones = data.bones.setSize(nn = input.readInt(true)); Object[] constraintBones = data.bones.setSize(nn = input.readInt(true));
for (int ii = 0; ii < nn; ii++) for (int ii = 0; ii < nn; ii++)
constraintBones[ii] = bones[input.readInt(true)]; constraintBones[ii] = bones[input.readInt(true)];
data.target = (BoneData)bones[input.readInt(true)]; data.target = (BoneData)bones[input.readInt(true)];
data.local = input.readBoolean(); int flags = input.read();
data.relative = input.readBoolean(); data.skinRequired = (flags & 1) != 0;
data.local = (flags & 2) != 0;
data.relative = (flags & 4) != 0;
data.offsetRotation = input.readFloat(); data.offsetRotation = input.readFloat();
data.offsetX = input.readFloat() * scale; data.offsetX = input.readFloat() * scale;
data.offsetY = input.readFloat() * scale; data.offsetY = input.readFloat() * scale;
@ -282,6 +303,31 @@ public class SkeletonBinary extends SkeletonLoader {
o[i] = data; 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. // Default skin.
Skin defaultSkin = readSkin(input, skeletonData, true, nonessential); Skin defaultSkin = readSkin(input, skeletonData, true, nonessential);
if (defaultSkin != null) { if (defaultSkin != null) {
@ -302,8 +348,7 @@ public class SkeletonBinary extends SkeletonLoader {
Object[] items = linkedMeshes.items; Object[] items = linkedMeshes.items;
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++) {
LinkedMesh linkedMesh = (LinkedMesh)items[i]; LinkedMesh linkedMesh = (LinkedMesh)items[i];
Skin skin = linkedMesh.skin == null ? skeletonData.getDefaultSkin() : skeletonData.findSkin(linkedMesh.skin); Skin skin = skeletonData.skins.get(linkedMesh.skinIndex);
if (skin == null) throw new SerializationException("Skin not found: " + linkedMesh.skin);
Attachment parent = skin.getAttachment(linkedMesh.slotIndex, linkedMesh.parent); Attachment parent = skin.getAttachment(linkedMesh.slotIndex, linkedMesh.parent);
if (parent == null) throw new SerializationException("Parent mesh not found: " + linkedMesh.parent); if (parent == null) throw new SerializationException("Parent mesh not found: " + linkedMesh.parent);
linkedMesh.mesh.setTimelineAttachment(linkedMesh.inheritTimelines ? (VertexAttachment)parent : linkedMesh.mesh); linkedMesh.mesh.setTimelineAttachment(linkedMesh.inheritTimelines ? (VertexAttachment)parent : linkedMesh.mesh);
@ -315,7 +360,7 @@ public class SkeletonBinary extends SkeletonLoader {
// Events. // Events.
o = skeletonData.events.setSize(n = input.readInt(true)); o = skeletonData.events.setSize(n = input.readInt(true));
for (int i = 0; i < n; i++) { 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.intValue = input.readInt(false);
data.floatValue = input.readFloat(); data.floatValue = input.readFloat();
data.stringValue = input.readString(); data.stringValue = input.readString();
@ -353,7 +398,7 @@ public class SkeletonBinary extends SkeletonLoader {
if (slotCount == 0) return null; if (slotCount == 0) return null;
skin = new Skin("default"); skin = new Skin("default");
} else { } else {
skin = new Skin(input.readStringRef()); skin = new Skin(input.readString());
Object[] bones = skin.bones.setSize(input.readInt(true)), items = skeletonData.bones.items; Object[] bones = skin.bones.setSize(input.readInt(true)), items = skeletonData.bones.items;
for (int i = 0, n = skin.bones.size; i < n; i++) for (int i = 0, n = skin.bones.size; i < n; i++)
bones[i] = items[input.readInt(true)]; 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++) for (int i = 0, n = input.readInt(true); i < n; i++)
skin.constraints.add((ConstraintData)items[input.readInt(true)]); skin.constraints.add((ConstraintData)items[input.readInt(true)]);
items = skeletonData.pathConstraints.items; 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++) for (int i = 0, n = input.readInt(true); i < n; i++)
skin.constraints.add((ConstraintData)items[input.readInt(true)]); skin.constraints.add((ConstraintData)items[input.readInt(true)]);
skin.constraints.shrink(); skin.constraints.shrink();
@ -387,12 +435,13 @@ public class SkeletonBinary extends SkeletonLoader {
String attachmentName, boolean nonessential) throws IOException { String attachmentName, boolean nonessential) throws IOException {
float scale = this.scale; float scale = this.scale;
String name = input.readStringRef(); int flags = input.readByte();
if (name == null) name = attachmentName; String name = (flags & 8) != 0 ? input.readStringRef() : attachmentName;
switch (AttachmentType.values[flags & 0b111]) {
switch (AttachmentType.values[input.readByte()]) {
case region: { 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 rotation = input.readFloat();
float x = input.readFloat(); float x = input.readFloat();
float y = input.readFloat(); float y = input.readFloat();
@ -400,8 +449,6 @@ public class SkeletonBinary extends SkeletonLoader {
float scaleY = input.readFloat(); float scaleY = input.readFloat();
float width = input.readFloat(); float width = input.readFloat();
float height = input.readFloat(); float height = input.readFloat();
int color = input.readInt();
Sequence sequence = readSequence(input);
if (path == null) path = name; if (path == null) path = name;
RegionAttachment region = attachmentLoader.newRegionAttachment(skin, name, path, sequence); RegionAttachment region = attachmentLoader.newRegionAttachment(skin, name, path, sequence);
@ -420,43 +467,41 @@ public class SkeletonBinary extends SkeletonLoader {
return region; return region;
} }
case boundingbox: { case boundingbox: {
int vertexCount = input.readInt(true); Vertices vertices = readVertices(input, (flags & 16) != 0);
Vertices vertices = readVertices(input, vertexCount);
int color = nonessential ? input.readInt() : 0; int color = nonessential ? input.readInt() : 0;
BoundingBoxAttachment box = attachmentLoader.newBoundingBoxAttachment(skin, name); BoundingBoxAttachment box = attachmentLoader.newBoundingBoxAttachment(skin, name);
if (box == null) return null; if (box == null) return null;
box.setWorldVerticesLength(vertexCount << 1); box.setWorldVerticesLength(vertices.length);
box.setVertices(vertices.vertices); box.setVertices(vertices.vertices);
box.setBones(vertices.bones); box.setBones(vertices.bones);
if (nonessential) Color.rgba8888ToColor(box.getColor(), color); if (nonessential) Color.rgba8888ToColor(box.getColor(), color);
return box; return box;
} }
case mesh: { case mesh: {
String path = input.readStringRef(); String path = (flags & 16) != 0 ? input.readStringRef() : name;
int color = input.readInt(); int color = (flags & 32) != 0 ? input.readInt() : 0xffffffff;
int vertexCount = input.readInt(true); Sequence sequence = (flags & 64) != 0 ? readSequence(input) : null;
float[] uvs = readFloatArray(input, vertexCount << 1, 1);
short[] triangles = readShortArray(input);
Vertices vertices = readVertices(input, vertexCount);
int hullLength = input.readInt(true); 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; short[] edges = null;
float width = 0, height = 0; float width = 0, height = 0;
if (nonessential) { if (nonessential) {
edges = readShortArray(input); edges = readShortArray(input, input.readInt(true));
width = input.readFloat(); width = input.readFloat();
height = input.readFloat(); height = input.readFloat();
} }
if (path == null) path = name;
MeshAttachment mesh = attachmentLoader.newMeshAttachment(skin, name, path, sequence); MeshAttachment mesh = attachmentLoader.newMeshAttachment(skin, name, path, sequence);
if (mesh == null) return null; if (mesh == null) return null;
mesh.setPath(path); mesh.setPath(path);
Color.rgba8888ToColor(mesh.getColor(), color); Color.rgba8888ToColor(mesh.getColor(), color);
mesh.setBones(vertices.bones); mesh.setBones(vertices.bones);
mesh.setVertices(vertices.vertices); mesh.setVertices(vertices.vertices);
mesh.setWorldVerticesLength(vertexCount << 1); mesh.setWorldVerticesLength(vertices.length);
mesh.setTriangles(triangles); mesh.setTriangles(triangles);
mesh.setRegionUVs(uvs); mesh.setRegionUVs(uvs);
if (sequence == null) mesh.updateRegion(); if (sequence == null) mesh.updateRegion();
@ -470,19 +515,18 @@ public class SkeletonBinary extends SkeletonLoader {
return mesh; return mesh;
} }
case linkedmesh: { case linkedmesh: {
String path = input.readStringRef(); String path = (flags & 16) != 0 ? input.readStringRef() : name;
int color = input.readInt(); int color = (flags & 32) != 0 ? input.readInt() : 0xffffffff;
String skinName = input.readStringRef(); Sequence sequence = (flags & 64) != 0 ? readSequence(input) : null;
boolean inheritTimelines = (flags & 128) != 0;
int skinIndex = input.readInt(true);
String parent = input.readStringRef(); String parent = input.readStringRef();
boolean inheritTimelines = input.readBoolean();
Sequence sequence = readSequence(input);
float width = 0, height = 0; float width = 0, height = 0;
if (nonessential) { if (nonessential) {
width = input.readFloat(); width = input.readFloat();
height = input.readFloat(); height = input.readFloat();
} }
if (path == null) path = name;
MeshAttachment mesh = attachmentLoader.newMeshAttachment(skin, name, path, sequence); MeshAttachment mesh = attachmentLoader.newMeshAttachment(skin, name, path, sequence);
if (mesh == null) return null; if (mesh == null) return null;
mesh.setPath(path); mesh.setPath(path);
@ -492,15 +536,14 @@ public class SkeletonBinary extends SkeletonLoader {
mesh.setWidth(width * scale); mesh.setWidth(width * scale);
mesh.setHeight(height * 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; return mesh;
} }
case path: { case path: {
boolean closed = input.readBoolean(); boolean closed = (flags & 16) != 0;
boolean constantSpeed = input.readBoolean(); boolean constantSpeed = (flags & 32) != 0;
int vertexCount = input.readInt(true); Vertices vertices = readVertices(input, (flags & 64) != 0);
Vertices vertices = readVertices(input, vertexCount); float[] lengths = new float[vertices.length / 6];
float[] lengths = new float[vertexCount / 3];
for (int i = 0, n = lengths.length; i < n; i++) for (int i = 0, n = lengths.length; i < n; i++)
lengths[i] = input.readFloat() * scale; lengths[i] = input.readFloat() * scale;
int color = nonessential ? input.readInt() : 0; int color = nonessential ? input.readInt() : 0;
@ -509,7 +552,7 @@ public class SkeletonBinary extends SkeletonLoader {
if (path == null) return null; if (path == null) return null;
path.setClosed(closed); path.setClosed(closed);
path.setConstantSpeed(constantSpeed); path.setConstantSpeed(constantSpeed);
path.setWorldVerticesLength(vertexCount << 1); path.setWorldVerticesLength(vertices.length);
path.setVertices(vertices.vertices); path.setVertices(vertices.vertices);
path.setBones(vertices.bones); path.setBones(vertices.bones);
path.setLengths(lengths); path.setLengths(lengths);
@ -532,14 +575,13 @@ public class SkeletonBinary extends SkeletonLoader {
} }
case clipping: case clipping:
int endSlotIndex = input.readInt(true); int endSlotIndex = input.readInt(true);
int vertexCount = input.readInt(true); Vertices vertices = readVertices(input, (flags & 16) != 0);
Vertices vertices = readVertices(input, vertexCount);
int color = nonessential ? input.readInt() : 0; int color = nonessential ? input.readInt() : 0;
ClippingAttachment clip = attachmentLoader.newClippingAttachment(skin, name); ClippingAttachment clip = attachmentLoader.newClippingAttachment(skin, name);
if (clip == null) return null; if (clip == null) return null;
clip.setEndSlot(skeletonData.slots.get(endSlotIndex)); clip.setEndSlot(skeletonData.slots.get(endSlotIndex));
clip.setWorldVerticesLength(vertexCount << 1); clip.setWorldVerticesLength(vertices.length);
clip.setVertices(vertices.vertices); clip.setVertices(vertices.vertices);
clip.setBones(vertices.bones); clip.setBones(vertices.bones);
if (nonessential) Color.rgba8888ToColor(clip.getColor(), color); if (nonessential) Color.rgba8888ToColor(clip.getColor(), color);
@ -549,7 +591,6 @@ public class SkeletonBinary extends SkeletonLoader {
} }
private Sequence readSequence (SkeletonInput input) throws IOException { private Sequence readSequence (SkeletonInput input) throws IOException {
if (!input.readBoolean()) return null;
Sequence sequence = new Sequence(input.readInt(true)); Sequence sequence = new Sequence(input.readInt(true));
sequence.setStart(input.readInt(true)); sequence.setStart(input.readInt(true));
sequence.setDigits(input.readInt(true)); sequence.setDigits(input.readInt(true));
@ -557,16 +598,17 @@ public class SkeletonBinary extends SkeletonLoader {
return sequence; return sequence;
} }
private Vertices readVertices (SkeletonInput input, int vertexCount) throws IOException { private Vertices readVertices (SkeletonInput input, boolean weighted) throws IOException {
float scale = this.scale; float scale = this.scale;
int verticesLength = vertexCount << 1; int vertexCount = input.readInt(true);
Vertices vertices = new Vertices(); Vertices vertices = new Vertices();
if (!input.readBoolean()) { vertices.length = vertexCount << 1;
vertices.vertices = readFloatArray(input, verticesLength, scale); if (!weighted) {
vertices.vertices = readFloatArray(input, vertices.length, scale);
return vertices; return vertices;
} }
FloatArray weights = new FloatArray(verticesLength * 3 * 3); FloatArray weights = new FloatArray(vertices.length * 3 * 3);
IntArray bonesArray = new IntArray(verticesLength * 3); IntArray bonesArray = new IntArray(vertices.length * 3);
for (int i = 0; i < vertexCount; i++) { for (int i = 0; i < vertexCount; i++) {
int boneCount = input.readInt(true); int boneCount = input.readInt(true);
bonesArray.add(boneCount); bonesArray.add(boneCount);
@ -594,11 +636,10 @@ public class SkeletonBinary extends SkeletonLoader {
return array; return array;
} }
private short[] readShortArray (SkeletonInput input) throws IOException { private short[] readShortArray (SkeletonInput input, int n) throws IOException {
int n = input.readInt(true);
short[] array = new short[n]; short[] array = new short[n];
for (int i = 0; i < n; i++) for (int i = 0; i < n; i++)
array[i] = input.readShort(); array[i] = (short)input.readInt(true);
return array; return array;
} }
@ -777,34 +818,34 @@ public class SkeletonBinary extends SkeletonLoader {
int type = input.readByte(), frameCount = input.readInt(true), bezierCount = input.readInt(true); int type = input.readByte(), frameCount = input.readInt(true), bezierCount = input.readInt(true);
switch (type) { switch (type) {
case BONE_ROTATE: case BONE_ROTATE:
timelines.add(readTimeline(input, new RotateTimeline(frameCount, bezierCount, boneIndex), 1)); readTimeline(input, timelines, new RotateTimeline(frameCount, bezierCount, boneIndex), 1);
break; break;
case BONE_TRANSLATE: case BONE_TRANSLATE:
timelines.add(readTimeline(input, new TranslateTimeline(frameCount, bezierCount, boneIndex), scale)); readTimeline(input, timelines, new TranslateTimeline(frameCount, bezierCount, boneIndex), scale);
break; break;
case BONE_TRANSLATEX: case BONE_TRANSLATEX:
timelines.add(readTimeline(input, new TranslateXTimeline(frameCount, bezierCount, boneIndex), scale)); readTimeline(input, timelines, new TranslateXTimeline(frameCount, bezierCount, boneIndex), scale);
break; break;
case BONE_TRANSLATEY: case BONE_TRANSLATEY:
timelines.add(readTimeline(input, new TranslateYTimeline(frameCount, bezierCount, boneIndex), scale)); readTimeline(input, timelines, new TranslateYTimeline(frameCount, bezierCount, boneIndex), scale);
break; break;
case BONE_SCALE: case BONE_SCALE:
timelines.add(readTimeline(input, new ScaleTimeline(frameCount, bezierCount, boneIndex), 1)); readTimeline(input, timelines, new ScaleTimeline(frameCount, bezierCount, boneIndex), 1);
break; break;
case BONE_SCALEX: case BONE_SCALEX:
timelines.add(readTimeline(input, new ScaleXTimeline(frameCount, bezierCount, boneIndex), 1)); readTimeline(input, timelines, new ScaleXTimeline(frameCount, bezierCount, boneIndex), 1);
break; break;
case BONE_SCALEY: case BONE_SCALEY:
timelines.add(readTimeline(input, new ScaleYTimeline(frameCount, bezierCount, boneIndex), 1)); readTimeline(input, timelines, new ScaleYTimeline(frameCount, bezierCount, boneIndex), 1);
break; break;
case BONE_SHEAR: case BONE_SHEAR:
timelines.add(readTimeline(input, new ShearTimeline(frameCount, bezierCount, boneIndex), 1)); readTimeline(input, timelines, new ShearTimeline(frameCount, bezierCount, boneIndex), 1);
break; break;
case BONE_SHEARX: case BONE_SHEARX:
timelines.add(readTimeline(input, new ShearXTimeline(frameCount, bezierCount, boneIndex), 1)); readTimeline(input, timelines, new ShearXTimeline(frameCount, bezierCount, boneIndex), 1);
break; break;
case BONE_SHEARY: 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); IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount, input.readInt(true), index);
float time = input.readFloat(), mix = input.readFloat(), softness = input.readFloat() * scale; float time = input.readFloat(), mix = input.readFloat(), softness = input.readFloat() * scale;
for (int frame = 0, bezier = 0;; frame++) { 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; if (frame == frameLast) break;
float time2 = input.readFloat(), mix2 = input.readFloat(), softness2 = input.readFloat() * scale; float time2 = input.readFloat(), mix2 = input.readFloat(), softness2 = input.readFloat() * scale;
switch (input.readByte()) { switch (input.readByte()) {
@ -872,20 +914,18 @@ public class SkeletonBinary extends SkeletonLoader {
int index = input.readInt(true); int index = input.readInt(true);
PathConstraintData data = skeletonData.pathConstraints.get(index); PathConstraintData data = skeletonData.pathConstraints.get(index);
for (int ii = 0, nn = input.readInt(true); ii < nn; ii++) { 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: case PATH_POSITION:
timelines readTimeline(input, timelines, new PathConstraintPositionTimeline(frameCount, bezierCount, index),
.add(readTimeline(input, new PathConstraintPositionTimeline(input.readInt(true), input.readInt(true), index), data.positionMode == PositionMode.fixed ? scale : 1);
data.positionMode == PositionMode.fixed ? scale : 1));
break; break;
case PATH_SPACING: case PATH_SPACING:
timelines readTimeline(input, timelines, new PathConstraintSpacingTimeline(frameCount, bezierCount, index),
.add(readTimeline(input, new PathConstraintSpacingTimeline(input.readInt(true), input.readInt(true), index), data.spacingMode == SpacingMode.length || data.spacingMode == SpacingMode.fixed ? scale : 1);
data.spacingMode == SpacingMode.length || data.spacingMode == SpacingMode.fixed ? scale : 1));
break; break;
case PATH_MIX: case PATH_MIX:
PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(input.readInt(true), input.readInt(true), PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(frameCount, bezierCount, index);
index);
float time = input.readFloat(), mixRotate = input.readFloat(), mixX = input.readFloat(), mixY = input.readFloat(); float time = input.readFloat(), mixRotate = input.readFloat(), mixX = input.readFloat(), mixY = input.readFloat();
for (int frame = 0, bezier = 0, frameLast = timeline.getFrameCount() - 1;; frame++) { for (int frame = 0, bezier = 0, frameLast = timeline.getFrameCount() - 1;; frame++) {
timeline.setFrame(frame, time, mixRotate, mixX, mixY); 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. // Attachment timelines.
for (int i = 0, n = input.readInt(true); i < n; i++) { for (int i = 0, n = input.readInt(true); i < n; i++) {
Skin skin = skeletonData.skins.get(input.readInt(true)); Skin skin = skeletonData.skins.get(input.readInt(true));
@ -1024,7 +1094,8 @@ public class SkeletonBinary extends SkeletonLoader {
Event event = new Event(time, eventData); Event event = new Event(time, eventData);
event.intValue = input.readInt(false); event.intValue = input.readInt(false);
event.floatValue = input.readFloat(); 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) { if (event.getData().audioPath != null) {
event.volume = input.readFloat(); event.volume = input.readFloat();
event.balance = input.readFloat(); event.balance = input.readFloat();
@ -1041,7 +1112,8 @@ public class SkeletonBinary extends SkeletonLoader {
return new Animation(name, timelines, duration); return new Animation(name, timelines, duration);
} }
private Timeline readTimeline (SkeletonInput input, CurveTimeline1 timeline, float scale) throws IOException { private void readTimeline (SkeletonInput input, Array<Timeline> timelines, CurveTimeline1 timeline, float scale)
throws IOException {
float time = input.readFloat(), value = input.readFloat() * scale; float time = input.readFloat(), value = input.readFloat() * scale;
for (int frame = 0, bezier = 0, frameLast = timeline.getFrameCount() - 1;; frame++) { for (int frame = 0, bezier = 0, frameLast = timeline.getFrameCount() - 1;; frame++) {
timeline.setFrame(frame, time, value); timeline.setFrame(frame, time, value);
@ -1057,10 +1129,11 @@ public class SkeletonBinary extends SkeletonLoader {
time = time2; time = time2;
value = value2; value = value2;
} }
return timeline; timelines.add(timeline);
} }
private Timeline readTimeline (SkeletonInput input, CurveTimeline2 timeline, float scale) throws IOException { private void readTimeline (SkeletonInput input, Array<Timeline> timelines, CurveTimeline2 timeline, float scale)
throws IOException {
float time = input.readFloat(), value1 = input.readFloat() * scale, value2 = input.readFloat() * scale; float time = input.readFloat(), value1 = input.readFloat() * scale, value2 = input.readFloat() * scale;
for (int frame = 0, bezier = 0, frameLast = timeline.getFrameCount() - 1;; frame++) { for (int frame = 0, bezier = 0, frameLast = timeline.getFrameCount() - 1;; frame++) {
timeline.setFrame(frame, time, value1, value2); timeline.setFrame(frame, time, value1, value2);
@ -1078,7 +1151,7 @@ public class SkeletonBinary extends SkeletonLoader {
value1 = nvalue1; value1 = nvalue1;
value2 = nvalue2; value2 = nvalue2;
} }
return timeline; timelines.add(timeline);
} }
void setBezier (SkeletonInput input, CurveTimeline timeline, int bezier, int frame, int value, float time1, float time2, 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 { static class Vertices {
int length;
int[] bones; int[] bones;
float[] vertices; float[] vertices;
} }
@ -1143,4 +1217,19 @@ public class SkeletonBinary extends SkeletonLoader {
return new String(chars, 0, charCount); 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;
}
}
} }

View File

@ -98,6 +98,8 @@ import com.esotericsoftware.spine.attachments.VertexAttachment;
* <a href="http://esotericsoftware.com/spine-loading-skeleton-data#JSON-and-binary-data">JSON and binary data</a> in the Spine * <a href="http://esotericsoftware.com/spine-loading-skeleton-data#JSON-and-binary-data">JSON and binary data</a> in the Spine
* Runtimes Guide. */ * Runtimes Guide. */
public class SkeletonJson extends SkeletonLoader { public class SkeletonJson extends SkeletonLoader {
private final Array<LinkedMesh> linkedMeshes = new Array();
public SkeletonJson (AttachmentLoader attachmentLoader) { public SkeletonJson (AttachmentLoader attachmentLoader) {
super(attachmentLoader); super(attachmentLoader);
} }
@ -161,6 +163,8 @@ public class SkeletonJson extends SkeletonLoader {
String color = boneMap.getString("color", null); String color = boneMap.getString("color", null);
if (color != null) Color.valueOf(color, data.getColor()); if (color != null) Color.valueOf(color, data.getColor());
data.icon = boneMap.getString("icon", null);
skeletonData.bones.add(data); skeletonData.bones.add(data);
} }
@ -877,6 +881,8 @@ public class SkeletonJson extends SkeletonLoader {
} }
} }
// BOZO - Physics timelines.
// Attachment timelines. // Attachment timelines.
for (JsonValue attachmentsMap = map.getChild("attachments"); attachmentsMap != null; attachmentsMap = attachmentsMap.next) { for (JsonValue attachmentsMap = map.getChild("attachments"); attachmentsMap != null; attachmentsMap = attachmentsMap.next) {
Skin skin = skeletonData.findSkin(attachmentsMap.name); Skin skin = skeletonData.findSkin(attachmentsMap.name);

View File

@ -33,9 +33,7 @@ import java.io.InputStream;
import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.g2d.TextureAtlas; 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.AtlasAttachmentLoader;
import com.esotericsoftware.spine.attachments.AttachmentLoader; import com.esotericsoftware.spine.attachments.AttachmentLoader;
@ -46,7 +44,6 @@ import com.esotericsoftware.spine.attachments.AttachmentLoader;
abstract public class SkeletonLoader { abstract public class SkeletonLoader {
final AttachmentLoader attachmentLoader; final AttachmentLoader attachmentLoader;
float scale = 1; float scale = 1;
final Array<LinkedMesh> linkedMeshes = new Array();
/** Creates a skeleton loader that loads attachments using an {@link AtlasAttachmentLoader} with the specified atlas. */ /** Creates a skeleton loader that loads attachments using an {@link AtlasAttachmentLoader} with the specified atlas. */
public SkeletonLoader (TextureAtlas atlas) { public SkeletonLoader (TextureAtlas atlas) {

View File

@ -251,7 +251,7 @@ public class TransformConstraint implements Updatable {
float rotation = bone.arotation; float rotation = bone.arotation;
if (mixRotate != 0) { if (mixRotate != 0) {
float r = target.arotation - rotation + data.offsetRotation; 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; rotation += r * mixRotate;
} }
@ -268,7 +268,7 @@ public class TransformConstraint implements Updatable {
float shearY = bone.ashearY; float shearY = bone.ashearY;
if (mixShearY != 0) { if (mixShearY != 0) {
float r = target.ashearY - shearY + data.offsetShearY; 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; shearY += r * mixShearY;
} }

View File

@ -32,6 +32,7 @@ package com.esotericsoftware.spine.utils;
public class SpineUtils { public class SpineUtils {
static public final float PI = 3.1415927f; static public final float PI = 3.1415927f;
static public final float PI2 = PI * 2; 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 radiansToDegrees = 180f / PI;
static public final float radDeg = radiansToDegrees; static public final float radDeg = radiansToDegrees;
static public final float degreesToRadians = PI / 180; static public final float degreesToRadians = PI / 180;