Separate path timelines.

This commit is contained in:
NathanSweet 2016-06-07 13:15:08 +02:00
parent b41dead263
commit b3f84390c5
5 changed files with 193 additions and 54 deletions

View File

@ -866,17 +866,16 @@ public class Animation {
} }
} }
// BOZO! - Separate into multiple timelines. static public class PathConstraintPositionTimeline extends CurveTimeline {
static public class PathConstraintTimeline extends CurveTimeline { static public final int ENTRIES = 2;
static public final int ENTRIES = 5; static final int PREV_TIME = -2, PREV_VALUE = -1;
static private final int PREV_TIME = -5, PREV_POSITION = -4, PREV_ROTATE = -3, PREV_TRANSLATE = -2; static final int VALUE = 1;
static private final int POSITION = 1, ROTATE = 2, TRANSLATE = 3;
int pathConstraintIndex; int pathConstraintIndex;
private final float[] frames; // time, rotate mix, translate mix, scale mix, shear mix, ... final float[] frames; // time, position, ...
public PathConstraintTimeline (int frameCount) { public PathConstraintPositionTimeline (int frameCount) {
super(frameCount); super(frameCount);
frames = new float[frameCount * ENTRIES]; frames = new float[frameCount * ENTRIES];
} }
@ -894,11 +893,93 @@ public class Animation {
return frames; return frames;
} }
/** Sets the time, position, and mixes of the specified keyframe. */ /** Sets the time and value of the specified keyframe. */
public void setFrame (int frameIndex, float time, float position, float rotateMix, float translateMix) { public void setFrame (int frameIndex, float time, float value) {
frameIndex *= ENTRIES;
frames[frameIndex] = time;
frames[frameIndex + VALUE] = value;
}
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
PathConstraint constraint = skeleton.pathConstraints.get(pathConstraintIndex);
if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame.
int i = frames.length;
constraint.position += (frames[i + PREV_VALUE] - constraint.position) * alpha;
return;
}
// Interpolate between the previous frame and the current frame.
int frame = binarySearch(frames, time, ENTRIES);
float position = frames[frame + PREV_VALUE];
float frameTime = frames[frame];
float percent = getCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
constraint.position += (position + (frames[frame + VALUE] - position) * percent - constraint.position) * alpha;
}
}
static public class PathConstraintSpacingTimeline extends PathConstraintPositionTimeline {
public PathConstraintSpacingTimeline (int frameCount) {
super(frameCount);
}
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
PathConstraint constraint = skeleton.pathConstraints.get(pathConstraintIndex);
if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame.
int i = frames.length;
constraint.spacing += (frames[i + PREV_VALUE] - constraint.spacing) * alpha;
return;
}
// Interpolate between the previous frame and the current frame.
int frame = binarySearch(frames, time, ENTRIES);
float spacing = frames[frame + PREV_VALUE];
float frameTime = frames[frame];
float percent = getCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
constraint.spacing += (spacing + (frames[frame + VALUE] - spacing) * percent - constraint.spacing) * alpha;
}
}
static public class PathConstraintMixTimeline extends CurveTimeline {
static public final int ENTRIES = 3;
static private final int PREV_TIME = -3, PREV_ROTATE = -2, PREV_TRANSLATE = -1;
static private final int ROTATE = 1, TRANSLATE = 2;
int pathConstraintIndex;
private final float[] frames; // time, rotate mix, translate mix, ...
public PathConstraintMixTimeline (int frameCount) {
super(frameCount);
frames = new float[frameCount * ENTRIES];
}
public void setPathConstraintIndex (int index) {
if (index < 0) throw new IllegalArgumentException("index must be >= 0.");
this.pathConstraintIndex = index;
}
public int getPathConstraintIndex () {
return pathConstraintIndex;
}
public float[] getFrames () {
return frames;
}
/** Sets the time and mixes of the specified keyframe. */
public void setFrame (int frameIndex, float time, float rotateMix, float translateMix) {
frameIndex *= ENTRIES; frameIndex *= ENTRIES;
frames[frameIndex] = time; frames[frameIndex] = time;
frames[frameIndex + POSITION] = position;
frames[frameIndex + ROTATE] = rotateMix; frames[frameIndex + ROTATE] = rotateMix;
frames[frameIndex + TRANSLATE] = translateMix; frames[frameIndex + TRANSLATE] = translateMix;
} }
@ -911,7 +992,6 @@ public class Animation {
if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame. if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame.
int i = frames.length; int i = frames.length;
constraint.position += (frames[i + PREV_POSITION] - constraint.position) * alpha;
constraint.rotateMix += (frames[i + PREV_ROTATE] - constraint.rotateMix) * alpha; constraint.rotateMix += (frames[i + PREV_ROTATE] - constraint.rotateMix) * alpha;
constraint.translateMix += (frames[i + PREV_TRANSLATE] - constraint.translateMix) * alpha; constraint.translateMix += (frames[i + PREV_TRANSLATE] - constraint.translateMix) * alpha;
return; return;
@ -919,13 +999,11 @@ public class Animation {
// Interpolate between the previous frame and the current frame. // Interpolate between the previous frame and the current frame.
int frame = binarySearch(frames, time, ENTRIES); int frame = binarySearch(frames, time, ENTRIES);
float position = frames[frame + PREV_POSITION];
float rotate = frames[frame + PREV_ROTATE]; float rotate = frames[frame + PREV_ROTATE];
float translate = frames[frame + PREV_TRANSLATE]; float translate = frames[frame + PREV_TRANSLATE];
float frameTime = frames[frame]; float frameTime = frames[frame];
float percent = getCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); float percent = getCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
constraint.position += (position + (frames[frame + POSITION] - position) * percent - constraint.position) * alpha;
constraint.rotateMix += (rotate + (frames[frame + ROTATE] - rotate) * percent - constraint.rotateMix) * alpha; constraint.rotateMix += (rotate + (frames[frame + ROTATE] - rotate) * percent - constraint.rotateMix) * alpha;
constraint.translateMix += (translate + (frames[frame + TRANSLATE] - translate) * percent - constraint.translateMix) constraint.translateMix += (translate + (frames[frame + TRANSLATE] - translate) * percent - constraint.translateMix)
* alpha; * alpha;

View File

@ -144,7 +144,7 @@ public class PathConstraint implements Updatable {
Slot target = this.target; Slot target = this.target;
float position = this.position; float position = this.position;
int verticesLength = path.getWorldVerticesLength(), curveCount = verticesLength / 6, lastCurve = NONE; int verticesLength = path.getWorldVerticesLength(), curveCount = verticesLength / 6, lastCurve = NONE;
float[] spaces = this.spaces.items, out = this.positions.setSize(spacesCount * 3), world; float[] spaces = this.spaces.items, out = this.positions.setSize(spacesCount * 3 + 2), world;
boolean closed = path.getClosed(); boolean closed = path.getClosed();
if (!path.getConstantSpeed()) { if (!path.getConstantSpeed()) {

View File

@ -48,7 +48,9 @@ import com.esotericsoftware.spine.Animation.DeformTimeline;
import com.esotericsoftware.spine.Animation.DrawOrderTimeline; import com.esotericsoftware.spine.Animation.DrawOrderTimeline;
import com.esotericsoftware.spine.Animation.EventTimeline; import com.esotericsoftware.spine.Animation.EventTimeline;
import com.esotericsoftware.spine.Animation.IkConstraintTimeline; import com.esotericsoftware.spine.Animation.IkConstraintTimeline;
import com.esotericsoftware.spine.Animation.PathConstraintTimeline; import com.esotericsoftware.spine.Animation.PathConstraintMixTimeline;
import com.esotericsoftware.spine.Animation.PathConstraintPositionTimeline;
import com.esotericsoftware.spine.Animation.PathConstraintSpacingTimeline;
import com.esotericsoftware.spine.Animation.RotateTimeline; import com.esotericsoftware.spine.Animation.RotateTimeline;
import com.esotericsoftware.spine.Animation.ScaleTimeline; import com.esotericsoftware.spine.Animation.ScaleTimeline;
import com.esotericsoftware.spine.Animation.ShearTimeline; import com.esotericsoftware.spine.Animation.ShearTimeline;
@ -69,12 +71,17 @@ import com.esotericsoftware.spine.attachments.RegionAttachment;
import com.esotericsoftware.spine.attachments.VertexAttachment; import com.esotericsoftware.spine.attachments.VertexAttachment;
public class SkeletonBinary { public class SkeletonBinary {
static public final int TIMELINE_ROTATE = 0; static public final int BONE_ROTATE = 0;
static public final int TIMELINE_TRANSLATE = 1; static public final int BONE_TRANSLATE = 1;
static public final int TIMELINE_SCALE = 2; static public final int BONE_SCALE = 2;
static public final int TIMELINE_SHEAR = 3; static public final int BONE_SHEAR = 3;
static public final int TIMELINE_ATTACHMENT = 4;
static public final int TIMELINE_COLOR = 5; static public final int SLOT_ATTACHMENT = 0;
static public final int SLOT_COLOR = 1;
static public final int PATH_POSITION = 0;
static public final int PATH_SPACING = 1;
static public final int PATH_MIX = 2;
static public final int CURVE_LINEAR = 0; static public final int CURVE_LINEAR = 0;
static public final int CURVE_STEPPED = 1; static public final int CURVE_STEPPED = 1;
@ -488,7 +495,7 @@ public class SkeletonBinary {
int timelineType = input.readByte(); int timelineType = input.readByte();
int frameCount = input.readInt(true); int frameCount = input.readInt(true);
switch (timelineType) { switch (timelineType) {
case TIMELINE_COLOR: { case SLOT_COLOR: {
ColorTimeline timeline = new ColorTimeline(frameCount); ColorTimeline timeline = new ColorTimeline(frameCount);
timeline.slotIndex = slotIndex; timeline.slotIndex = slotIndex;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
@ -501,7 +508,7 @@ public class SkeletonBinary {
duration = Math.max(duration, timeline.getFrames()[(frameCount - 1) * ColorTimeline.ENTRIES]); duration = Math.max(duration, timeline.getFrames()[(frameCount - 1) * ColorTimeline.ENTRIES]);
break; break;
} }
case TIMELINE_ATTACHMENT: case SLOT_ATTACHMENT:
AttachmentTimeline timeline = new AttachmentTimeline(frameCount); AttachmentTimeline timeline = new AttachmentTimeline(frameCount);
timeline.slotIndex = slotIndex; timeline.slotIndex = slotIndex;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) for (int frameIndex = 0; frameIndex < frameCount; frameIndex++)
@ -520,7 +527,7 @@ public class SkeletonBinary {
int timelineType = input.readByte(); int timelineType = input.readByte();
int frameCount = input.readInt(true); int frameCount = input.readInt(true);
switch (timelineType) { switch (timelineType) {
case TIMELINE_ROTATE: { case BONE_ROTATE: {
RotateTimeline timeline = new RotateTimeline(frameCount); RotateTimeline timeline = new RotateTimeline(frameCount);
timeline.boneIndex = boneIndex; timeline.boneIndex = boneIndex;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
@ -531,14 +538,14 @@ public class SkeletonBinary {
duration = Math.max(duration, timeline.getFrames()[(frameCount - 1) * RotateTimeline.ENTRIES]); duration = Math.max(duration, timeline.getFrames()[(frameCount - 1) * RotateTimeline.ENTRIES]);
break; break;
} }
case TIMELINE_TRANSLATE: case BONE_TRANSLATE:
case TIMELINE_SCALE: case BONE_SCALE:
case TIMELINE_SHEAR: { case BONE_SHEAR: {
TranslateTimeline timeline; TranslateTimeline timeline;
float timelineScale = 1; float timelineScale = 1;
if (timelineType == TIMELINE_SCALE) if (timelineType == BONE_SCALE)
timeline = new ScaleTimeline(frameCount); timeline = new ScaleTimeline(frameCount);
else if (timelineType == TIMELINE_SHEAR) else if (timelineType == BONE_SHEAR)
timeline = new ShearTimeline(frameCount); timeline = new ShearTimeline(frameCount);
else { else {
timeline = new TranslateTimeline(frameCount); timeline = new TranslateTimeline(frameCount);
@ -590,15 +597,37 @@ public class SkeletonBinary {
// Path constraint timelines. // Path constraint timelines.
for (int i = 0, n = input.readInt(true); i < n; i++) { for (int i = 0, n = input.readInt(true); i < n; i++) {
int index = input.readInt(true); int index = input.readInt(true);
int frameCount = input.readInt(true); for (int ii = 0, nn = input.readInt(true); ii < nn; ii++) {
PathConstraintTimeline timeline = new PathConstraintTimeline(frameCount); int timelineType = input.readByte();
timeline.pathConstraintIndex = index; int frameCount = input.readInt(true);
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { switch (timelineType) {
timeline.setFrame(frameIndex, input.readFloat(), input.readFloat(), input.readFloat(), input.readFloat()); case PATH_POSITION:
if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline); case PATH_SPACING: {
PathConstraintPositionTimeline timeline;
if (timelineType == PATH_SPACING)
timeline = new PathConstraintSpacingTimeline(frameCount);
else
timeline = new PathConstraintPositionTimeline(frameCount);
timeline.pathConstraintIndex = index;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
timeline.setFrame(frameIndex, input.readFloat(), input.readFloat());
if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline);
}
timelines.add(timeline);
duration = Math.max(duration, timeline.getFrames()[(frameCount - 1) * PathConstraintPositionTimeline.ENTRIES]);
}
case PATH_MIX: {
PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(frameCount);
timeline.pathConstraintIndex = index;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
timeline.setFrame(frameIndex, input.readFloat(), input.readFloat(), input.readFloat());
if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline);
}
timelines.add(timeline);
duration = Math.max(duration, timeline.getFrames()[(frameCount - 1) * PathConstraintMixTimeline.ENTRIES]);
}
}
} }
timelines.add(timeline);
duration = Math.max(duration, timeline.getFrames()[(frameCount - 1) * PathConstraintTimeline.ENTRIES]);
} }
// Deform timelines. // Deform timelines.

View File

@ -90,7 +90,7 @@ public class SkeletonData {
return null; return null;
} }
/** @return -1 if the bone was not found. */ /** @return -1 if the slot was not found. */
public int findSlotIndex (String slotName) { public int findSlotIndex (String slotName) {
if (slotName == null) throw new IllegalArgumentException("slotName cannot be null."); if (slotName == null) throw new IllegalArgumentException("slotName cannot be null.");
Array<SlotData> slots = this.slots; Array<SlotData> slots = this.slots;
@ -206,6 +206,15 @@ public class SkeletonData {
return null; return null;
} }
/** @return -1 if the path constraint was not found. */
public int findPathConstraintIndex (String pathConstraintName) {
if (pathConstraintName == null) throw new IllegalArgumentException("pathConstraintName cannot be null.");
Array<PathConstraintData> pathConstraints = this.pathConstraints;
for (int i = 0, n = pathConstraints.size; i < n; i++)
if (pathConstraints.get(i).name.equals(pathConstraintName)) return i;
return -1;
}
// --- // ---
/** @return May be null. */ /** @return May be null. */

View File

@ -47,7 +47,9 @@ import com.esotericsoftware.spine.Animation.DeformTimeline;
import com.esotericsoftware.spine.Animation.DrawOrderTimeline; import com.esotericsoftware.spine.Animation.DrawOrderTimeline;
import com.esotericsoftware.spine.Animation.EventTimeline; import com.esotericsoftware.spine.Animation.EventTimeline;
import com.esotericsoftware.spine.Animation.IkConstraintTimeline; import com.esotericsoftware.spine.Animation.IkConstraintTimeline;
import com.esotericsoftware.spine.Animation.PathConstraintTimeline; import com.esotericsoftware.spine.Animation.PathConstraintMixTimeline;
import com.esotericsoftware.spine.Animation.PathConstraintPositionTimeline;
import com.esotericsoftware.spine.Animation.PathConstraintSpacingTimeline;
import com.esotericsoftware.spine.Animation.RotateTimeline; import com.esotericsoftware.spine.Animation.RotateTimeline;
import com.esotericsoftware.spine.Animation.ScaleTimeline; import com.esotericsoftware.spine.Animation.ScaleTimeline;
import com.esotericsoftware.spine.Animation.ShearTimeline; import com.esotericsoftware.spine.Animation.ShearTimeline;
@ -280,7 +282,7 @@ public class SkeletonJson {
String type = map.getString("type", AttachmentType.region.name()); String type = map.getString("type", AttachmentType.region.name());
// Warning: These types are deprecated and will be removed in the near future. // BOZO - Warning: These types are deprecated and will be removed in the near future.
if (type.equals("skinnedmesh")) type = "weightedmesh"; if (type.equals("skinnedmesh")) type = "weightedmesh";
if (type.equals("weightedmesh")) type = "mesh"; if (type.equals("weightedmesh")) type = "mesh";
if (type.equals("weightedlinkedmesh")) type = "linkedmesh"; if (type.equals("weightedlinkedmesh")) type = "linkedmesh";
@ -393,7 +395,6 @@ public class SkeletonJson {
for (JsonValue slotMap = map.getChild("slots"); slotMap != null; slotMap = slotMap.next) { for (JsonValue slotMap = map.getChild("slots"); slotMap != null; slotMap = slotMap.next) {
int slotIndex = skeletonData.findSlotIndex(slotMap.name); int slotIndex = skeletonData.findSlotIndex(slotMap.name);
if (slotIndex == -1) throw new SerializationException("Slot not found: " + slotMap.name); if (slotIndex == -1) throw new SerializationException("Slot not found: " + slotMap.name);
for (JsonValue timelineMap = slotMap.child; timelineMap != null; timelineMap = timelineMap.next) { for (JsonValue timelineMap = slotMap.child; timelineMap != null; timelineMap = timelineMap.next) {
String timelineName = timelineMap.name; String timelineName = timelineMap.name;
if (timelineName.equals("color")) { if (timelineName.equals("color")) {
@ -428,7 +429,6 @@ public class SkeletonJson {
for (JsonValue boneMap = map.getChild("bones"); boneMap != null; boneMap = boneMap.next) { for (JsonValue boneMap = map.getChild("bones"); boneMap != null; boneMap = boneMap.next) {
int boneIndex = skeletonData.findBoneIndex(boneMap.name); int boneIndex = skeletonData.findBoneIndex(boneMap.name);
if (boneIndex == -1) throw new SerializationException("Bone not found: " + boneMap.name); if (boneIndex == -1) throw new SerializationException("Bone not found: " + boneMap.name);
for (JsonValue timelineMap = boneMap.child; timelineMap != null; timelineMap = timelineMap.next) { for (JsonValue timelineMap = boneMap.child; timelineMap != null; timelineMap = timelineMap.next) {
String timelineName = timelineMap.name; String timelineName = timelineMap.name;
if (timelineName.equals("rotate")) { if (timelineName.equals("rotate")) {
@ -506,19 +506,42 @@ public class SkeletonJson {
} }
// Path constraint timelines. // Path constraint timelines.
for (JsonValue constraintMap = map.getChild("path"); constraintMap != null; constraintMap = constraintMap.next) { for (JsonValue constraintMap = map.getChild("paths"); constraintMap != null; constraintMap = constraintMap.next) {
PathConstraintData constraint = skeletonData.findPathConstraint(constraintMap.name); int index = skeletonData.findPathConstraintIndex(constraintMap.name);
PathConstraintTimeline timeline = new PathConstraintTimeline(constraintMap.size); if (index == -1) throw new SerializationException("Path constraint not found: " + constraintMap.name);
timeline.pathConstraintIndex = skeletonData.getPathConstraints().indexOf(constraint, true); for (JsonValue timelineMap = constraintMap.child; timelineMap != null; timelineMap = timelineMap.next) {
int frameIndex = 0; String timelineName = timelineMap.name;
for (JsonValue valueMap = constraintMap.child; valueMap != null; valueMap = valueMap.next) { if (timelineName.equals("position") || timelineName.equals("spacing")) {
timeline.setFrame(frameIndex, valueMap.getFloat("time"), valueMap.getFloat("scaleMix", 1), PathConstraintPositionTimeline timeline;
valueMap.getFloat("rotateMix", 1), valueMap.getFloat("translateMix", 1)); if (timelineName.equals("spacing"))
readCurve(valueMap, timeline, frameIndex); timeline = new PathConstraintSpacingTimeline(timelineMap.size);
frameIndex++; else
timeline = new PathConstraintPositionTimeline(timelineMap.size);
timeline.pathConstraintIndex = index;
int frameIndex = 0;
for (JsonValue valueMap = constraintMap.child; valueMap != null; valueMap = valueMap.next) {
timeline.setFrame(frameIndex, valueMap.getFloat("time"), valueMap.getFloat(timelineName, 1));
readCurve(valueMap, timeline, frameIndex);
frameIndex++;
}
timelines.add(timeline);
duration = Math.max(duration,
timeline.getFrames()[(timeline.getFrameCount() - 1) * PathConstraintPositionTimeline.ENTRIES]);
} else if (timelineName.equals("mix")) {
PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(timelineMap.size);
timeline.pathConstraintIndex = index;
int frameIndex = 0;
for (JsonValue valueMap = constraintMap.child; valueMap != null; valueMap = valueMap.next) {
timeline.setFrame(frameIndex, valueMap.getFloat("time"), valueMap.getFloat("rotateMix", 1),
valueMap.getFloat("translateMix", 1));
readCurve(valueMap, timeline, frameIndex);
frameIndex++;
}
timelines.add(timeline);
duration = Math.max(duration,
timeline.getFrames()[(timeline.getFrameCount() - 1) * PathConstraintMixTimeline.ENTRIES]);
}
} }
timelines.add(timeline);
duration = Math.max(duration, timeline.getFrames()[(timeline.getFrameCount() - 1) * PathConstraintTimeline.ENTRIES]);
} }
// Deform timelines. // Deform timelines.