diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PhysicsConstraint.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PhysicsConstraint.java index baadc420e..e84cae0d7 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PhysicsConstraint.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PhysicsConstraint.java @@ -29,6 +29,7 @@ package com.esotericsoftware.spine; +import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.utils.Array; import com.esotericsoftware.spine.PhysicsConstraintData.Node; @@ -47,10 +48,15 @@ public class PhysicsConstraint implements Updatable { boolean active; + private final Skeleton skeleton; + float remaining, lastTime; + final Vector2 temp = new Vector2(); + public PhysicsConstraint (PhysicsConstraintData data, Skeleton skeleton) { if (data == null) throw new IllegalArgumentException("data cannot be null."); if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); this.data = data; + this.skeleton = skeleton; nodes = new Array(data.nodes.size); for (NodeData nodeData : data.nodes) @@ -69,10 +75,10 @@ public class PhysicsConstraint implements Updatable { } /** Copy constructor. */ - public PhysicsConstraint (PhysicsConstraint constraint, Skeleton skeleton) { + public PhysicsConstraint (PhysicsConstraint constraint) { if (constraint == null) throw new IllegalArgumentException("constraint cannot be null."); - if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); data = constraint.data; + skeleton = constraint.skeleton; nodes = new Array(constraint.nodes.size); for (Node node : constraint.nodes) @@ -91,6 +97,9 @@ public class PhysicsConstraint implements Updatable { } public void setToSetupPose () { + remaining = 0; + lastTime = skeleton.time; + Object[] nodes = this.nodes.items; for (int i = 0, n = this.nodes.size; i < n; i++) ((Node)nodes[i]).setToSetupPose(); @@ -110,7 +119,29 @@ public class PhysicsConstraint implements Updatable { /** Applies the constraint to the constrained bones. */ public void update () { - // BOZO! - Physics. + Object[] nodes = this.nodes.items; + int nodeCount = this.nodes.size; + Vector2 temp = this.temp; + for (int i = 0; i < nodeCount; i++) { + Node node = (Node)nodes[i]; + if (node.parentBone == null) continue; + node.parentBone.localToWorld(temp.set(node.data.x, node.data.y)); + node.x = temp.x; + node.y = temp.y; + } + + Object[] springs = this.springs.items; + int springCount = this.springs.size; + + remaining += lastTime - skeleton.time; + lastTime = skeleton.time; + while (remaining > 0.016f) { + remaining -= 0.016f; + for (int i = 0; i < springCount; i++) + ((Spring)springs[i]).update(); + for (int i = 0; i < nodeCount; i++) + ((Node)nodes[i]).update(this); + } } public Array getNodes () { diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PhysicsConstraintData.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PhysicsConstraintData.java index cf273fb42..8d14f6d23 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PhysicsConstraintData.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PhysicsConstraintData.java @@ -31,6 +31,7 @@ package com.esotericsoftware.spine; import static com.esotericsoftware.spine.utils.SpineUtils.*; +import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Null; @@ -111,9 +112,13 @@ public class PhysicsConstraintData extends ConstraintData { static public class Node { public final NodeData data; - public final @Null Bone parentBone; - public final Bone[] bones; - public float x, y, px, py, ax, ay; + public @Null Bone parentBone; + public Bone[] bones; + public float x, y, vx, vy, ax, ay; + + Node (NodeData data) { // Editor. + this.data = data; + } public Node (NodeData data, Skeleton skeleton) { this.data = data; @@ -134,8 +139,8 @@ public class PhysicsConstraintData extends ConstraintData { arraycopy(node.bones, 0, bones, 0, bones.length); x = node.x; y = node.y; - px = node.px; - py = node.py; + vx = node.vx; + vy = node.vy; ax = node.ax; ay = node.ay; } @@ -143,11 +148,30 @@ public class PhysicsConstraintData extends ConstraintData { public void setToSetupPose () { x = data.x; y = data.y; - px = x; - py = y; + vx = 0; + vy = 0; ax = 0; ay = 0; } + + public void update (PhysicsConstraint constraint) { + if (parentBone != null) { + vx = 0; + vy = 0; + ax = 0; + ay = 0; + return; + } + float friction = 0.98f; + vx += ax - constraint.wind / friction; + vy += ay - constraint.gravity / friction; + ax = 0; + ay = 0; + x += vx; + y += vy; + vx *= friction; + vy *= friction; + } } static public class SpringData { @@ -159,10 +183,14 @@ public class PhysicsConstraintData extends ConstraintData { static public class Spring { public final SpringData data; - public final Node node1, node2; - public final Bone[] bones; + public Node node1, node2; + public Bone[] bones; public float length, strength, damping; + Spring (SpringData data) { // Editor. + this.data = data; + } + public Spring (SpringData data, PhysicsConstraint constraint, Skeleton skeleton) { this.data = data; @@ -192,5 +220,34 @@ public class PhysicsConstraintData extends ConstraintData { strength = data.strength; damping = data.damping; } + + public void update () { + float strength = 1f; + + float x = node2.x - node1.x, y = node2.y - node1.y; + float d = (float)Math.sqrt(Math.max(x * x + y * y, 0.00001f)); + if (data.rope && d <= length) return; + + Vector2 unit_vector = new Vector2(x / d, y / d); + float distance_error = unit_vector.dot(x, y) - length; + float distance_impulse = 0.33f * strength * distance_error; + + float vx = node2.vx - node1.vx, vy = node2.vy - node1.vy; + float velocity_error = unit_vector.dot(vx, vy); + float velocity_impulse = 0.2f * velocity_error; + + float impulse = -(distance_impulse + velocity_impulse); + Vector2 f = unit_vector.scl(impulse); + + node1.ax -= f.x - vx * damping; + node1.ay -= f.y - vy * damping; + node2.ax += f.x - vx * damping; + node2.ay += f.y - vy * damping; + + if (!data.stretch && node2.parentBone == null) { + node2.x = node1.x + length * x / d; + node2.y = node1.y + length * y / d; + } + } } } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java index b46f98fd1..832bc27df 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java @@ -60,8 +60,9 @@ public class Skeleton { final Array updateCache = new Array(); @Null Skin skin; final Color color; - float scaleX = 1, scaleY = 1; float x, y; + float scaleX = 1, scaleY = 1; + float time; public Skeleton (SkeletonData data) { if (data == null) throw new IllegalArgumentException("data cannot be null."); @@ -153,12 +154,15 @@ public class Skeleton { physicsConstraints = new Array(skeleton.physicsConstraints.size); for (PhysicsConstraint physicsConstraint : skeleton.physicsConstraints) - physicsConstraints.add(new PhysicsConstraint(physicsConstraint, this)); + physicsConstraints.add(new PhysicsConstraint(physicsConstraint)); skin = skeleton.skin; color = new Color(skeleton.color); + x = skeleton.x; + y = skeleton.y; scaleX = skeleton.scaleX; scaleY = skeleton.scaleY; + time = skeleton.time; updateCache(); } @@ -791,6 +795,22 @@ public class Skeleton { this.y = y; } + /** Returns the skeleton's time. This is used for time-based manipulations, such as {@link PhysicsConstraint}. + *

+ * See {@link #update(float)}. */ + public float getTime () { + return time; + } + + public void setTime (float time) { + this.time = time; + } + + /** Increments the skeleton's {@link #time}. */ + public void update (float delta) { + time += delta; + } + public String toString () { return data.name != null ? data.name : super.toString(); }