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 PathConstraintTimeline extends CurveTimeline {
static public final int ENTRIES = 5;
static private final int PREV_TIME = -5, PREV_POSITION = -4, PREV_ROTATE = -3, PREV_TRANSLATE = -2;
static private final int POSITION = 1, ROTATE = 2, TRANSLATE = 3;
static public class PathConstraintPositionTimeline extends CurveTimeline {
static public final int ENTRIES = 2;
static final int PREV_TIME = -2, PREV_VALUE = -1;
static final int VALUE = 1;
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);
frames = new float[frameCount * ENTRIES];
}
@ -894,11 +893,93 @@ public class Animation {
return frames;
}
/** Sets the time, position, and mixes of the specified keyframe. */
public void setFrame (int frameIndex, float time, float position, float rotateMix, float translateMix) {
/** Sets the time and value of the specified keyframe. */
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;
frames[frameIndex] = time;
frames[frameIndex + POSITION] = position;
frames[frameIndex + ROTATE] = rotateMix;
frames[frameIndex + TRANSLATE] = translateMix;
}
@ -911,7 +992,6 @@ public class Animation {
if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame.
int i = frames.length;
constraint.position += (frames[i + PREV_POSITION] - constraint.position) * alpha;
constraint.rotateMix += (frames[i + PREV_ROTATE] - constraint.rotateMix) * alpha;
constraint.translateMix += (frames[i + PREV_TRANSLATE] - constraint.translateMix) * alpha;
return;
@ -919,13 +999,11 @@ public class Animation {
// Interpolate between the previous frame and the current frame.
int frame = binarySearch(frames, time, ENTRIES);
float position = frames[frame + PREV_POSITION];
float rotate = frames[frame + PREV_ROTATE];
float translate = frames[frame + PREV_TRANSLATE];
float frameTime = frames[frame];
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.translateMix += (translate + (frames[frame + TRANSLATE] - translate) * percent - constraint.translateMix)
* alpha;

View File

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

View File

@ -90,7 +90,7 @@ public class SkeletonData {
return null;
}
/** @return -1 if the bone was not found. */
/** @return -1 if the slot was not found. */
public int findSlotIndex (String slotName) {
if (slotName == null) throw new IllegalArgumentException("slotName cannot be null.");
Array<SlotData> slots = this.slots;
@ -206,6 +206,15 @@ public class SkeletonData {
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. */

View File

@ -47,7 +47,9 @@ import com.esotericsoftware.spine.Animation.DeformTimeline;
import com.esotericsoftware.spine.Animation.DrawOrderTimeline;
import com.esotericsoftware.spine.Animation.EventTimeline;
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.ScaleTimeline;
import com.esotericsoftware.spine.Animation.ShearTimeline;
@ -280,7 +282,7 @@ public class SkeletonJson {
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("weightedmesh")) type = "mesh";
if (type.equals("weightedlinkedmesh")) type = "linkedmesh";
@ -393,7 +395,6 @@ public class SkeletonJson {
for (JsonValue slotMap = map.getChild("slots"); slotMap != null; slotMap = slotMap.next) {
int slotIndex = skeletonData.findSlotIndex(slotMap.name);
if (slotIndex == -1) throw new SerializationException("Slot not found: " + slotMap.name);
for (JsonValue timelineMap = slotMap.child; timelineMap != null; timelineMap = timelineMap.next) {
String timelineName = timelineMap.name;
if (timelineName.equals("color")) {
@ -428,7 +429,6 @@ public class SkeletonJson {
for (JsonValue boneMap = map.getChild("bones"); boneMap != null; boneMap = boneMap.next) {
int boneIndex = skeletonData.findBoneIndex(boneMap.name);
if (boneIndex == -1) throw new SerializationException("Bone not found: " + boneMap.name);
for (JsonValue timelineMap = boneMap.child; timelineMap != null; timelineMap = timelineMap.next) {
String timelineName = timelineMap.name;
if (timelineName.equals("rotate")) {
@ -506,19 +506,42 @@ public class SkeletonJson {
}
// Path constraint timelines.
for (JsonValue constraintMap = map.getChild("path"); constraintMap != null; constraintMap = constraintMap.next) {
PathConstraintData constraint = skeletonData.findPathConstraint(constraintMap.name);
PathConstraintTimeline timeline = new PathConstraintTimeline(constraintMap.size);
timeline.pathConstraintIndex = skeletonData.getPathConstraints().indexOf(constraint, true);
int frameIndex = 0;
for (JsonValue valueMap = constraintMap.child; valueMap != null; valueMap = valueMap.next) {
timeline.setFrame(frameIndex, valueMap.getFloat("time"), valueMap.getFloat("scaleMix", 1),
valueMap.getFloat("rotateMix", 1), valueMap.getFloat("translateMix", 1));
readCurve(valueMap, timeline, frameIndex);
frameIndex++;
for (JsonValue constraintMap = map.getChild("paths"); constraintMap != null; constraintMap = constraintMap.next) {
int index = skeletonData.findPathConstraintIndex(constraintMap.name);
if (index == -1) throw new SerializationException("Path constraint not found: " + constraintMap.name);
for (JsonValue timelineMap = constraintMap.child; timelineMap != null; timelineMap = timelineMap.next) {
String timelineName = timelineMap.name;
if (timelineName.equals("position") || timelineName.equals("spacing")) {
PathConstraintPositionTimeline timeline;
if (timelineName.equals("spacing"))
timeline = new PathConstraintSpacingTimeline(timelineMap.size);
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.