Experimental WIP for mixing unkeyed bones to setup pose.

#621
This commit is contained in:
NathanSweet 2016-08-21 20:58:40 +02:00
parent c59a58ae5b
commit b4924b0a49
9 changed files with 358 additions and 195 deletions

View File

@ -530,6 +530,33 @@ public class AnimationStateTest {
state.addAnimation(0, "events1", false, 5);
run(0.1f, 10, null);
setup("setAnimation during AnimationStateListener"); // 23
state.addListener(new AnimationStateListener() {
public void start (TrackEntry entry) {
if (entry.getAnimation().getName().equals("events1")) state.setAnimation(1, "events2", false);
}
public void interrupt (TrackEntry entry) {
state.addAnimation(3, "events2", false, 0);
}
public void event (TrackEntry entry, Event event) {
if (entry.getTrackIndex() != 2) state.setAnimation(2, "events2", false);
}
public void end (TrackEntry entry) {
if (entry.getAnimation().getName().equals("events1")) state.setAnimation(0, "events2", false);
}
public void complete (TrackEntry entry) {
if (entry.getAnimation().getName().equals("events1")) state.setAnimation(1, "events2", false);
}
});
state.addAnimation(0, "events1", false, 0);
state.addAnimation(0, "events2", false, 0);
state.setAnimation(1, "events2", false);
run(0.1f, 10, null);
System.out.println("AnimationState tests passed.");
}
@ -538,11 +565,13 @@ public class AnimationStateTest {
expected.addAll(expectedArray);
stateData = new AnimationStateData(skeletonData);
state = new AnimationState(stateData);
state.addListener(stateListener);
time = 0;
fail = false;
log(test + ": " + description);
log(String.format("%-3s%-12s%-7s%-7s%-7s", "#", "EVENT", "TRACK", "TOTAL", "RESULT"));
if (expectedArray.length > 0) {
state.addListener(stateListener);
log(String.format("%-3s%-12s%-7s%-7s%-7s", "#", "EVENT", "TRACK", "TOTAL", "RESULT"));
}
}
void run (float incr, float endTime, TestListener listener) {

View File

@ -65,7 +65,7 @@ public class BonePlotting {
for (Animation animation : skeletonData.getAnimations()) {
float time = 0;
while (time < animation.getDuration()) {
animation.apply(skeleton, time, time, false, null);
animation.apply(skeleton, time, time, false, null, 1, false);
skeleton.updateWorldTransform();
System.out
.println(animation.getName() + "," + bone.getWorldX() + "," + bone.getWorldY() + "," + bone.getWorldRotationX());

View File

@ -116,9 +116,8 @@ public class Box2DExample extends ApplicationAdapter {
Box2dAttachment attachment = (Box2dAttachment)slot.getAttachment();
PolygonShape boxPoly = new PolygonShape();
boxPoly.setAsBox(attachment.getWidth() / 2 * attachment.getScaleX(),
attachment.getHeight() / 2 * attachment.getScaleY(), vector.set(attachment.getX(), attachment.getY()),
attachment.getRotation() * MathUtils.degRad);
boxPoly.setAsBox(attachment.getWidth() / 2 * attachment.getScaleX(), attachment.getHeight() / 2 * attachment.getScaleY(),
vector.set(attachment.getX(), attachment.getY()), attachment.getRotation() * MathUtils.degRad);
BodyDef boxBodyDef = new BodyDef();
boxBodyDef.type = BodyType.StaticBody;
@ -146,7 +145,7 @@ public class Box2DExample extends ApplicationAdapter {
batch.setTransformMatrix(camera.view);
batch.begin();
animation.apply(skeleton, time, time, true, events);
animation.apply(skeleton, time, time, true, events, 1, false);
skeleton.x += 8 * delta;
skeleton.updateWorldTransform();
skeletonRenderer.draw(batch, skeleton);

View File

@ -177,7 +177,7 @@ public class EventTimelineTests {
int beforeCount = firedEvents.size;
Array<Event> original = new Array(firedEvents);
timeline.apply(skeleton, lastTimeLooped, timeLooped, firedEvents, 1);
timeline.apply(skeleton, lastTimeLooped, timeLooped, firedEvents, 1, false);
while (beforeCount < firedEvents.size) {
char fired = firedEvents.get(beforeCount).getData().getName().charAt(0);
@ -186,7 +186,7 @@ public class EventTimelineTests {
} else {
if (firedEvents.size > eventsCount) {
if (print) System.out.println(lastTimeLooped + "->" + timeLooped + ": " + fired + " == ?");
timeline.apply(skeleton, lastTimeLooped, timeLooped, original, 1);
timeline.apply(skeleton, lastTimeLooped, timeLooped, original, 1, false);
fail("Too many events fired.");
}
}
@ -194,7 +194,7 @@ public class EventTimelineTests {
System.out.println(lastTimeLooped + "->" + timeLooped + ": " + fired + " == " + events[eventIndex]);
}
if (fired != events[eventIndex]) {
timeline.apply(skeleton, lastTimeLooped, timeLooped, original, 1);
timeline.apply(skeleton, lastTimeLooped, timeLooped, original, 1, false);
fail("Wrong event fired.");
}
eventIndex++;
@ -206,7 +206,7 @@ public class EventTimelineTests {
i++;
}
if (firedEvents.size < eventsCount) {
timeline.apply(skeleton, lastTimeLooped, timeLooped, firedEvents, 1);
timeline.apply(skeleton, lastTimeLooped, timeLooped, firedEvents, 1, false);
if (print) System.out.println(firedEvents);
fail("Event not fired: " + events[eventIndex] + ", " + frames[eventIndex]);
}

View File

@ -104,21 +104,22 @@ public class MixTest extends ApplicationAdapter {
skeleton.setX(-50);
} else if (time > beforeJump + jump) {
// just walk after jump
walkAnimation.apply(skeleton, time, time, true, events);
walkAnimation.apply(skeleton, time, time, true, events, 1, false);
} else if (time > blendOutStart) {
// blend out jump
walkAnimation.apply(skeleton, time, time, true, events);
jumpAnimation.mix(skeleton, time - beforeJump, time - beforeJump, false, events, 1 - (time - blendOutStart) / blendOut);
walkAnimation.apply(skeleton, time, time, true, events, 1, false);
jumpAnimation.apply(skeleton, time - beforeJump, time - beforeJump, false, events, 1 - (time - blendOutStart) / blendOut,
false);
} else if (time > beforeJump + blendIn) {
// just jump
jumpAnimation.apply(skeleton, time - beforeJump, time - beforeJump, false, events);
jumpAnimation.apply(skeleton, time - beforeJump, time - beforeJump, false, events, 1, false);
} else if (time > beforeJump) {
// blend in jump
walkAnimation.apply(skeleton, time, time, true, events);
jumpAnimation.mix(skeleton, time - beforeJump, time - beforeJump, false, events, (time - beforeJump) / blendIn);
walkAnimation.apply(skeleton, time, time, true, events, 1, false);
jumpAnimation.apply(skeleton, time - beforeJump, time - beforeJump, false, events, (time - beforeJump) / blendIn, false);
} else {
// just walk before jump
walkAnimation.apply(skeleton, time, time, true, events);
walkAnimation.apply(skeleton, time, time, true, events, 1, false);
}
skeleton.updateWorldTransform();

View File

@ -131,7 +131,7 @@ public class NormalMapTest extends ApplicationAdapter {
public void render () {
float lastTime = time;
time += Gdx.graphics.getDeltaTime();
if (animation != null) animation.apply(skeleton, lastTime, time, true, null);
if (animation != null) animation.apply(skeleton, lastTime, time, true, null, 1, false);
skeleton.updateWorldTransform();
skeleton.update(Gdx.graphics.getDeltaTime());

View File

@ -64,27 +64,14 @@ public class Animation {
this.duration = duration;
}
/** Poses the skeleton at the specified time for this animation.
* @param lastTime The last time the animation was applied.
* @param events Any triggered events are added. May be null. */
public void apply (Skeleton skeleton, float lastTime, float time, boolean loop, Array<Event> events) {
if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null.");
if (loop && duration != 0) {
time %= duration;
if (lastTime > 0) lastTime %= duration;
}
Array<Timeline> timelines = this.timelines;
for (int i = 0, n = timelines.size; i < n; i++)
timelines.get(i).apply(skeleton, lastTime, time, events, 1);
}
/** Poses the skeleton at the specified time for this animation mixed with the current pose.
/** Poses the skeleton at the specified time for this animation mixed with the current or setup pose.
* @param lastTime The last time the animation was applied.
* @param events Any triggered events are added. May be null.
* @param alpha The amount of this animation that affects the current pose. */
public void mix (Skeleton skeleton, float lastTime, float time, boolean loop, Array<Event> events, float alpha) {
* @param alpha The amount of this animation that affects the current pose.
* @param setupPose If true, the animation is mixed with the setup pose, else it is mixed with the current pose. Pass true when
* alpha is 1. */
public void apply (Skeleton skeleton, float lastTime, float time, boolean loop, Array<Event> events, float alpha,
boolean setupPose) {
if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null.");
if (loop && duration != 0) {
@ -94,7 +81,7 @@ public class Animation {
Array<Timeline> timelines = this.timelines;
for (int i = 0, n = timelines.size; i < n; i++)
timelines.get(i).apply(skeleton, lastTime, time, events, alpha);
timelines.get(i).apply(skeleton, lastTime, time, events, alpha, setupPose);
}
public String getName () {
@ -147,8 +134,20 @@ public class Animation {
static public interface Timeline {
/** Sets the value(s) for the specified time.
* @param events May be null to not collect fired events. */
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha);
* @param events May be null to not collect fired events.
* @param setupPose If true, the timeline is mixed with the setup pose, else it is mixed with the current pose. Pass true
* when alpha is 1. */
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, boolean setupPose);
public int getId ();
}
static private enum TimelineType {
rotate, translate, scale, shear, //
attachment, color, deform, //
event, drawOrder, //
ikConstraint, transformConstraint, //
pathConstraintPosition, pathConstraintSpacing, pathConstraintMix
}
/** Base class for frames that use an interpolation bezier curve. */
@ -251,6 +250,10 @@ public class Animation {
frames = new float[frameCount << 1];
}
public int getId () {
return (TimelineType.rotate.ordinal() << 24) + boneIndex;
}
public void setBoneIndex (int index) {
if (index < 0) throw new IllegalArgumentException("index must be >= 0.");
this.boneIndex = index;
@ -271,19 +274,19 @@ public class Animation {
frames[frameIndex + ROTATION] = degrees;
}
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha) {
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, boolean setupPose) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
Bone bone = skeleton.bones.get(boneIndex);
if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame.
float amount = bone.data.rotation + frames[frames.length + PREV_ROTATION] - bone.rotation;
while (amount > 180)
amount -= 360;
while (amount < -180)
amount += 360;
bone.rotation += amount * alpha;
if (setupPose)
bone.rotation = bone.data.rotation + frames[frames.length + PREV_ROTATION] * alpha;
else {
float amount = bone.data.rotation + frames[frames.length + PREV_ROTATION] - bone.rotation;
amount -= (16384 - (int)(16384.499999999996 - amount / 360)) * 360;
bone.rotation += amount * alpha;
}
return;
}
@ -294,16 +297,16 @@ public class Animation {
float percent = getCurvePercent((frame >> 1) - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
float amount = frames[frame + ROTATION] - prevRotation;
while (amount > 180)
amount -= 360;
while (amount < -180)
amount += 360;
amount = bone.data.rotation + (prevRotation + amount * percent) - bone.rotation;
while (amount > 180)
amount -= 360;
while (amount < -180)
amount += 360;
bone.rotation += amount * alpha;
amount = amount - (16384 - (int)(16384.499999999996 - amount / 360)) * 360;
if (setupPose) {
amount = prevRotation + amount * percent;
amount = amount - (16384 - (int)(16384.499999999996 - amount / 360)) * 360;
bone.rotation = bone.data.rotation + amount * alpha;
} else {
amount = bone.data.rotation + (prevRotation + amount * percent) - bone.rotation;
amount -= (16384 - (int)(16384.499999999996 - amount / 360)) * 360;
bone.rotation += amount * alpha;
}
}
}
@ -320,6 +323,10 @@ public class Animation {
frames = new float[frameCount * ENTRIES];
}
public int getId () {
return (TimelineType.translate.ordinal() << 24) + boneIndex;
}
public void setBoneIndex (int index) {
if (index < 0) throw new IllegalArgumentException("index must be >= 0.");
this.boneIndex = index;
@ -341,15 +348,20 @@ public class Animation {
frames[frameIndex + Y] = y;
}
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha) {
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, boolean setupPose) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
Bone bone = skeleton.bones.get(boneIndex);
if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame.
bone.x += (bone.data.x + frames[frames.length + PREV_X] - bone.x) * alpha;
bone.y += (bone.data.y + frames[frames.length + PREV_Y] - bone.y) * alpha;
if (setupPose) {
bone.x = bone.data.x + frames[frames.length + PREV_X] * alpha;
bone.y = bone.data.y + frames[frames.length + PREV_Y] * alpha;
} else {
bone.x += (bone.data.x + frames[frames.length + PREV_X] - bone.x) * alpha;
bone.y += (bone.data.y + frames[frames.length + PREV_Y] - bone.y) * alpha;
}
return;
}
@ -360,8 +372,13 @@ public class Animation {
float frameTime = frames[frame];
float percent = getCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
bone.x += (bone.data.x + prevX + (frames[frame + X] - prevX) * percent - bone.x) * alpha;
bone.y += (bone.data.y + prevY + (frames[frame + Y] - prevY) * percent - bone.y) * alpha;
if (setupPose) {
bone.x = bone.data.x + (prevX + (frames[frame + X] - prevX) * percent) * alpha;
bone.y = bone.data.y + (prevY + (frames[frame + Y] - prevY) * percent) * alpha;
} else {
bone.x += (bone.data.x + prevX + (frames[frame + X] - prevX) * percent - bone.x) * alpha;
bone.y += (bone.data.y + prevY + (frames[frame + Y] - prevY) * percent - bone.y) * alpha;
}
}
}
@ -370,7 +387,11 @@ public class Animation {
super(frameCount);
}
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha) {
public int getId () {
return (TimelineType.scale.ordinal() << 24) + boneIndex;
}
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, boolean setupPose) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
@ -409,7 +430,11 @@ public class Animation {
super(frameCount);
}
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha) {
public int getId () {
return (TimelineType.shear.ordinal() << 24) + boneIndex;
}
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, boolean setupPose) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
@ -445,6 +470,10 @@ public class Animation {
frames = new float[frameCount * ENTRIES];
}
public int getId () {
return (TimelineType.color.ordinal() << 24) + slotIndex;
}
public void setSlotIndex (int index) {
if (index < 0) throw new IllegalArgumentException("index must be >= 0.");
this.slotIndex = index;
@ -468,7 +497,7 @@ public class Animation {
frames[frameIndex + A] = a;
}
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha) {
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, boolean setupPose) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
@ -513,6 +542,10 @@ public class Animation {
attachmentNames = new String[frameCount];
}
public int getId () {
return (TimelineType.attachment.ordinal() << 24) + slotIndex;
}
public int getFrameCount () {
return frames.length;
}
@ -540,7 +573,7 @@ public class Animation {
attachmentNames[frameIndex] = attachmentName;
}
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha) {
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, boolean setupPose) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
@ -556,117 +589,11 @@ public class Animation {
}
}
static public class EventTimeline implements Timeline {
private final float[] frames; // time, ...
private final Event[] events;
public EventTimeline (int frameCount) {
frames = new float[frameCount];
events = new Event[frameCount];
}
public int getFrameCount () {
return frames.length;
}
public float[] getFrames () {
return frames;
}
public Event[] getEvents () {
return events;
}
/** Sets the time of the specified keyframe. */
public void setFrame (int frameIndex, Event event) {
frames[frameIndex] = event.time;
events[frameIndex] = event;
}
/** Fires events for frames > lastTime and <= time. */
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> firedEvents, float alpha) {
if (firedEvents == null) return;
float[] frames = this.frames;
int frameCount = frames.length;
if (lastTime > time) { // Fire events after last time for looped animations.
apply(skeleton, lastTime, Integer.MAX_VALUE, firedEvents, alpha);
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.
int frame;
if (lastTime < frames[0])
frame = 0;
else {
frame = binarySearch(frames, lastTime);
float frameTime = frames[frame];
while (frame > 0) { // Fire multiple events with the same frame.
if (frames[frame - 1] != frameTime) break;
frame--;
}
}
for (; frame < frameCount && time >= frames[frame]; frame++)
firedEvents.add(events[frame]);
}
}
static public class DrawOrderTimeline implements Timeline {
private final float[] frames; // time, ...
private final int[][] drawOrders;
public DrawOrderTimeline (int frameCount) {
frames = new float[frameCount];
drawOrders = new int[frameCount][];
}
public int getFrameCount () {
return frames.length;
}
public float[] getFrames () {
return frames;
}
public int[][] getDrawOrders () {
return drawOrders;
}
/** Sets the time of the specified keyframe.
* @param drawOrder May be null to use bind pose draw order. */
public void setFrame (int frameIndex, float time, int[] drawOrder) {
frames[frameIndex] = time;
drawOrders[frameIndex] = drawOrder;
}
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> firedEvents, float alpha) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
int frame;
if (time >= frames[frames.length - 1]) // Time is after last frame.
frame = frames.length - 1;
else
frame = binarySearch(frames, time) - 1;
Array<Slot> drawOrder = skeleton.drawOrder;
Array<Slot> slots = skeleton.slots;
int[] drawOrderToSetupIndex = drawOrders[frame];
if (drawOrderToSetupIndex == null)
System.arraycopy(slots.items, 0, drawOrder.items, 0, slots.size);
else {
for (int i = 0, n = drawOrderToSetupIndex.length; i < n; i++)
drawOrder.set(i, slots.get(drawOrderToSetupIndex[i]));
}
}
}
static public class DeformTimeline extends CurveTimeline {
private final float[] frames; // time, ...
private final float[][] frameVertices;
int slotIndex;
VertexAttachment attachment;
private final float[] frames; // time, ...
private final float[][] frameVertices;
public DeformTimeline (int frameCount) {
super(frameCount);
@ -674,6 +601,10 @@ public class Animation {
frameVertices = new float[frameCount][];
}
public int getId () {
return (TimelineType.deform.ordinal() << 24) + slotIndex;
}
public void setSlotIndex (int index) {
if (index < 0) throw new IllegalArgumentException("index must be >= 0.");
this.slotIndex = index;
@ -687,7 +618,7 @@ public class Animation {
this.attachment = attachment;
}
public Attachment getAttachment () {
public VertexAttachment getAttachment () {
return attachment;
}
@ -705,7 +636,8 @@ public class Animation {
frameVertices[frameIndex] = vertices;
}
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> firedEvents, float alpha) {
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> firedEvents, float alpha,
boolean setupPose) {
Slot slot = skeleton.slots.get(slotIndex);
Attachment slotAttachment = slot.attachment;
if (!(slotAttachment instanceof VertexAttachment) || !((VertexAttachment)slotAttachment).applyDeform(attachment)) return;
@ -751,6 +683,122 @@ public class Animation {
}
}
static public class EventTimeline implements Timeline {
private final float[] frames; // time, ...
private final Event[] events;
public EventTimeline (int frameCount) {
frames = new float[frameCount];
events = new Event[frameCount];
}
public int getId () {
return TimelineType.event.ordinal() << 24;
}
public int getFrameCount () {
return frames.length;
}
public float[] getFrames () {
return frames;
}
public Event[] getEvents () {
return events;
}
/** Sets the time of the specified keyframe. */
public void setFrame (int frameIndex, Event event) {
frames[frameIndex] = event.time;
events[frameIndex] = event;
}
/** Fires events for frames > lastTime and <= time. */
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> firedEvents, float alpha,
boolean setupPose) {
if (firedEvents == null) return;
float[] frames = this.frames;
int frameCount = frames.length;
if (lastTime > time) { // Fire events after last time for looped animations.
apply(skeleton, lastTime, Integer.MAX_VALUE, firedEvents, alpha, setupPose);
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.
int frame;
if (lastTime < frames[0])
frame = 0;
else {
frame = binarySearch(frames, lastTime);
float frameTime = frames[frame];
while (frame > 0) { // Fire multiple events with the same frame.
if (frames[frame - 1] != frameTime) break;
frame--;
}
}
for (; frame < frameCount && time >= frames[frame]; frame++)
firedEvents.add(events[frame]);
}
}
static public class DrawOrderTimeline implements Timeline {
private final float[] frames; // time, ...
private final int[][] drawOrders;
public DrawOrderTimeline (int frameCount) {
frames = new float[frameCount];
drawOrders = new int[frameCount][];
}
public int getId () {
return TimelineType.drawOrder.ordinal() << 24;
}
public int getFrameCount () {
return frames.length;
}
public float[] getFrames () {
return frames;
}
public int[][] getDrawOrders () {
return drawOrders;
}
/** Sets the time of the specified keyframe.
* @param drawOrder May be null to use bind pose draw order. */
public void setFrame (int frameIndex, float time, int[] drawOrder) {
frames[frameIndex] = time;
drawOrders[frameIndex] = drawOrder;
}
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> firedEvents, float alpha,
boolean setupPose) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
int frame;
if (time >= frames[frames.length - 1]) // Time is after last frame.
frame = frames.length - 1;
else
frame = binarySearch(frames, time) - 1;
Array<Slot> drawOrder = skeleton.drawOrder;
Array<Slot> slots = skeleton.slots;
int[] drawOrderToSetupIndex = drawOrders[frame];
if (drawOrderToSetupIndex == null)
System.arraycopy(slots.items, 0, drawOrder.items, 0, slots.size);
else {
for (int i = 0, n = drawOrderToSetupIndex.length; i < n; i++)
drawOrder.set(i, slots.get(drawOrderToSetupIndex[i]));
}
}
}
static public class IkConstraintTimeline extends CurveTimeline {
static public final int ENTRIES = 3;
static private final int PREV_TIME = -3, PREV_MIX = -2, PREV_BEND_DIRECTION = -1;
@ -764,6 +812,10 @@ public class Animation {
frames = new float[frameCount * ENTRIES];
}
public int getId () {
return (TimelineType.ikConstraint.ordinal() << 24) + ikConstraintIndex;
}
public void setIkConstraintIndex (int index) {
if (index < 0) throw new IllegalArgumentException("index must be >= 0.");
this.ikConstraintIndex = index;
@ -785,7 +837,7 @@ public class Animation {
frames[frameIndex + BEND_DIRECTION] = bendDirection;
}
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha) {
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, boolean setupPose) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
@ -821,6 +873,10 @@ public class Animation {
frames = new float[frameCount * ENTRIES];
}
public int getId () {
return (TimelineType.transformConstraint.ordinal() << 24) + transformConstraintIndex;
}
public void setTransformConstraintIndex (int index) {
if (index < 0) throw new IllegalArgumentException("index must be >= 0.");
this.transformConstraintIndex = index;
@ -844,7 +900,7 @@ public class Animation {
frames[frameIndex + SHEAR] = shearMix;
}
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha) {
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, boolean setupPose) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
@ -890,6 +946,10 @@ public class Animation {
frames = new float[frameCount * ENTRIES];
}
public int getId () {
return (TimelineType.pathConstraintPosition.ordinal() << 24) + pathConstraintIndex;
}
public void setPathConstraintIndex (int index) {
if (index < 0) throw new IllegalArgumentException("index must be >= 0.");
this.pathConstraintIndex = index;
@ -910,7 +970,7 @@ public class Animation {
frames[frameIndex + VALUE] = value;
}
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha) {
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, boolean setupPose) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
@ -937,7 +997,11 @@ public class Animation {
super(frameCount);
}
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha) {
public int getId () {
return (TimelineType.pathConstraintSpacing.ordinal() << 24) + pathConstraintIndex;
}
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, boolean setupPose) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
@ -973,6 +1037,10 @@ public class Animation {
frames = new float[frameCount * ENTRIES];
}
public int getId () {
return (TimelineType.pathConstraintMix.ordinal() << 24) + pathConstraintIndex;
}
public void setPathConstraintIndex (int index) {
if (index < 0) throw new IllegalArgumentException("index must be >= 0.");
this.pathConstraintIndex = index;
@ -994,7 +1062,7 @@ public class Animation {
frames[frameIndex + TRANSLATE] = translateMix;
}
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha) {
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, boolean setupPose) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.

View File

@ -32,7 +32,9 @@
package com.esotericsoftware.spine;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.BooleanArray;
import com.badlogic.gdx.utils.IntArray;
import com.badlogic.gdx.utils.IntSet;
import com.badlogic.gdx.utils.Pool;
import com.badlogic.gdx.utils.Pool.Poolable;
import com.esotericsoftware.spine.Animation.AttachmentTimeline;
@ -51,6 +53,7 @@ public class AnimationState {
}
};
private final EventQueue queue = new EventQueue(listeners, trackEntryPool);
private final IntSet usage = new IntSet();
private float timeScale = 1;
/** Creates an uninitialized AnimationState. The animation state data must be set before use. */
@ -116,12 +119,12 @@ public class AnimationState {
if (current == null) continue;
if (current.delay > 0) continue;
float alpha = current.alpha;
float mix = current.alpha;
if (current.mixingFrom != null) {
alpha *= current.mixTime / current.mixDuration;
applyMixingFrom(current.mixingFrom, skeleton, alpha);
if (alpha >= 1) {
alpha = 1;
mix *= current.mixTime / current.mixDuration;
applyMixingFrom(current.mixingFrom, skeleton, mix);
if (mix >= 1) {
mix = 1;
queue.end(current.mixingFrom);
current.mixingFrom = null;
}
@ -129,8 +132,9 @@ public class AnimationState {
float animationLast = current.animationLast, animationTime = current.getAnimationTime();
Array<Timeline> timelines = current.animation.timelines;
BooleanArray setupPose = current.setupPose;
for (int ii = 0, n = timelines.size; ii < n; ii++)
timelines.get(ii).apply(skeleton, animationLast, animationTime, events, alpha);
timelines.get(ii).apply(skeleton, animationLast, animationTime, events, mix, setupPose.get(ii));
queueEvents(current, animationTime);
current.animationLast = animationTime;
current.trackLast = current.trackTime;
@ -145,16 +149,25 @@ public class AnimationState {
float animationLast = entry.animationLast, animationTime = entry.getAnimationTime();
Array<Timeline> timelines = entry.animation.timelines;
float alpha = entry.alpha;
BooleanArray setupPose = entry.setupPose;
float alphaFull = entry.alpha, alphaMix = entry.alpha * (1 - mix);
if (attachments && drawOrder) {
for (int i = 0, n = timelines.size; i < n; i++)
timelines.get(i).apply(skeleton, animationLast, animationTime, events, alpha);
for (int i = 0, n = timelines.size; i < n; i++) {
Timeline timeline = timelines.get(i);
if (setupPose.get(i))
timeline.apply(skeleton, animationLast, animationTime, events, alphaMix, true);
else
timeline.apply(skeleton, animationLast, animationTime, events, alphaFull, false);
}
} else {
for (int i = 0, n = timelines.size; i < n; i++) {
Timeline timeline = timelines.get(i);
if (!attachments && timeline instanceof AttachmentTimeline) continue;
if (!drawOrder && timeline instanceof DrawOrderTimeline) continue;
timeline.apply(skeleton, animationLast, animationTime, events, alpha);
if (setupPose.get(i))
timeline.apply(skeleton, animationLast, animationTime, events, alphaMix, true);
else
timeline.apply(skeleton, animationLast, animationTime, events, alphaFull, false);
}
}
@ -254,6 +267,58 @@ public class AnimationState {
}
queue.drain();
updateSetupPose();
}
private void updateSetupPose () {
usage.clear();
int i = 0, n = tracks.size;
for (; i < n; i++) {
TrackEntry entry = tracks.get(i);
if (entry == null) continue;
if (entry.mixingFrom != null) {
updateFirstSetupPose(entry.mixingFrom);
updateSetupPose(entry);
} else
updateFirstSetupPose(entry);
break;
}
for (i++; i < n; i++) {
TrackEntry entry = tracks.get(i);
if (entry == null) continue;
if (entry.mixingFrom != null) updateSetupPose(entry.mixingFrom);
updateSetupPose(entry);
}
}
private void updateFirstSetupPose (TrackEntry entry) {
IntSet usage = this.usage;
BooleanArray setupPose = entry.setupPose;
setupPose.clear();
Array<Timeline> timelines = entry.animation.timelines;
for (int ii = 0, nn = timelines.size; ii < nn; ii++) {
Timeline timeline = timelines.get(ii);
usage.add(timeline.getId());
setupPose.add(true);
}
}
private void updateSetupPose (TrackEntry entry) {
IntSet usage = this.usage;
BooleanArray setupPose = entry.setupPose;
setupPose.clear();
Array<Timeline> timelines = entry.animation.timelines;
for (int ii = 0, nn = timelines.size; ii < nn; ii++) {
Timeline timeline = timelines.get(ii);
int id = timeline.getId();
if (usage.contains(id))
setupPose.add(false);
else {
usage.add(id);
setupPose.add(true);
}
}
}
/** @see #setAnimation(int, Animation, boolean) */
@ -426,12 +491,14 @@ public class AnimationState {
float delay, trackTime, trackLast, trackEnd, animationStart, animationEnd, animationLast, timeScale;
float alpha;
float mixTime, mixDuration;
final BooleanArray setupPose = new BooleanArray();
public void reset () {
next = null;
mixingFrom = null;
animation = null;
listener = null;
setupPose.clear();
}
public int getTrackIndex () {

View File

@ -264,8 +264,7 @@ public class SkeletonViewer extends ApplicationAdapter {
ShapeRenderer shapes = debugRenderer.getShapeRenderer();
TrackEntry entry = state.getCurrent(0);
if (entry != null) {
float percent = entry.getTrackTime() / entry.getTrackEnd();
if (entry.getLoop()) percent %= 1;
float percent = entry.getAnimationTime() / entry.getAnimationEnd();
float x = ui.window.getRight() + (Gdx.graphics.getWidth() - ui.window.getRight()) * percent;
shapes.setColor(Color.CYAN);
shapes.begin(ShapeType.Line);