Update cache now handles all constraint configurations.

Can also apply constraints in a specific order, except for IK which must always be first.
This commit is contained in:
NathanSweet 2016-06-03 21:46:02 +02:00
parent 8538e793d6
commit 5dcf6b3b02
3 changed files with 143 additions and 57 deletions

View File

@ -50,6 +50,8 @@ public class Bone implements Updatable {
float c, d, worldY; float c, d, worldY;
float worldSignX, worldSignY; float worldSignX, worldSignY;
boolean sorted;
/** @param parent May be null. */ /** @param parent May be null. */
public Bone (BoneData data, Skeleton skeleton, Bone parent) { public Bone (BoneData data, Skeleton skeleton, Bone parent) {
if (data == null) throw new IllegalArgumentException("data cannot be null."); if (data == null) throw new IllegalArgumentException("data cannot be null.");

View File

@ -42,6 +42,8 @@ public class IkConstraint implements Updatable {
float mix = 1; float mix = 1;
int bendDirection; int bendDirection;
int level;
public IkConstraint (IkConstraintData data, Skeleton skeleton) { public IkConstraint (IkConstraintData data, Skeleton skeleton) {
if (data == null) throw new IllegalArgumentException("data cannot be null."); if (data == null) throw new IllegalArgumentException("data cannot be null.");
if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null.");
@ -135,6 +137,10 @@ public class IkConstraint implements Updatable {
else if (rotationIK < -180) rotationIK += 360; else if (rotationIK < -180) rotationIK += 360;
bone.updateWorldTransform(bone.x, bone.y, bone.rotation + (rotationIK - bone.rotation) * alpha, bone.appliedScaleX, bone.updateWorldTransform(bone.x, bone.y, bone.rotation + (rotationIK - bone.rotation) * alpha, bone.appliedScaleX,
bone.appliedScaleY, bone.shearX, bone.shearY); bone.appliedScaleY, bone.shearX, bone.shearY);
/*
* parents of this thing this thing (bone) ik constraint
*/
} }
/** Adjusts the parent and child bone rotations so the tip of the child is as close to the target position as possible. The /** Adjusts the parent and child bone rotations so the tip of the child is as close to the target position as possible. The
@ -142,7 +148,7 @@ public class IkConstraint implements Updatable {
* @param child A direct descendant of the parent bone. */ * @param child A direct descendant of the parent bone. */
static public void apply (Bone parent, Bone child, float targetX, float targetY, int bendDir, float alpha) { static public void apply (Bone parent, Bone child, float targetX, float targetY, int bendDir, float alpha) {
if (alpha == 0) return; if (alpha == 0) return;
float px = parent.x, py = parent.y, psx = parent.appliedScaleX, psy = parent.appliedScaleY; float px = parent.x, py = parent.y, psx = parent.appliedScaleX, psy = parent.appliedScaleY, csx = child.appliedScaleX;
int os1, os2, s2; int os1, os2, s2;
if (psx < 0) { if (psx < 0) {
psx = -psx; psx = -psx;
@ -156,25 +162,32 @@ public class IkConstraint implements Updatable {
psy = -psy; psy = -psy;
s2 = -s2; s2 = -s2;
} }
float cx = child.x, cy = child.y, csx = child.appliedScaleX;
boolean u = Math.abs(psx - psy) <= 0.0001f;
if (!u && cy != 0) {
child.worldX = parent.a * cx + parent.worldX;
child.worldY = parent.c * cx + parent.worldY;
cy = 0;
}
if (csx < 0) { if (csx < 0) {
csx = -csx; csx = -csx;
os2 = 180; os2 = 180;
} else } else
os2 = 0; os2 = 0;
float cx = child.x, cy, cwx, cwy, a = parent.a, b = parent.b, c = parent.c, d = parent.d;
boolean u = Math.abs(psx - psy) <= 0.0001f;
if (!u) {
cy = 0;
cwx = a * cx + parent.worldX;
cwy = c * cx + parent.worldY;
} else {
cy = child.y;
cwx = a * cx + b * cy + parent.worldX;
cwy = c * cx + d * cy + parent.worldY;
}
Bone pp = parent.parent; Bone pp = parent.parent;
float ppa = pp.a, ppb = pp.b, ppc = pp.c, ppd = pp.d, id = 1 / (ppa * ppd - ppb * ppc); a = pp.a;
float x = targetX - pp.worldX, y = targetY - pp.worldY; b = pp.b;
float tx = (x * ppd - y * ppb) * id - px, ty = (y * ppa - x * ppc) * id - py; c = pp.c;
x = child.worldX - pp.worldX; d = pp.d;
y = child.worldY - pp.worldY; float id = 1 / (a * d - b * c), x = targetX - pp.worldX, y = targetY - pp.worldY;
float dx = (x * ppd - y * ppb) * id - px, dy = (y * ppa - x * ppc) * id - py; float tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py;
x = cwx - pp.worldX;
y = cwy - pp.worldY;
float dx = (x * d - y * b) * id - px, dy = (y * a - x * c) * id - py;
float l1 = (float)Math.sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2; float l1 = (float)Math.sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2;
outer: outer:
if (u) { if (u) {
@ -184,18 +197,21 @@ public class IkConstraint implements Updatable {
cos = -1; cos = -1;
else if (cos > 1) cos = 1; else if (cos > 1) cos = 1;
a2 = (float)Math.acos(cos) * bendDir; a2 = (float)Math.acos(cos) * bendDir;
float a = l1 + l2 * cos, o = l2 * sin(a2); a = l1 + l2 * cos;
a1 = atan2(ty * a - tx * o, tx * a + ty * o); b = l2 * sin(a2);
a1 = atan2(ty * a - tx * b, tx * a + ty * b);
} else { } else {
float a = psx * l2, b = psy * l2, ta = atan2(ty, tx); a = psx * l2;
float aa = a * a, bb = b * b, ll = l1 * l1, dd = tx * tx + ty * ty; b = psy * l2;
float c0 = bb * ll + aa * dd - aa * bb, c1 = -2 * bb * l1, c2 = bb - aa; float aa = a * a, bb = b * b, dd = tx * tx + ty * ty, ta = atan2(ty, tx);
float d = c1 * c1 - 4 * c2 * c0; c = bb * l1 * l1 + aa * dd - aa * bb;
float c1 = -2 * bb * l1, c2 = bb - aa;
d = c1 * c1 - 4 * c2 * c;
if (d >= 0) { if (d >= 0) {
float q = (float)Math.sqrt(d); float q = (float)Math.sqrt(d);
if (c1 < 0) q = -q; if (c1 < 0) q = -q;
q = -(c1 + q) / 2; q = -(c1 + q) / 2;
float r0 = q / c2, r1 = c0 / q; float r0 = q / c2, r1 = c / q;
float r = Math.abs(r0) < Math.abs(r1) ? r0 : r1; float r = Math.abs(r0) < Math.abs(r1) ? r0 : r1;
if (r * r <= dd) { if (r * r <= dd) {
y = (float)Math.sqrt(dd - r * r) * bendDir; y = (float)Math.sqrt(dd - r * r) * bendDir;

View File

@ -140,47 +140,115 @@ public class Skeleton {
/** Caches information about bones and constraints. Must be called if bones or constraints are added or removed. */ /** Caches information about bones and constraints. Must be called if bones or constraints are added or removed. */
public void updateCache () { public void updateCache () {
Array<Bone> bones = this.bones;
Array<Updatable> updateCache = this.updateCache; Array<Updatable> updateCache = this.updateCache;
Array<IkConstraint> ikConstraints = this.ikConstraints;
Array<TransformConstraint> transformConstraints = this.transformConstraints;
Array<PathConstraint> pathConstraints = this.pathConstraints;
int ikConstraintsCount = ikConstraints.size;
updateCache.clear(); updateCache.clear();
Array<Bone> bones = this.bones;
for (int i = 0, n = bones.size; i < n; i++)
bones.get(i).sorted = false;
// IK first, in hierarchy depth order.
Array<IkConstraint> ikConstraints = this.ikConstraints;
int ikCount = ikConstraints.size;
for (int i = 0, level, n = ikCount; i < n; i++) {
IkConstraint ik = ikConstraints.get(i);
Bone bone = ik.bones.first().parent;
for (level = 0; bone != null; level++)
bone = bone.parent;
ik.level = level;
}
for (int i = 1, ii; i < ikCount; i++) {
IkConstraint ik = ikConstraints.get(i);
int level = ik.level;
for (ii = i - 1; ii >= 0; ii--) {
IkConstraint other = ikConstraints.get(ii);
if (other.level < level) break;
ikConstraints.set(ii + 1, other);
}
ikConstraints.set(ii + 1, ik);
}
for (int i = 0, n = ikConstraints.size; i < n; i++) {
IkConstraint constraint = ikConstraints.get(i);
Bone target = constraint.target;
sortBone(target);
Array<Bone> constrained = constraint.bones;
Bone parent = constrained.first();
sortBone(parent);
updateCache.add(constraint);
reset(target.children);
reset(parent.children);
constrained.peek().sorted = true;
}
Array<PathConstraint> pathConstraints = this.pathConstraints;
for (int i = 0, n = pathConstraints.size; i < n; i++) {
PathConstraint constraint = pathConstraints.get(i);
Bone target = constraint.target.bone;
sortBone(target);
Array<Bone> constrained = constraint.bones;
int boneCount = constrained.size;
for (int ii = 0; ii < boneCount; ii++)
sortBone(constrained.get(ii));
updateCache.add(constraint);
resetChildren(constrained);
reset(target.children);
for (int ii = 0; ii < boneCount; ii++)
constrained.get(ii).sorted = true;
}
Array<TransformConstraint> transformConstraints = this.transformConstraints;
for (int i = 0, n = transformConstraints.size; i < n; i++) {
TransformConstraint constraint = transformConstraints.get(i);
Bone target = constraint.target;
sortBone(target);
// BOZO - Update transform constraints to support multiple constrained bones.
// Array<Bone> constrained = constraint.bones;
// int boneCount = constrained.size;
// for (int ii = 0; ii < boneCount; ii++)
// sortBone(constrained.get(ii));
sortBone(constraint.bone);
updateCache.add(constraint);
// resetChildren(constrained);
reset(constraint.bone.children); // BOZO - Remove.
reset(target.children);
// for (int ii = 0; ii < boneCount; ii++)
// constrained.get(ii).sorted = true;
constraint.bone.sorted = true; // BOZO - Remove.
}
for (int i = 0, n = bones.size; i < n; i++)
sortBone(bones.get(i));
}
private void sortBone (Bone bone) {
if (bone.sorted) return;
Bone parent = bone.parent;
if (parent != null) sortBone(parent);
bone.sorted = true;
updateCache.add(bone);
}
private void resetChildren (Array<Bone> bones) {
for (int i = 0, n = bones.size; i < n; i++)
reset(bones.get(i).children);
}
private void reset (Array<Bone> bones) {
for (int i = 0, n = bones.size; i < n; i++) { for (int i = 0, n = bones.size; i < n; i++) {
Bone bone = bones.get(i); Bone bone = bones.get(i);
updateCache.add(bone); if (bone.sorted) reset(bone.children);
for (int ii = 0; ii < ikConstraintsCount; ii++) { bone.sorted = false;
IkConstraint ikConstraint = ikConstraints.get(ii);
if (bone == ikConstraint.bones.peek()) {
updateCache.add(ikConstraint);
break;
}
}
}
for (int i = 0, n = pathConstraints.size; i < n; i++) {
PathConstraint pathConstraint = pathConstraints.get(i);
// BOZO! - Fix update order for multiple bones.
Bone bone = pathConstraint.bones.peek();
for (int ii = updateCache.size - 1; ii >= 0; ii--) {
if (updateCache.get(ii) == bone) {
updateCache.insert(ii + 1, pathConstraint);
break;
}
}
}
for (int i = 0, n = transformConstraints.size; i < n; i++) {
TransformConstraint transformConstraint = transformConstraints.get(i);
Bone bone = transformConstraint.bone;
for (int ii = updateCache.size - 1; ii >= 0; ii--) {
if (updateCache.get(ii) == bone) {
updateCache.insert(ii + 1, transformConstraint);
break;
}
}
} }
} }