[libgdx] updateCache sorting improvements.

This commit is contained in:
Nathan Sweet 2025-05-02 18:34:23 -04:00
parent 020cd51b96
commit 589200250b
8 changed files with 85 additions and 100 deletions

View File

@ -65,12 +65,4 @@ public class Bone extends PosedActive<BoneData, BoneLocal, BonePose> {
public Array<Bone> getChildren () { public Array<Bone> getChildren () {
return children; return children;
} }
void resetUpdate (Skeleton skeleton) {
if (applied.world != skeleton.update) return;
applied.world = 0;
Bone[] children = this.children.items;
for (int i = 0, n = this.children.size; i < n; i++)
children[i].resetUpdate(skeleton);
}
} }

View File

@ -139,8 +139,8 @@ public class BonePose extends BoneLocal implements Update {
* Some information is ambiguous in the world transform, such as -1,-1 scale versus 180 rotation. The local transform after * Some information is ambiguous in the world transform, such as -1,-1 scale versus 180 rotation. The local transform after
* calling this method is equivalent to the local transform used to compute the world transform, but may not be identical. */ * calling this method is equivalent to the local transform used to compute the world transform, but may not be identical. */
public void updateLocalTransform (Skeleton skeleton) { public void updateLocalTransform (Skeleton skeleton) {
world = skeleton.update;
local = 0; local = 0;
world = skeleton.update;
if (bone.parent == null) { if (bone.parent == null) {
x = worldX - skeleton.x; x = worldX - skeleton.x;
@ -219,10 +219,33 @@ public class BonePose extends BoneLocal implements Update {
} }
} }
/** When true, the world transform has been modified and the local transform no longer matches. Call /** If the world transform has been modified and the local transform no longer matches, {@link #updateLocalTransform(Skeleton)}
* {@link #updateLocalTransform(Skeleton)} before using the local transform. */ * is called. */
public boolean isLocalDirty (Skeleton skeleton) { public void validateLocalTransform (Skeleton skeleton) {
return local == skeleton.update; if (local == skeleton.update) updateLocalTransform(skeleton);
}
void modifyLocal (Skeleton skeleton) {
if (local == skeleton.update) updateLocalTransform(skeleton);
world = 0;
resetWorld(skeleton.update);
}
void modifyWorld (int update) {
local = update;
world = update;
resetWorld(update);
}
void resetWorld (int update) {
Bone[] children = bone.children.items;
for (int i = 0, n = bone.children.size; i < n; i++) {
BonePose child = children[i].applied;
if (child.world == update) {
child.world = 0;
child.resetWorld(update);
}
}
} }
/** Part of the world transform matrix for the X axis. If changed, {@link #updateLocalTransform(Skeleton)} should be called. */ /** Part of the world transform matrix for the X axis. If changed, {@link #updateLocalTransform(Skeleton)} should be called. */

View File

@ -76,19 +76,12 @@ public class IkConstraint extends Constraint<IkConstraint, IkConstraintData, IkC
void sort (Skeleton skeleton) { void sort (Skeleton skeleton) {
skeleton.sortBone(target); skeleton.sortBone(target);
Bone parent = bones.items[0].bone; Bone parent = bones.items[0].bone;
skeleton.resetCache(parent);
skeleton.sortBone(parent); skeleton.sortBone(parent);
if (bones.size == 1) {
skeleton.updateCache.add(this); skeleton.updateCache.add(this);
parent.sorted = false;
skeleton.sortReset(parent.children); skeleton.sortReset(parent.children);
} else { skeleton.constrained(parent);
Bone child = bones.items[1].bone; if (bones.size > 1) skeleton.constrained(bones.items[1].bone);
skeleton.resetCache(child);
skeleton.sortBone(child);
skeleton.updateCache.add(this);
skeleton.sortReset(parent.children);
child.sorted = true;
}
} }
boolean isSourceActive () { boolean isSourceActive () {
@ -112,9 +105,9 @@ public class IkConstraint extends Constraint<IkConstraint, IkConstraintData, IkC
/** Applies 1 bone IK. The target is specified in the world coordinate system. */ /** Applies 1 bone IK. The target is specified in the world coordinate system. */
static public void apply (Skeleton skeleton, BonePose bone, float targetX, float targetY, boolean compress, boolean stretch, static public void apply (Skeleton skeleton, BonePose bone, float targetX, float targetY, boolean compress, boolean stretch,
boolean uniform, float alpha) { boolean uniform, float mix) {
if (bone == null) throw new IllegalArgumentException("bone cannot be null."); if (bone == null) throw new IllegalArgumentException("bone cannot be null.");
if (bone.local == skeleton.update) bone.updateLocalTransform(skeleton); bone.modifyLocal(skeleton);
BonePose p = bone.bone.parent.applied; BonePose p = bone.bone.parent.applied;
float pa = p.a, pb = p.b, pc = p.c, pd = p.d; float pa = p.a, pb = p.b, pc = p.c, pd = p.d;
float rotationIK = -bone.shearX - bone.rotation, tx, ty; float rotationIK = -bone.shearX - bone.rotation, tx, ty;
@ -148,7 +141,7 @@ public class IkConstraint extends Constraint<IkConstraint, IkConstraintData, IkC
rotationIK -= 360; rotationIK -= 360;
else if (rotationIK < -180) // else if (rotationIK < -180) //
rotationIK += 360; rotationIK += 360;
bone.rotation += rotationIK * alpha; bone.rotation += rotationIK * mix;
if (compress || stretch) { if (compress || stretch) {
switch (bone.inherit) { switch (bone.inherit) {
case noScale, noScaleOrReflection -> { case noScale, noScaleOrReflection -> {
@ -160,25 +153,23 @@ public class IkConstraint extends Constraint<IkConstraint, IkConstraintData, IkC
if (b > 0.0001f) { if (b > 0.0001f) {
float dd = tx * tx + ty * ty; float dd = tx * tx + ty * ty;
if ((compress && dd < b * b) || (stretch && dd > b * b)) { if ((compress && dd < b * b) || (stretch && dd > b * b)) {
float s = ((float)Math.sqrt(dd) / b - 1) * alpha + 1; float s = ((float)Math.sqrt(dd) / b - 1) * mix + 1;
bone.scaleX *= s; bone.scaleX *= s;
if (uniform) bone.scaleY *= s; if (uniform) bone.scaleY *= s;
} }
} }
} }
bone.updateWorldTransform(skeleton);
bone.bone.resetUpdate(skeleton);
} }
/** Applies 2 bone IK. The target is specified in the world coordinate system. /** Applies 2 bone IK. The target is specified in the world coordinate system.
* @param child A direct descendant of the parent bone. */ * @param child A direct descendant of the parent bone. */
static public void apply (Skeleton skeleton, BonePose parent, BonePose child, float targetX, float targetY, int bendDir, static public void apply (Skeleton skeleton, BonePose parent, BonePose child, float targetX, float targetY, int bendDir,
boolean stretch, boolean uniform, float softness, float alpha) { boolean stretch, boolean uniform, float softness, float mix) {
if (parent == null) throw new IllegalArgumentException("parent cannot be null."); if (parent == null) throw new IllegalArgumentException("parent cannot be null.");
if (child == null) throw new IllegalArgumentException("child cannot be null."); if (child == null) throw new IllegalArgumentException("child cannot be null.");
if (parent.inherit != Inherit.normal || child.inherit != Inherit.normal) return; if (parent.inherit != Inherit.normal || child.inherit != Inherit.normal) return;
if (parent.local == skeleton.update) parent.updateLocalTransform(skeleton); parent.modifyLocal(skeleton);
if (child.local == skeleton.update) child.updateLocalTransform(skeleton); child.modifyLocal(skeleton);
float px = parent.x, py = parent.y, psx = parent.scaleX, psy = parent.scaleY, csx = child.scaleX; float px = parent.x, py = parent.y, psx = parent.scaleX, psy = parent.scaleY, csx = child.scaleX;
int os1, os2, s2; int os1, os2, s2;
if (psx < 0) { if (psx < 0) {
@ -218,10 +209,8 @@ public class IkConstraint extends Constraint<IkConstraint, IkConstraintData, IkC
float dx = (x * d - y * b) * id - px, dy = (y * a - x * c) * id - py; 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.bone.data.length * csx, a1, a2; float l1 = (float)Math.sqrt(dx * dx + dy * dy), l2 = child.bone.data.length * csx, a1, a2;
if (l1 < 0.0001f) { if (l1 < 0.0001f) {
apply(skeleton, parent, targetX, targetY, false, stretch, false, alpha); apply(skeleton, parent, targetX, targetY, false, stretch, false, mix);
child.rotation = 0; child.rotation = 0;
child.updateWorldTransform(skeleton);
child.bone.resetUpdate(skeleton);
return; return;
} }
x = targetX - pp.worldX; x = targetX - pp.worldX;
@ -250,7 +239,7 @@ public class IkConstraint extends Constraint<IkConstraint, IkConstraintData, IkC
cos = 1; cos = 1;
a2 = 0; a2 = 0;
if (stretch) { if (stretch) {
a = ((float)Math.sqrt(dd) / (l1 + l2) - 1) * alpha + 1; a = ((float)Math.sqrt(dd) / (l1 + l2) - 1) * mix + 1;
parent.scaleX *= a; parent.scaleX *= a;
if (uniform) parent.scaleY *= a; if (uniform) parent.scaleY *= a;
} }
@ -315,15 +304,12 @@ public class IkConstraint extends Constraint<IkConstraint, IkConstraintData, IkC
a1 -= 360; a1 -= 360;
else if (a1 < -180) // else if (a1 < -180) //
a1 += 360; a1 += 360;
parent.rotation += a1 * alpha; parent.rotation += a1 * mix;
parent.updateWorldTransform(skeleton);
a2 = ((a2 + os) * radDeg - child.shearX) * s2 + os2 - child.rotation; a2 = ((a2 + os) * radDeg - child.shearX) * s2 + os2 - child.rotation;
if (a2 > 180) if (a2 > 180)
a2 -= 360; a2 -= 360;
else if (a2 < -180) // else if (a2 < -180) //
a2 += 360; a2 += 360;
child.rotation += a2 * alpha; child.rotation += a2 * mix;
child.updateWorldTransform(skeleton);
parent.bone.resetUpdate(skeleton);
} }
} }

View File

@ -152,7 +152,7 @@ public class PathConstraint extends Constraint<PathConstraint, PathConstraintDat
BonePose p = slot.bone.applied; BonePose p = slot.bone.applied;
offsetRotation *= p.a * p.d - p.b * p.c > 0 ? degRad : -degRad; offsetRotation *= p.a * p.d - p.b * p.c > 0 ? degRad : -degRad;
} }
for (int i = 0, p = 3; i < boneCount; i++, p += 3) { for (int i = 0, p = 3, u = skeleton.update; i < boneCount; i++, p += 3) {
BonePose bone = bones[i]; BonePose bone = bones[i];
bone.worldX += (boneX - bone.worldX) * mixX; bone.worldX += (boneX - bone.worldX) * mixX;
bone.worldY += (boneY - bone.worldY) * mixY; bone.worldY += (boneY - bone.worldY) * mixY;
@ -196,8 +196,7 @@ public class PathConstraint extends Constraint<PathConstraint, PathConstraintDat
bone.c = sin * a + cos * c; bone.c = sin * a + cos * c;
bone.d = sin * b + cos * d; bone.d = sin * b + cos * d;
} }
bone.local = skeleton.update; bone.modifyWorld(u);
bone.bone.resetUpdate(skeleton);
} }
} }
@ -462,16 +461,16 @@ public class PathConstraint extends Constraint<PathConstraint, PathConstraintDat
void sort (Skeleton skeleton) { void sort (Skeleton skeleton) {
int slotIndex = slot.getData().index; int slotIndex = slot.getData().index;
Bone slotBone = slot.bone; Bone slotBone = slot.bone;
if (skeleton.skin != null) sortPathConstraintAttachment(skeleton, skeleton.skin, slotIndex, slotBone); if (skeleton.skin != null) sortPathSlot(skeleton, skeleton.skin, slotIndex, slotBone);
if (skeleton.data.defaultSkin != null && skeleton.data.defaultSkin != skeleton.skin) if (skeleton.data.defaultSkin != null && skeleton.data.defaultSkin != skeleton.skin)
sortPathConstraintAttachment(skeleton, skeleton.data.defaultSkin, slotIndex, slotBone); sortPathSlot(skeleton, skeleton.data.defaultSkin, slotIndex, slotBone);
sortPathConstraintAttachment(skeleton, slot.pose.attachment, slotBone); sortPath(skeleton, slot.pose.attachment, slotBone);
BonePose[] bones = this.bones.items; BonePose[] bones = this.bones.items;
int boneCount = this.bones.size; int boneCount = this.bones.size;
for (int i = 0; i < boneCount; i++) { for (int i = 0; i < boneCount; i++) {
Bone bone = bones[i].bone; Bone bone = bones[i].bone;
skeleton.resetCache(bone);
skeleton.sortBone(bone); skeleton.sortBone(bone);
skeleton.constrained(bone);
} }
skeleton.updateCache.add(this); skeleton.updateCache.add(this);
for (int i = 0; i < boneCount; i++) for (int i = 0; i < boneCount; i++)
@ -480,15 +479,15 @@ public class PathConstraint extends Constraint<PathConstraint, PathConstraintDat
bones[i].bone.sorted = true; bones[i].bone.sorted = true;
} }
private void sortPathConstraintAttachment (Skeleton skeleton, Skin skin, int slotIndex, Bone slotBone) { private void sortPathSlot (Skeleton skeleton, Skin skin, int slotIndex, Bone slotBone) {
Object[] entries = skin.attachments.orderedItems().items; Object[] entries = skin.attachments.orderedItems().items;
for (int i = 0, n = skin.attachments.size; i < n; i++) { for (int i = 0, n = skin.attachments.size; i < n; i++) {
var entry = (SkinEntry)entries[i]; var entry = (SkinEntry)entries[i];
if (entry.slotIndex == slotIndex) sortPathConstraintAttachment(skeleton, entry.attachment, slotBone); if (entry.slotIndex == slotIndex) sortPath(skeleton, entry.attachment, slotBone);
} }
} }
private void sortPathConstraintAttachment (Skeleton skeleton, Attachment attachment, Bone slotBone) { private void sortPath (Skeleton skeleton, Attachment attachment, Bone slotBone) {
if (!(attachment instanceof PathAttachment pathAttachment)) return; if (!(attachment instanceof PathAttachment pathAttachment)) return;
int[] pathBones = pathAttachment.getBones(); int[] pathBones = pathAttachment.getBones();
if (pathBones == null) if (pathBones == null)

View File

@ -263,16 +263,15 @@ public class PhysicsConstraint extends Constraint<PhysicsConstraint, PhysicsCons
tx = l * bone.a; tx = l * bone.a;
ty = l * bone.c; ty = l * bone.c;
} }
bone.local = skeleton.update; bone.modifyWorld(skeleton.update);
bone.bone.resetUpdate(skeleton);
} }
void sort (Skeleton skeleton) { void sort (Skeleton skeleton) {
Bone bone = this.bone.bone; Bone bone = this.bone.bone;
skeleton.sortBone(bone); skeleton.sortBone(bone);
skeleton.resetCache(bone);
skeleton.updateCache.add(this); skeleton.updateCache.add(this);
skeleton.sortReset(bone.children); skeleton.sortReset(bone.children);
skeleton.constrained(bone);
} }
boolean isSourceActive () { boolean isSourceActive () {

View File

@ -202,7 +202,7 @@ public class Skeleton {
if (updateCache[i] instanceof Bone bone) updateCache[i] = bone.applied; if (updateCache[i] instanceof Bone bone) updateCache[i] = bone.applied;
} }
void resetCache (Posed object) { void constrained (Posed object) {
if (object.pose == object.applied) { if (object.pose == object.applied) {
object.applied = object.constrained; object.applied = object.constrained;
resetCache.add(object); resetCache.add(object);

View File

@ -70,22 +70,10 @@ public class Slider extends Constraint<Slider, SliderData, SliderPose> {
pose.time = Math.max(0, pose.time); pose.time = Math.max(0, pose.time);
} }
SliderData data = this.data;
Timeline[] timelines = animation.timelines.items;
int timelineCount = animation.timelines.size;
Bone[] bones = skeleton.bones.items; Bone[] bones = skeleton.bones.items;
if (pose.mix == 1) { Timeline[] timelines = animation.timelines.items;
for (int i = 0; i < timelineCount; i++) for (int i = 0, n = animation.timelines.size; i < n; i++)
if (timelines[i] instanceof BoneTimeline timeline) bones[timeline.getBoneIndex()].resetUpdate(skeleton); if (timelines[i] instanceof BoneTimeline timeline) bones[timeline.getBoneIndex()].applied.modifyLocal(skeleton);
} else {
for (int i = 0; i < timelineCount; i++) {
if (timelines[i] instanceof BoneTimeline timeline) {
Bone bone = bones[timeline.getBoneIndex()];
bone.resetUpdate(skeleton);
if (bone.applied.local == skeleton.update) bone.applied.updateLocalTransform(skeleton);
}
}
}
SliderPose pose = applied; SliderPose pose = applied;
animation.apply(skeleton, pose.time, pose.time, data.loop, null, pose.mix, data.additive ? MixBlend.add : MixBlend.replace, animation.apply(skeleton, pose.time, pose.time, data.loop, null, pose.mix, data.additive ? MixBlend.add : MixBlend.replace,
@ -93,35 +81,32 @@ public class Slider extends Constraint<Slider, SliderData, SliderPose> {
} }
void sort (Skeleton skeleton) { void sort (Skeleton skeleton) {
Timeline[] timelines = data.animation.timelines.items; if (bone != null && !data.local) skeleton.sortBone(bone);
int timelineCount = data.animation.timelines.size;
Bone[] bones = skeleton.bones.items;
for (int i = 0; i < timelineCount; i++)
if (timelines[i] instanceof BoneTimeline timeline) skeleton.sortBone(bones[timeline.getBoneIndex()]);
skeleton.updateCache.add(this); skeleton.updateCache.add(this);
Timeline[] timelines = data.animation.timelines.items;
Bone[] bones = skeleton.bones.items;
Slot[] slots = skeleton.slots.items; Slot[] slots = skeleton.slots.items;
Constraint[] constraints = skeleton.constraints.items; Constraint[] constraints = skeleton.constraints.items;
PhysicsConstraint[] physics = skeleton.physics.items; PhysicsConstraint[] physics = skeleton.physics.items;
int physicsCount = skeleton.physics.size; int physicsCount = skeleton.physics.size;
for (int i = 0; i < timelineCount; i++) { for (int i = 0, n = data.animation.timelines.size; i < n; i++) {
Timeline t = timelines[i]; Timeline t = timelines[i];
if (t instanceof BoneTimeline timeline) { if (t instanceof BoneTimeline timeline) {
Bone bone = bones[timeline.getBoneIndex()]; Bone bone = bones[timeline.getBoneIndex()];
skeleton.resetCache(bone);
skeleton.sortReset(bone.children);
bone.sorted = false; bone.sorted = false;
skeleton.sortReset(bone.children);
skeleton.constrained(bone);
} else if (t instanceof SlotTimeline timeline) } else if (t instanceof SlotTimeline timeline)
skeleton.resetCache(slots[timeline.getSlotIndex()]); skeleton.constrained(slots[timeline.getSlotIndex()]);
else if (t instanceof PhysicsConstraintTimeline timeline) { else if (t instanceof PhysicsConstraintTimeline timeline) {
if (timeline.constraintIndex == -1) { if (timeline.constraintIndex == -1) {
for (int ii = 0; ii < physicsCount; ii++) for (int ii = 0; ii < physicsCount; ii++)
skeleton.resetCache(physics[ii]); skeleton.constrained(physics[ii]);
} else } else
skeleton.resetCache(constraints[timeline.constraintIndex]); skeleton.constrained(constraints[timeline.constraintIndex]);
} else if (t instanceof ConstraintTimeline timeline) // } else if (t instanceof ConstraintTimeline timeline) //
skeleton.resetCache(constraints[timeline.getConstraintIndex()]); skeleton.constrained(constraints[timeline.getConstraintIndex()]);
} }
} }

View File

@ -72,13 +72,16 @@ public class TransformConstraint extends Constraint<TransformConstraint, Transfo
float[] offsets = data.offsets; float[] offsets = data.offsets;
BonePose source = this.source.applied; BonePose source = this.source.applied;
int update = skeleton.update; int update = skeleton.update;
if (localSource && source.local == skeleton.update) source.updateLocalTransform(skeleton); if (localSource) source.modifyLocal(skeleton);
FromProperty[] fromItems = data.properties.items; FromProperty[] fromItems = data.properties.items;
int fn = data.properties.size; int fn = data.properties.size;
BonePose[] bones = this.bones.items; BonePose[] bones = this.bones.items;
for (int i = 0, n = this.bones.size; i < n; i++) { for (int i = 0, n = this.bones.size; i < n; i++) {
BonePose bone = bones[i]; BonePose bone = bones[i];
if (localTarget && bone.local == update) bone.updateLocalTransform(skeleton); if (localTarget)
bone.modifyLocal(skeleton);
else
bone.modifyWorld(update);
for (int f = 0; f < fn; f++) { for (int f = 0; f < fn; f++) {
FromProperty from = fromItems[f]; FromProperty from = fromItems[f];
float value = from.value(source, localSource, offsets) - from.offset; float value = from.value(source, localSource, offsets) - from.offset;
@ -97,28 +100,26 @@ public class TransformConstraint extends Constraint<TransformConstraint, Transfo
} }
} }
} }
if (localTarget)
bone.updateWorldTransform(skeleton);
else
bone.local = update;
bone.bone.resetUpdate(skeleton);
} }
} }
void sort (Skeleton skeleton) { void sort (Skeleton skeleton) {
skeleton.sortBone(source); if (!data.localSource) skeleton.sortBone(source);
BonePose[] bones = this.bones.items; BonePose[] bones = this.bones.items;
int boneCount = this.bones.size; int boneCount = this.bones.size;
for (int i = 0; i < boneCount; i++) { boolean worldTarget = !data.localTarget;
Bone bone = bones[i].bone; if (worldTarget) {
skeleton.resetCache(bone); for (int i = 0; i < boneCount; i++)
skeleton.sortBone(bone); skeleton.sortBone(bones[i].bone);
} }
skeleton.updateCache.add(this); skeleton.updateCache.add(this);
for (int i = 0; i < boneCount; i++) {
Bone bone = bones[i].bone;
skeleton.sortReset(bone.children);
skeleton.constrained(bone);
}
for (int i = 0; i < boneCount; i++) for (int i = 0; i < boneCount; i++)
skeleton.sortReset(bones[i].bone.children); bones[i].bone.sorted = worldTarget;
for (int i = 0; i < boneCount; i++)
bones[i].bone.sorted = true;
} }
boolean isSourceActive () { boolean isSourceActive () {