Path constraint refactoring + position mode.

This commit is contained in:
NathanSweet 2016-06-06 17:29:44 +02:00
parent 2bb702e2be
commit b41dead263
4 changed files with 120 additions and 129 deletions

View File

@ -5,20 +5,23 @@ import static com.badlogic.gdx.math.MathUtils.*;
import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.FloatArray; import com.badlogic.gdx.utils.FloatArray;
import com.esotericsoftware.spine.PathConstraintData.PositionMode;
import com.esotericsoftware.spine.PathConstraintData.RotateMode;
import com.esotericsoftware.spine.PathConstraintData.SpacingMode;
import com.esotericsoftware.spine.attachments.Attachment; import com.esotericsoftware.spine.attachments.Attachment;
import com.esotericsoftware.spine.attachments.PathAttachment; import com.esotericsoftware.spine.attachments.PathAttachment;
public class PathConstraint implements Updatable { public class PathConstraint implements Updatable {
static private final int NONE = -1; static private final int NONE = -1, BEFORE = -2, AFTER = -3;
static private final int BEFORE = -2;
static private final int AFTER = -3;
final PathConstraintData data; final PathConstraintData data;
final Array<Bone> bones; final Array<Bone> bones;
Slot target; Slot target;
float position, spacing, rotateMix, translateMix; float position, spacing, rotateMix, translateMix;
FloatArray spaces = new FloatArray(), positions = new FloatArray(), temp = new FloatArray(); final FloatArray spaces = new FloatArray(), positions = new FloatArray();
final FloatArray world = new FloatArray(), curves = new FloatArray(), lengths = new FloatArray();
final float[] segments = new float[10];
public PathConstraint (PathConstraintData data, Skeleton skeleton) { public PathConstraint (PathConstraintData data, Skeleton skeleton) {
if (data == null) throw new IllegalArgumentException("data cannot be null."); if (data == null) throw new IllegalArgumentException("data cannot be null.");
@ -53,6 +56,7 @@ public class PathConstraint implements Updatable {
update(); update();
} }
@SuppressWarnings("null")
public void update () { public void update () {
Attachment attachment = target.getAttachment(); Attachment attachment = target.getAttachment();
if (!(attachment instanceof PathAttachment)) return; if (!(attachment instanceof PathAttachment)) return;
@ -61,70 +65,43 @@ public class PathConstraint implements Updatable {
boolean translate = translateMix > 0, rotate = rotateMix > 0; boolean translate = translateMix > 0, rotate = rotateMix > 0;
if (!translate && !rotate) return; if (!translate && !rotate) return;
PathAttachment path = (PathAttachment)attachment;
PathConstraintData data = this.data; PathConstraintData data = this.data;
Object[] bones = this.bones.items;
int boneCount = this.bones.size;
SpacingMode spacingMode = data.spacingMode; SpacingMode spacingMode = data.spacingMode;
RotateMode rotateMode = data.rotateMode; RotateMode rotateMode = data.rotateMode;
float offsetRotation = data.offsetRotation; boolean tangents = rotateMode == RotateMode.tangent, scale = rotateMode == RotateMode.chainScale;
FloatArray spacesArray = this.spaces; boolean lengthSpacing = spacingMode == SpacingMode.length;
spacesArray.clear(); int spacesCount = tangents ? boneCount : boneCount + 1;
spacesArray.add(0); float[] spaces = this.spaces.setSize(spacesCount), lengths = null;
Array<Bone> bones = this.bones;
int boneCount = bones.size;
if (boneCount == 1) {
float[] positions = computeWorldPositions(path, rotate, spacingMode == SpacingMode.percent);
Bone bone = bones.first();
bone.worldX += (positions[0] - bone.worldX) * translateMix;
bone.worldY += (positions[1] - bone.worldY) * translateMix;
if (rotate) {
float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
float r = positions[2] - atan2(c, a) + offsetRotation * degRad;
if (r > PI)
r -= PI2;
else if (r < -PI) r += PI2;
r *= rotateMix;
float cos = cos(r), sin = sin(r);
bone.a = cos * a - sin * c;
bone.b = cos * b - sin * d;
bone.c = sin * a + cos * c;
bone.d = sin * b + cos * d;
}
return;
}
float[] spaces;
float spacing = this.spacing; float spacing = this.spacing;
boolean scale = rotateMode == RotateMode.chainScale, lengthMode = spacingMode == SpacingMode.length; if (scale || lengthSpacing) {
if (scale || lengthMode) { if (scale) lengths = this.lengths.setSize(boneCount);
spaces = spacesArray.setSize(1 + boneCount * 2);
for (int i = 0; i < boneCount;) { for (int i = 0; i < boneCount;) {
Bone bone = bones.get(i++); Bone bone = (Bone)bones[i];
float length = bone.data.length, x = length * bone.a, y = length * bone.c; float length = bone.data.length, x = length * bone.a, y = length * bone.c;
length = (float)Math.sqrt(x * x + y * y); length = (float)Math.sqrt(x * x + y * y);
spaces[i] = lengthMode ? Math.max(0, length + spacing) : spacing; if (scale) lengths[i] = length;
spaces[i + boneCount] = length; spaces[++i] = lengthSpacing ? Math.max(0, length + spacing) : spacing;
} }
} else { } else {
spaces = spacesArray.setSize(1 + boneCount); for (int i = 1; i <= spacesCount; i++)
for (int i = 1; i <= boneCount; i++)
spaces[i] = spacing; spaces[i] = spacing;
} }
boolean tangents = rotateMode == RotateMode.tangent; float[] positions = computeWorldPositions((PathAttachment)attachment, spacesCount, tangents,
float[] positions = computeWorldPositions(path, tangents, spacingMode == SpacingMode.percent); data.positionMode == PositionMode.percent, spacingMode == SpacingMode.percent);
float boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation;
float boneX = positions[0], boneY = positions[1];
boolean tip = rotateMode == RotateMode.chain && offsetRotation == 0; boolean tip = rotateMode == RotateMode.chain && offsetRotation == 0;
for (int i = 0, p = 3; i < boneCount; p += 3) { for (int i = 0, p = 3; i < boneCount; i++, p += 3) {
Bone bone = bones.get(i++); Bone bone = (Bone)bones[i];
bone.worldX += (boneX - bone.worldX) * translateMix; bone.worldX += (boneX - bone.worldX) * translateMix;
bone.worldY += (boneY - bone.worldY) * translateMix; bone.worldY += (boneY - bone.worldY) * translateMix;
float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY; float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY;
if (scale) { if (scale) {
float space = spaces[i + boneCount]; float length = lengths[i];
if (space != 0) { if (length != 0) {
float s = ((float)Math.sqrt(dx * dx + dy * dy) / space - 1) * rotateMix + 1; float s = ((float)Math.sqrt(dx * dx + dy * dy) / length - 1) * rotateMix + 1;
bone.a *= s; bone.a *= s;
bone.c *= s; bone.c *= s;
} }
@ -135,7 +112,7 @@ public class PathConstraint implements Updatable {
float a = bone.a, b = bone.b, c = bone.c, d = bone.d, r, cos, sin; float a = bone.a, b = bone.b, c = bone.c, d = bone.d, r, cos, sin;
if (tangents) if (tangents)
r = positions[p - 1]; r = positions[p - 1];
else if (spaces[i] == 0) else if (spaces[i + 1] == 0)
r = positions[p + 2]; r = positions[p + 2];
else else
r = atan2(dy, dx); r = atan2(dy, dx);
@ -162,24 +139,24 @@ public class PathConstraint implements Updatable {
} }
} }
private float[] computeWorldPositions (PathAttachment path, boolean tangents, boolean percentSpacing) { private float[] computeWorldPositions (PathAttachment path, int spacesCount, boolean tangents, boolean percentPosition,
boolean percentSpacing) {
Slot target = this.target; Slot target = this.target;
float position = this.position; float position = this.position;
int verticesLength = path.getWorldVerticesLength(), curves = verticesLength / 6, lastCurve = NONE; int verticesLength = path.getWorldVerticesLength(), curveCount = verticesLength / 6, lastCurve = NONE;
int spacesCount = spaces.size; float[] spaces = this.spaces.items, out = this.positions.setSize(spacesCount * 3), world;
float[] spaces = this.spaces.items, out = this.positions.setSize(spacesCount * 3), temp;
boolean closed = path.getClosed(); boolean closed = path.getClosed();
if (!path.getConstantSpeed()) { if (!path.getConstantSpeed()) {
float pathLength = path.getTotalLength(); float pathLength = path.getTotalLength();
position *= pathLength; if (percentPosition) position *= pathLength;
if (percentSpacing) { if (percentSpacing) {
for (int i = 0; i < spacesCount; i++) for (int i = 0; i < spacesCount; i++)
spaces[i] *= pathLength; spaces[i] *= pathLength;
} }
curves--; curveCount--;
float[] curveLengths = path.getCurveLengths().items; float[] curveLengths = path.getCurveLengths().items;
temp = this.temp.setSize(8); world = this.world.setSize(8);
for (int i = 0, o = 0, curve = 0; i < spacesCount; i++, o += 3) { for (int i = 0, o = 0, curve = 0; i < spacesCount; i++, o += 3) {
float space = spaces[i]; float space = spaces[i];
position += space; position += space;
@ -192,16 +169,16 @@ public class PathConstraint implements Updatable {
} else if (p < 0) { } else if (p < 0) {
if (lastCurve != BEFORE) { if (lastCurve != BEFORE) {
lastCurve = BEFORE; lastCurve = BEFORE;
path.computeWorldVertices(target, 2, 4, temp, 0); path.computeWorldVertices(target, 2, 4, world, 0);
} }
addBeforePosition(p, temp, 0, out, o); addBeforePosition(p, world, 0, out, o);
continue; continue;
} else if (p > pathLength) { } else if (p > pathLength) {
if (lastCurve != AFTER) { if (lastCurve != AFTER) {
lastCurve = AFTER; lastCurve = AFTER;
path.computeWorldVertices(target, verticesLength - 6, 4, temp, 0); path.computeWorldVertices(target, verticesLength - 6, 4, world, 0);
} }
addAfterPosition(p - pathLength, temp, 0, out, o); addAfterPosition(p - pathLength, world, 0, out, o);
continue; continue;
} }
@ -220,45 +197,45 @@ public class PathConstraint implements Updatable {
if (curve != lastCurve) { if (curve != lastCurve) {
lastCurve = curve; lastCurve = curve;
if (closed && curve == curves) { if (closed && curve == curveCount) {
path.computeWorldVertices(target, verticesLength - 4, 4, temp, 0); path.computeWorldVertices(target, verticesLength - 4, 4, world, 0);
path.computeWorldVertices(target, 0, 4, temp, 4); path.computeWorldVertices(target, 0, 4, world, 4);
} else } else
path.computeWorldVertices(target, curve * 6 + 2, 8, temp, 0); path.computeWorldVertices(target, curve * 6 + 2, 8, world, 0);
} }
addCurvePosition(p, temp[0], temp[1], temp[2], temp[3], temp[4], temp[5], temp[6], temp[7], out, o, addCurvePosition(p, world[0], world[1], world[2], world[3], world[4], world[5], world[6], world[7], out, o,
tangents || (space == 0 && i > 0)); tangents || (space == 0 && i > 0));
} }
return out; return out;
} }
// World vertices, verticesStart to verticesStart + verticesLength - 1. // World vertices.
int verticesStart = 10 + curves;
temp = this.temp.setSize(verticesStart + verticesLength + 2);
if (closed) { if (closed) {
verticesLength += 2; verticesLength += 2;
int verticesEnd = verticesStart + verticesLength; world = this.world.setSize(verticesLength);
path.computeWorldVertices(target, 2, verticesLength - 4, temp, verticesStart); path.computeWorldVertices(target, 2, verticesLength - 4, world, 0);
path.computeWorldVertices(target, 0, 2, temp, verticesEnd - 4); path.computeWorldVertices(target, 0, 2, world, verticesLength - 4);
temp[verticesEnd - 2] = temp[verticesStart]; world[verticesLength - 2] = world[0];
temp[verticesEnd - 1] = temp[verticesStart + 1]; world[verticesLength - 1] = world[1];
} else { } else {
verticesStart--; curveCount--;
verticesLength -= 4; verticesLength -= 4;
path.computeWorldVertices(target, 2, verticesLength, temp, verticesStart); world = this.world.setSize(verticesLength);
path.computeWorldVertices(target, 2, verticesLength, world, 0);
} }
// Curve lengths, 10 to verticesStart - 1. // Curve lengths.
float[] curves = this.curves.setSize(curveCount);
float pathLength = 0; float pathLength = 0;
float x1 = temp[verticesStart], y1 = temp[verticesStart + 1], cx1 = 0, cy1 = 0, cx2 = 0, cy2 = 0, x2 = 0, y2 = 0; float x1 = world[0], y1 = world[1], cx1 = 0, cy1 = 0, cx2 = 0, cy2 = 0, x2 = 0, y2 = 0;
float tmpx, tmpy, dddfx, dddfy, ddfx, ddfy, dfx, dfy; float tmpx, tmpy, dddfx, dddfy, ddfx, ddfy, dfx, dfy;
for (int i = 10, v = verticesStart + 2; i < verticesStart; i++, v += 6) { for (int i = 0, w = 2; i < curveCount; i++, w += 6) {
cx1 = temp[v]; cx1 = world[w];
cy1 = temp[v + 1]; cy1 = world[w + 1];
cx2 = temp[v + 2]; cx2 = world[w + 2];
cy2 = temp[v + 3]; cy2 = world[w + 3];
x2 = temp[v + 4]; x2 = world[w + 4];
y2 = temp[v + 5]; y2 = world[w + 5];
tmpx = (x1 - cx1 * 2 + cx2) * 0.1875f; tmpx = (x1 - cx1 * 2 + cx2) * 0.1875f;
tmpy = (y1 - cy1 * 2 + cy2) * 0.1875f; tmpy = (y1 - cy1 * 2 + cy2) * 0.1875f;
dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.09375f; dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.09375f;
@ -279,18 +256,19 @@ public class PathConstraint implements Updatable {
dfx += ddfx + dddfx; dfx += ddfx + dddfx;
dfy += ddfy + dddfy; dfy += ddfy + dddfy;
pathLength += (float)Math.sqrt(dfx * dfx + dfy * dfy); pathLength += (float)Math.sqrt(dfx * dfx + dfy * dfy);
temp[i] = pathLength; curves[i] = pathLength;
x1 = x2; x1 = x2;
y1 = y2; y1 = y2;
} }
position *= pathLength; if (percentPosition) position *= pathLength;
if (percentSpacing) { if (percentSpacing) {
for (int i = 0; i < spacesCount; i++) for (int i = 0; i < spacesCount; i++)
spaces[i] *= pathLength; spaces[i] *= pathLength;
} }
float[] segments = this.segments;
float curveLength = 0; float curveLength = 0;
for (int i = 0, o = 0, curve = 10, segment = 0; i < spacesCount; i++, o += 3) { for (int i = 0, o = 0, curve = 0, segment = 0; i < spacesCount; i++, o += 3) {
float space = spaces[i]; float space = spaces[i];
position += space; position += space;
float p = position; float p = position;
@ -298,23 +276,23 @@ public class PathConstraint implements Updatable {
if (closed) { if (closed) {
p %= pathLength; p %= pathLength;
if (p < 0) p += pathLength; if (p < 0) p += pathLength;
curve = 10; curve = 0;
} else if (p < 0) { } else if (p < 0) {
addBeforePosition(p, temp, verticesStart, out, o); addBeforePosition(p, world, 0, out, o);
continue; continue;
} else if (p > pathLength) { } else if (p > pathLength) {
addAfterPosition(p - pathLength, temp, verticesStart + verticesLength - 4, out, o); addAfterPosition(p - pathLength, world, verticesLength - 4, out, o);
continue; continue;
} }
// Determine curve containing position. // Determine curve containing position.
for (;; curve++) { for (;; curve++) {
float length = temp[curve]; float length = curves[curve];
if (p > length) continue; if (p > length) continue;
if (curve == 10) if (curve == 0)
p /= length; p /= length;
else { else {
float prev = temp[curve - 1]; float prev = curves[curve - 1];
p = (p - prev) / (length - prev); p = (p - prev) / (length - prev);
} }
break; break;
@ -323,15 +301,15 @@ public class PathConstraint implements Updatable {
// Curve segment lengths, 0 to 9. // Curve segment lengths, 0 to 9.
if (curve != lastCurve) { if (curve != lastCurve) {
lastCurve = curve; lastCurve = curve;
int ii = verticesStart + (curve - 10) * 6; int ii = curve * 6;
x1 = temp[ii]; x1 = world[ii];
y1 = temp[ii + 1]; y1 = world[ii + 1];
cx1 = temp[ii + 2]; cx1 = world[ii + 2];
cy1 = temp[ii + 3]; cy1 = world[ii + 3];
cx2 = temp[ii + 4]; cx2 = world[ii + 4];
cy2 = temp[ii + 5]; cy2 = world[ii + 5];
x2 = temp[ii + 6]; x2 = world[ii + 6];
y2 = temp[ii + 7]; y2 = world[ii + 7];
tmpx = (x1 - cx1 * 2 + cx2) * 0.03f; tmpx = (x1 - cx1 * 2 + cx2) * 0.03f;
tmpy = (y1 - cy1 * 2 + cy2) * 0.03f; tmpy = (y1 - cy1 * 2 + cy2) * 0.03f;
dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.006f; dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.006f;
@ -341,35 +319,35 @@ public class PathConstraint implements Updatable {
dfx = (cx1 - x1) * 0.3f + tmpx + dddfx * 0.16666667f; dfx = (cx1 - x1) * 0.3f + tmpx + dddfx * 0.16666667f;
dfy = (cy1 - y1) * 0.3f + tmpy + dddfy * 0.16666667f; dfy = (cy1 - y1) * 0.3f + tmpy + dddfy * 0.16666667f;
curveLength = (float)Math.sqrt(dfx * dfx + dfy * dfy); curveLength = (float)Math.sqrt(dfx * dfx + dfy * dfy);
temp[0] = curveLength; segments[0] = curveLength;
for (ii = 1; ii < 8; ii++) { for (ii = 1; ii < 8; ii++) {
dfx += ddfx; dfx += ddfx;
dfy += ddfy; dfy += ddfy;
ddfx += dddfx; ddfx += dddfx;
ddfy += dddfy; ddfy += dddfy;
curveLength += (float)Math.sqrt(dfx * dfx + dfy * dfy); curveLength += (float)Math.sqrt(dfx * dfx + dfy * dfy);
temp[ii] = curveLength; segments[ii] = curveLength;
} }
dfx += ddfx; dfx += ddfx;
dfy += ddfy; dfy += ddfy;
curveLength += (float)Math.sqrt(dfx * dfx + dfy * dfy); curveLength += (float)Math.sqrt(dfx * dfx + dfy * dfy);
temp[8] = curveLength; segments[8] = curveLength;
dfx += ddfx + dddfx; dfx += ddfx + dddfx;
dfy += ddfy + dddfy; dfy += ddfy + dddfy;
curveLength += (float)Math.sqrt(dfx * dfx + dfy * dfy); curveLength += (float)Math.sqrt(dfx * dfx + dfy * dfy);
temp[9] = curveLength; segments[9] = curveLength;
segment = 0; segment = 0;
} }
// Weight by segment length. // Weight by segment length.
p *= curveLength; p *= curveLength;
for (;; segment++) { for (;; segment++) {
float length = temp[segment]; float length = segments[segment];
if (p > length) continue; if (p > length) continue;
if (segment == 0) if (segment == 0)
p /= length; p /= length;
else { else {
float prev = temp[segment - 1]; float prev = segments[segment - 1];
p = segment + (p - prev) / (length - prev); p = segment + (p - prev) / (length - prev);
} }
break; break;
@ -456,16 +434,4 @@ public class PathConstraint implements Updatable {
public String toString () { public String toString () {
return data.name; return data.name;
} }
static public enum RotateMode {
tangent, chain, chainScale;
static public final RotateMode[] values = RotateMode.values();
}
static public enum SpacingMode {
length, fixed, percent;
static public final SpacingMode[] values = SpacingMode.values();
}
} }

View File

@ -2,13 +2,12 @@
package com.esotericsoftware.spine; package com.esotericsoftware.spine;
import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Array;
import com.esotericsoftware.spine.PathConstraint.RotateMode;
import com.esotericsoftware.spine.PathConstraint.SpacingMode;
public class PathConstraintData { public class PathConstraintData {
final String name; final String name;
final Array<BoneData> bones = new Array(); final Array<BoneData> bones = new Array();
SlotData target; SlotData target;
PositionMode positionMode;
SpacingMode spacingMode; SpacingMode spacingMode;
RotateMode rotateMode; RotateMode rotateMode;
float offsetRotation; float offsetRotation;
@ -31,6 +30,14 @@ public class PathConstraintData {
this.target = target; this.target = target;
} }
public PositionMode getPositionMode () {
return positionMode;
}
public void setPositionMode (PositionMode positionMode) {
this.positionMode = positionMode;
}
public SpacingMode getSpacingMode () { public SpacingMode getSpacingMode () {
return spacingMode; return spacingMode;
} }
@ -94,4 +101,22 @@ public class PathConstraintData {
public String toString () { public String toString () {
return name; return name;
} }
static public enum PositionMode {
fixed, percent;
static public final PositionMode[] values = PositionMode.values();
}
static public enum SpacingMode {
length, fixed, percent;
static public final SpacingMode[] values = SpacingMode.values();
}
static public enum RotateMode {
tangent, chain, chainScale;
static public final RotateMode[] values = RotateMode.values();
}
} }

View File

@ -55,8 +55,8 @@ import com.esotericsoftware.spine.Animation.ShearTimeline;
import com.esotericsoftware.spine.Animation.Timeline; import com.esotericsoftware.spine.Animation.Timeline;
import com.esotericsoftware.spine.Animation.TransformConstraintTimeline; import com.esotericsoftware.spine.Animation.TransformConstraintTimeline;
import com.esotericsoftware.spine.Animation.TranslateTimeline; import com.esotericsoftware.spine.Animation.TranslateTimeline;
import com.esotericsoftware.spine.PathConstraint.RotateMode; import com.esotericsoftware.spine.PathConstraintData.RotateMode;
import com.esotericsoftware.spine.PathConstraint.SpacingMode; import com.esotericsoftware.spine.PathConstraintData.SpacingMode;
import com.esotericsoftware.spine.SkeletonJson.LinkedMesh; import com.esotericsoftware.spine.SkeletonJson.LinkedMesh;
import com.esotericsoftware.spine.attachments.AtlasAttachmentLoader; import com.esotericsoftware.spine.attachments.AtlasAttachmentLoader;
import com.esotericsoftware.spine.attachments.Attachment; import com.esotericsoftware.spine.attachments.Attachment;

View File

@ -54,8 +54,8 @@ import com.esotericsoftware.spine.Animation.ShearTimeline;
import com.esotericsoftware.spine.Animation.Timeline; import com.esotericsoftware.spine.Animation.Timeline;
import com.esotericsoftware.spine.Animation.TransformConstraintTimeline; import com.esotericsoftware.spine.Animation.TransformConstraintTimeline;
import com.esotericsoftware.spine.Animation.TranslateTimeline; import com.esotericsoftware.spine.Animation.TranslateTimeline;
import com.esotericsoftware.spine.PathConstraint.RotateMode; import com.esotericsoftware.spine.PathConstraintData.RotateMode;
import com.esotericsoftware.spine.PathConstraint.SpacingMode; import com.esotericsoftware.spine.PathConstraintData.SpacingMode;
import com.esotericsoftware.spine.attachments.AtlasAttachmentLoader; import com.esotericsoftware.spine.attachments.AtlasAttachmentLoader;
import com.esotericsoftware.spine.attachments.Attachment; import com.esotericsoftware.spine.attachments.Attachment;
import com.esotericsoftware.spine.attachments.AttachmentLoader; import com.esotericsoftware.spine.attachments.AttachmentLoader;