[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, //
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<Event> 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<Event> 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<Event> 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<Event> 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<Event> 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<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}. */
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;
}

View File

@ -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 {

View File

@ -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;
}

View File

@ -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) {
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;
}
}
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 += (wind * 500 - xOffset * strength - xVelocity * friction) * mass;
xVelocity += (100 * wind - xOffset * strength) * mass;
xOffset += xVelocity * step;
xVelocity *= damping;
}
if (y) {
yVelocity += (-gravity * 500 - yOffset * strength - yVelocity * friction) * mass;
yVelocity += (-100 * gravity - yOffset * strength) * 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;
}
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 (rotateOrShearX) {
float r = rotateOffset * mix, ra = bone.a, sin, cos;
if (data.rotate) {
if (data.shearX) {
float t = (float)Math.tan(r * 0.5f * degRad);
bone.b += bone.a * t;
bone.d += bone.c * t;
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;

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. */
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;

View File

@ -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<LinkedMesh> 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<Timeline> 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<Timeline> 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;
}
}
}

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
* Runtimes Guide. */
public class SkeletonJson extends SkeletonLoader {
private final Array<LinkedMesh> 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);

View File

@ -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<LinkedMesh> linkedMeshes = new Array();
/** Creates a skeleton loader that loads attachments using an {@link AtlasAttachmentLoader} with the specified atlas. */
public SkeletonLoader (TextureAtlas atlas) {

View File

@ -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;
}

View File

@ -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;