Path constraint optimization.

This commit is contained in:
NathanSweet 2016-05-29 05:58:23 +02:00
parent cdbc878f31
commit c4810e23d6

View File

@ -13,8 +13,7 @@ public class PathConstraint implements Updatable {
final Array<Bone> bones; final Array<Bone> bones;
Slot target; Slot target;
float position, rotateMix, translateMix, scaleMix; float position, rotateMix, translateMix, scaleMix;
final FloatArray lengths = new FloatArray(), positions = new FloatArray(); final FloatArray lengths = new FloatArray(), positions = new FloatArray(), temp = new FloatArray();
final FloatArray worldVertices = new FloatArray(), temp = new FloatArray();
public PathConstraint (PathConstraintData data, Skeleton skeleton) { public PathConstraint (PathConstraintData data, Skeleton skeleton) {
this.data = data; this.data = data;
@ -53,24 +52,23 @@ public class PathConstraint implements Updatable {
float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix; float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix;
boolean translate = translateMix > 0, rotate = rotateMix > 0, scale = scaleMix > 0; boolean translate = translateMix > 0, rotate = rotateMix > 0, scale = scaleMix > 0;
if (!translate && !rotate) return; if (!translate && !rotate && !scale) return;
PathAttachment path = (PathAttachment)attachment; PathAttachment path = (PathAttachment)attachment;
FloatArray lengths = this.lengths; FloatArray lengths = this.lengths;
lengths.clear(); lengths.clear();
lengths.add(0); lengths.add(0);
positions.clear();
Array<Bone> bones = this.bones; Array<Bone> bones = this.bones;
int boneCount = bones.size; int boneCount = bones.size;
if (boneCount == 1) { if (boneCount == 1) {
computeWorldPositions(path, rotate); float[] positions = computeWorldPositions(path, rotate);
Bone bone = bones.first(); Bone bone = bones.first();
bone.worldX += (positions.first() - bone.worldX) * translateMix; bone.worldX += (positions[0] - bone.worldX) * translateMix;
bone.worldY += (positions.get(1) - bone.worldY) * translateMix; bone.worldY += (positions[1] - bone.worldY) * translateMix;
if (rotate) { if (rotate) {
float a = bone.a, b = bone.b, c = bone.c, d = bone.d; float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
float r = positions.get(2) - atan2(c, a) + data.offsetRotation * degRad; float r = positions[2] - atan2(c, a) + data.offsetRotation * degRad;
if (r > PI) if (r > PI)
r -= PI2; r -= PI2;
else if (r < -PI) r += PI2; else if (r < -PI) r += PI2;
@ -84,46 +82,48 @@ public class PathConstraint implements Updatable {
return; return;
} }
for (int i = 0; i < boneCount; i++) for (int i = 0; i < boneCount; i++) {
lengths.add(bones.get(i).data.length); Bone bone = bones.get(i);
computeWorldPositions(path, false); float length = bone.data.length;
float x = length * bone.a, y = length * bone.c;
lengths.add((float)Math.sqrt(x * x + y * y));
}
float[] positions = computeWorldPositions(path, false);
float[] positions = this.positions.items; float boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation;
float boneX = positions[0], boneY = positions[1];
for (int i = 0, p = 2; i < boneCount; i++, p += 2) { for (int i = 0, p = 2; i < boneCount; i++, p += 2) {
Bone bone = bones.get(i); Bone bone = bones.get(i);
float x = positions[p], y = positions[p + 1];
if (scale) {
float dx = boneX - x, dy = boneY - y, d = (float)Math.sqrt(dx * dx + dy * dy);
// BOZO - Length not transformed by bone matrix.
float sx = bone.scaleX + (d / bone.data.length - bone.scaleX) * scaleMix;
bone.a *= sx;
bone.c *= sx;
}
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 r = atan2(y - boneY, x - boneX) + data.offsetRotation * degRad; if (scale) {
if (data.offsetRotation != 0) { float s = ((float)Math.sqrt(dx * dx + dy * dy) / lengths.get(i + 1) - 1) * scaleMix + 1;
bone.a *= s;
bone.c *= s;
}
if (!rotate) {
boneX = x; boneX = x;
boneY = y; boneY = y;
} else { } else {
// BOZO - Doesn't transform by bone matrix.
float cos = cos(r), sin = sin(r);
float length = bone.data.length, mix = rotateMix * (1 - scaleMix);
boneX = x + (length * cos + boneX - x) * mix;
boneY = y + (length * sin + boneY - y) * mix;
}
if (rotate) {
float a = bone.a, b = bone.b, c = bone.c, d = bone.d; float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
r -= atan2(c, a); float r = atan2(dy, dx) - atan2(c, a) + offsetRotation * degRad, cos, sin;
if (offsetRotation != 0) {
boneX = x;
boneY = y;
} else { // Mix between on path and at tip.
cos = cos(r);
sin = sin(r);
float length = bone.data.length;
boneX = x + (length * (cos * a - sin * c) - dx) * rotateMix;
boneY = y + (length * (sin * a + cos * c) - dy) * rotateMix;
}
if (r > PI) if (r > PI)
r -= PI2; r -= PI2;
else if (r < -PI) r += PI2; else if (r < -PI) //
r += PI2;
r *= rotateMix; r *= rotateMix;
float cos = cos(r), sin = sin(r); cos = cos(r);
sin = sin(r);
bone.a = cos * a - sin * c; bone.a = cos * a - sin * c;
bone.b = cos * b - sin * d; bone.b = cos * b - sin * d;
bone.c = sin * a + cos * c; bone.c = sin * a + cos * c;
@ -132,68 +132,71 @@ public class PathConstraint implements Updatable {
} }
} }
private void computeWorldPositions (PathAttachment path, boolean tangents) { private float[] computeWorldPositions (PathAttachment path, boolean tangents) {
Slot slot = target; Slot target = this.target;
float position = this.position; float position = this.position;
FloatArray out = positions; int lengthCount = lengths.size;
FloatArray lengths = this.lengths; float[] lengths = this.lengths.items;
FloatArray positions = this.positions;
int verticesLength = path.getWorldVerticesLength(), curves = verticesLength / 6; positions.clear();
boolean closed = path.getClosed(); boolean closed = path.getClosed();
float[] vertices; int verticesLength = path.getWorldVerticesLength(), curves = verticesLength / 6;
float[] temp;
if (!path.getConstantSpeed()) { if (!path.getConstantSpeed()) {
if (!closed) curves--; if (!closed) curves--;
float pathLength = path.getLength(); float pathLength = path.getLength();
vertices = worldVertices.setSize(8); temp = this.temp.setSize(8);
for (int i = 0, n = lengths.size; i < n; i++) { for (int i = 0; i < lengthCount; i++) {
position += lengths.get(i) / pathLength; position += lengths[i] / pathLength;
if (closed) { if (closed) {
position %= 1; position %= 1;
if (position < 0) position += 1; if (position < 0) position += 1;
} else if (position < 0 || position > 1) { } else if (position < 0 || position > 1) {
path.computeWorldVertices(slot, 0, 4, vertices, 0); path.computeWorldVertices(target, 0, 4, temp, 0);
path.computeWorldVertices(slot, verticesLength - 4, 4, vertices, 4); path.computeWorldVertices(target, verticesLength - 4, 4, temp, 4);
addOutsidePoint(position * pathLength, vertices, 8, pathLength, out, tangents); addOutsidePosition(position * pathLength, temp, 0, 8, pathLength, positions, tangents);
continue; continue;
} }
int curve = position < 1 ? (int)(curves * position) : curves - 1; int curve = position < 1 ? (int)(curves * position) : curves - 1;
if (closed && curve == curves - 1) { if (closed && curve == curves - 1) {
path.computeWorldVertices(slot, verticesLength - 4, 4, vertices, 0); path.computeWorldVertices(target, verticesLength - 4, 4, temp, 0);
path.computeWorldVertices(slot, 0, 4, vertices, 4); path.computeWorldVertices(target, 0, 4, temp, 4);
} else } else
path.computeWorldVertices(slot, curve * 6 + 2, 8, vertices, 0); path.computeWorldVertices(target, curve * 6 + 2, 8, temp, 0);
addCurvePoint(vertices[0], vertices[1], vertices[2], vertices[3], vertices[4], vertices[5], vertices[6], vertices[7], addCurvePosition((position - curve / (float)curves) * curves, temp[0], temp[1], temp[2], temp[3], temp[4], temp[5],
(position - curve / (float)curves) * curves, tangents, out); temp[6], temp[7], positions, tangents);
} }
return; return positions.items;
} }
// World vertices, verticesStart to verticesStart + verticesLength.
int verticesStart = 10 + curves;
temp = this.temp.setSize(verticesStart + verticesLength + 2);
if (closed) { if (closed) {
verticesLength += 2; verticesLength += 2;
vertices = worldVertices.setSize(verticesLength); int verticesEnd = verticesStart + verticesLength;
path.computeWorldVertices(slot, 2, verticesLength - 4, vertices, 0); path.computeWorldVertices(target, 2, verticesLength - 4, temp, verticesStart);
path.computeWorldVertices(slot, 0, 2, vertices, verticesLength - 4); path.computeWorldVertices(target, 0, 2, temp, verticesEnd - 4);
vertices[verticesLength - 2] = vertices[0]; temp[verticesEnd - 2] = temp[verticesStart];
vertices[verticesLength - 1] = vertices[1]; temp[verticesEnd - 1] = temp[verticesStart + 1];
} else { } else {
verticesStart--;
verticesLength -= 4; verticesLength -= 4;
vertices = worldVertices.setSize(verticesLength); path.computeWorldVertices(target, 2, verticesLength, temp, verticesStart);
path.computeWorldVertices(slot, 2, verticesLength, vertices, 0);
} }
// Curve lengths. // Curve lengths, 10 to verticesStart.
temp.setSize(10 + curves); // BOZO - Combine with worldVertices? float pathLength = 0;
float[] temp = this.temp.items; float x1 = temp[verticesStart], y1 = temp[verticesStart + 1], cx1 = 0, cy1 = 0, cx2 = 0, cy2 = 0, x2 = 0, y2 = 0;
float pathLength = 0, x1 = vertices[0], y1 = vertices[1], cx1, cy1, cx2, cy2, x2, y2;
float tmpx, tmpy, dddfx, dddfy, ddfx, ddfy, dfx, dfy; float tmpx, tmpy, dddfx, dddfy, ddfx, ddfy, dfx, dfy;
for (int i = 10, w = 2; w < verticesLength; i++, w += 6) { for (int i = 10, v = verticesStart + 2; i < verticesStart; i++, v += 6) {
cx1 = vertices[w]; cx1 = temp[v];
cy1 = vertices[w + 1]; cy1 = temp[v + 1];
cx2 = vertices[w + 2]; cx2 = temp[v + 2];
cy2 = vertices[w + 3]; cy2 = temp[v + 3];
x2 = vertices[w + 4]; x2 = temp[v + 4];
y2 = vertices[w + 5]; y2 = temp[v + 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;
@ -220,15 +223,17 @@ public class PathConstraint implements Updatable {
} }
position *= pathLength; position *= pathLength;
for (int i = 0, n = lengths.size; i < n; i++) { int lastCurve = 0;
position += lengths.get(i); float curveLength = 0;
for (int i = 0; i < lengthCount; i++) {
position += lengths[i];
float p = position; float p = position;
if (closed) { if (closed) {
p %= pathLength; p %= pathLength;
if (p < 0) p += pathLength; if (p < 0) p += pathLength;
} else if (p < 0 || p > pathLength) { } else if (p < 0 || p > pathLength) {
addOutsidePoint(p, vertices, verticesLength, pathLength, out, tangents); addOutsidePosition(p, temp, verticesStart, verticesLength, pathLength, positions, tangents);
continue; continue;
} }
@ -236,7 +241,7 @@ public class PathConstraint implements Updatable {
int curve; int curve;
float length = temp[10]; float length = temp[10];
if (p <= length) { if (p <= length) {
curve = 0; curve = verticesStart;
p /= length; p /= length;
} else { } else {
for (curve = 11;; curve++) { for (curve = 11;; curve++) {
@ -247,47 +252,50 @@ public class PathConstraint implements Updatable {
break; break;
} }
} }
curve = (curve - 10) * 6; curve = verticesStart + (curve - 10) * 6;
} }
// Curve segment lengths. // Curve segment lengths, 0 to 10.
x1 = vertices[curve]; if (curve != lastCurve) {
y1 = vertices[curve + 1]; lastCurve = curve;
cx1 = vertices[curve + 2]; x1 = temp[curve];
cy1 = vertices[curve + 3]; y1 = temp[curve + 1];
cx2 = vertices[curve + 4]; cx1 = temp[curve + 2];
cy2 = vertices[curve + 5]; cy1 = temp[curve + 3];
x2 = vertices[curve + 6]; cx2 = temp[curve + 4];
y2 = vertices[curve + 7]; cy2 = temp[curve + 5];
tmpx = (x1 - cx1 * 2 + cx2) * 0.03f; x2 = temp[curve + 6];
tmpy = (y1 - cy1 * 2 + cy2) * 0.03f; y2 = temp[curve + 7];
dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.006f; tmpx = (x1 - cx1 * 2 + cx2) * 0.03f;
dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.006f; tmpy = (y1 - cy1 * 2 + cy2) * 0.03f;
ddfx = tmpx * 2 + dddfx; dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.006f;
ddfy = tmpy * 2 + dddfy; dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.006f;
dfx = (cx1 - x1) * 0.3f + tmpx + dddfx * 0.16666667f; ddfx = tmpx * 2 + dddfx;
dfy = (cy1 - y1) * 0.3f + tmpy + dddfy * 0.16666667f; ddfy = tmpy * 2 + dddfy;
length = (float)Math.sqrt(dfx * dfx + dfy * dfy); dfx = (cx1 - x1) * 0.3f + tmpx + dddfx * 0.16666667f;
temp[0] = length; dfy = (cy1 - y1) * 0.3f + tmpy + dddfy * 0.16666667f;
for (int ii = 1; ii < 8; ii++) { curveLength = (float)Math.sqrt(dfx * dfx + dfy * dfy);
temp[0] = curveLength;
for (int ii = 1; ii < 8; ii++) {
dfx += ddfx;
dfy += ddfy;
ddfx += dddfx;
ddfy += dddfy;
curveLength += (float)Math.sqrt(dfx * dfx + dfy * dfy);
temp[ii] = curveLength;
}
dfx += ddfx; dfx += ddfx;
dfy += ddfy; dfy += ddfy;
ddfx += dddfx; curveLength += (float)Math.sqrt(dfx * dfx + dfy * dfy);
ddfy += dddfy; temp[8] = curveLength;
length += (float)Math.sqrt(dfx * dfx + dfy * dfy); dfx += ddfx + dddfx;
temp[ii] = length; dfy += ddfy + dddfy;
curveLength += (float)Math.sqrt(dfx * dfx + dfy * dfy);
temp[9] = curveLength;
} }
dfx += ddfx;
dfy += ddfy;
length += (float)Math.sqrt(dfx * dfx + dfy * dfy);
temp[8] = length;
dfx += ddfx + dddfx;
dfy += ddfy + dddfy;
length += (float)Math.sqrt(dfx * dfx + dfy * dfy);
temp[9] = length;
// Weight by segment length. // Weight by segment length.
p *= length; p *= curveLength;
length = temp[0]; length = temp[0];
if (p <= length) if (p <= length)
p = 0.1f * p / length; p = 0.1f * p / length;
@ -302,36 +310,39 @@ public class PathConstraint implements Updatable {
} }
} }
addCurvePoint(x1, y1, cx1, cy1, cx2, cy2, x2, y2, p, tangents, out); addCurvePosition(p, x1, y1, cx1, cy1, cx2, cy2, x2, y2, positions, tangents);
} }
return positions.items;
} }
private void addOutsidePoint (float position, float[] vertices, int verticesLength, float pathLength, FloatArray out, private void addOutsidePosition (float p, float[] temp, int verticesStart, int verticesLength, float pathLength,
boolean tangents) { FloatArray out, boolean tangents) {
float x1, y1, x2, y2; float x1, y1, x2, y2;
if (position < 0) { if (p < 0) {
x1 = vertices[0]; x1 = temp[verticesStart];
y1 = vertices[1]; y1 = temp[verticesStart + 1];
x2 = vertices[2] - x1; x2 = temp[verticesStart + 2] - x1;
y2 = vertices[3] - y1; y2 = temp[verticesStart + 3] - y1;
} else { } else {
x1 = vertices[verticesLength - 2]; verticesStart += verticesLength;
y1 = vertices[verticesLength - 1]; x1 = temp[verticesStart - 2];
x2 = x1 - vertices[verticesLength - 4]; y1 = temp[verticesStart - 1];
y2 = y1 - vertices[verticesLength - 3]; x2 = x1 - temp[verticesStart - 4];
position -= pathLength; y2 = y1 - temp[verticesStart - 3];
p -= pathLength;
} }
float r = atan2(y2, x2); float r = atan2(y2, x2);
out.add(x1 + position * cos(r)); out.add(x1 + p * cos(r));
out.add(y1 + position * sin(r)); out.add(y1 + p * sin(r));
if (tangents) out.add(r + PI); if (tangents) out.add(r + PI);
} }
private void addCurvePoint (float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2, float position, private void addCurvePosition (float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2,
boolean tangents, FloatArray out) { FloatArray out, boolean tangents) {
if (position == 0) position = 0.0001f; if (p == 0) p = 0.0001f;
float tt = position * position, ttt = tt * position, u = 1 - position, uu = u * u, uuu = uu * u; float tt = p * p, ttt = tt * p, u = 1 - p, uu = u * u, uuu = uu * u;
float ut = u * position, ut3 = ut * 3, uut3 = u * ut3, utt3 = ut3 * position; float ut = u * p, ut3 = ut * 3, uut3 = u * ut3, utt3 = ut3 * p;
float x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt, y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt; float x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt, y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt;
out.add(x); out.add(x);
out.add(y); out.add(y);