mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-03-09 12:16:53 +08:00
IK constraints for spine-libgdx.
Note Spine doesn't yet export IK data, so spine-libgdx doesn't yet load it. Soon!
This commit is contained in:
parent
88f805a74e
commit
6444e8e934
@ -273,7 +273,7 @@ public class Animation {
|
|||||||
float prevFrameValue = frames[frameIndex - 1];
|
float prevFrameValue = frames[frameIndex - 1];
|
||||||
float frameTime = frames[frameIndex];
|
float frameTime = frames[frameIndex];
|
||||||
float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime), 0, 1);
|
float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime), 0, 1);
|
||||||
percent = getCurvePercent(frameIndex / 2 - 1, percent);
|
percent = getCurvePercent((frameIndex >> 1) - 1, percent);
|
||||||
|
|
||||||
float amount = frames[frameIndex + FRAME_VALUE] - prevFrameValue;
|
float amount = frames[frameIndex + FRAME_VALUE] - prevFrameValue;
|
||||||
while (amount > 180)
|
while (amount > 180)
|
||||||
@ -657,7 +657,7 @@ public class Animation {
|
|||||||
|
|
||||||
float[] frames = this.frames;
|
float[] frames = this.frames;
|
||||||
if (time < frames[0]) return; // Time is before first frame.
|
if (time < frames[0]) return; // Time is before first frame.
|
||||||
|
|
||||||
float[][] frameVertices = this.frameVertices;
|
float[][] frameVertices = this.frameVertices;
|
||||||
int vertexCount = frameVertices[0].length;
|
int vertexCount = frameVertices[0].length;
|
||||||
|
|
||||||
@ -700,4 +700,63 @@ public class Animation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static public class IkConstraintTimeline extends CurveTimeline {
|
||||||
|
static private final int PREV_FRAME_TIME = -2;
|
||||||
|
static private final int FRAME_VALUE = 1;
|
||||||
|
|
||||||
|
int ikConstraintIndex;
|
||||||
|
private final float[] frames; // time, mix, ...
|
||||||
|
private final int[] bendDirections;
|
||||||
|
|
||||||
|
public IkConstraintTimeline (int frameCount) {
|
||||||
|
super(frameCount);
|
||||||
|
frames = new float[frameCount * 2];
|
||||||
|
bendDirections = new int[frameCount];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIkConstraintIndex (int ikConstraint) {
|
||||||
|
this.ikConstraintIndex = ikConstraint;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getIkConstraintIndex () {
|
||||||
|
return ikConstraintIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float[] getFrames () {
|
||||||
|
return frames;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets the time and mix and bend direction of the specified keyframe. */
|
||||||
|
public void setFrame (int frameIndex, float time, float mix, int bendDirection) {
|
||||||
|
bendDirections[frameIndex] = bendDirection;
|
||||||
|
frameIndex *= 2;
|
||||||
|
frames[frameIndex] = time;
|
||||||
|
frames[frameIndex + 1] = mix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha) {
|
||||||
|
float[] frames = this.frames;
|
||||||
|
if (time < frames[0]) return; // Time is before first frame.
|
||||||
|
|
||||||
|
IkConstraint ikConstraint = skeleton.ikConstraints.get(ikConstraintIndex);
|
||||||
|
|
||||||
|
if (time >= frames[frames.length - 2]) { // Time is after last frame.
|
||||||
|
ikConstraint.mix += (frames[frames.length - 1] - ikConstraint.mix) * alpha;
|
||||||
|
ikConstraint.bendDirection = bendDirections[bendDirections.length - 1];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interpolate between the previous frame and the current frame.
|
||||||
|
int frameIndex = binarySearch(frames, time, 2);
|
||||||
|
float prevFrameValue = frames[frameIndex - 1];
|
||||||
|
float frameTime = frames[frameIndex];
|
||||||
|
float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime), 0, 1);
|
||||||
|
percent = getCurvePercent((frameIndex >> 1) - 1, percent);
|
||||||
|
|
||||||
|
float mix = prevFrameValue + (frames[frameIndex + FRAME_VALUE] - prevFrameValue) * percent;
|
||||||
|
ikConstraint.mix += (mix - ikConstraint.mix) * alpha;
|
||||||
|
ikConstraint.bendDirection = bendDirections[(frameIndex - 2) >> 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,17 +34,18 @@ import static com.badlogic.gdx.math.Matrix3.*;
|
|||||||
|
|
||||||
import com.badlogic.gdx.math.MathUtils;
|
import com.badlogic.gdx.math.MathUtils;
|
||||||
import com.badlogic.gdx.math.Matrix3;
|
import com.badlogic.gdx.math.Matrix3;
|
||||||
|
import com.badlogic.gdx.math.Vector2;
|
||||||
|
|
||||||
public class Bone {
|
public class Bone {
|
||||||
final BoneData data;
|
final BoneData data;
|
||||||
final Bone parent;
|
final Bone parent;
|
||||||
float x, y;
|
float x, y;
|
||||||
float rotation;
|
float rotation, rotationIK;
|
||||||
float scaleX, scaleY;
|
float scaleX, scaleY;
|
||||||
|
|
||||||
float m00, m01, worldX; // a b x
|
float m00, m01, worldX; // a b x
|
||||||
float m10, m11, worldY; // c d y
|
float m10, m11, worldY; // c d y
|
||||||
float worldRotation;
|
float worldRotation, worldCos, worldSin;
|
||||||
float worldScaleX, worldScaleY;
|
float worldScaleX, worldScaleY;
|
||||||
|
|
||||||
/** @param parent May be null. */
|
/** @param parent May be null. */
|
||||||
@ -64,6 +65,7 @@ public class Bone {
|
|||||||
x = bone.x;
|
x = bone.x;
|
||||||
y = bone.y;
|
y = bone.y;
|
||||||
rotation = bone.rotation;
|
rotation = bone.rotation;
|
||||||
|
rotationIK = bone.rotationIK;
|
||||||
scaleX = bone.scaleX;
|
scaleX = bone.scaleX;
|
||||||
scaleY = bone.scaleY;
|
scaleY = bone.scaleY;
|
||||||
}
|
}
|
||||||
@ -71,6 +73,7 @@ public class Bone {
|
|||||||
/** Computes the world SRT using the parent bone and the local SRT. */
|
/** Computes the world SRT using the parent bone and the local SRT. */
|
||||||
public void updateWorldTransform (boolean flipX, boolean flipY) {
|
public void updateWorldTransform (boolean flipX, boolean flipY) {
|
||||||
Bone parent = this.parent;
|
Bone parent = this.parent;
|
||||||
|
float x = this.x, y = this.y;
|
||||||
if (parent != null) {
|
if (parent != null) {
|
||||||
worldX = x * parent.m00 + y * parent.m01 + parent.worldX;
|
worldX = x * parent.m00 + y * parent.m01 + parent.worldX;
|
||||||
worldY = x * parent.m10 + y * parent.m11 + parent.worldY;
|
worldY = x * parent.m10 + y * parent.m11 + parent.worldY;
|
||||||
@ -81,16 +84,18 @@ public class Bone {
|
|||||||
worldScaleX = scaleX;
|
worldScaleX = scaleX;
|
||||||
worldScaleY = scaleY;
|
worldScaleY = scaleY;
|
||||||
}
|
}
|
||||||
worldRotation = data.inheritRotation ? parent.worldRotation + rotation : rotation;
|
worldRotation = data.inheritRotation ? parent.worldRotation + rotationIK : rotationIK;
|
||||||
} else {
|
} else {
|
||||||
worldX = flipX ? -x : x;
|
worldX = flipX ? -x : x;
|
||||||
worldY = flipY ? -y : y;
|
worldY = flipY ? -y : y;
|
||||||
worldScaleX = scaleX;
|
worldScaleX = scaleX;
|
||||||
worldScaleY = scaleY;
|
worldScaleY = scaleY;
|
||||||
worldRotation = rotation;
|
worldRotation = rotationIK;
|
||||||
}
|
}
|
||||||
float cos = MathUtils.cosDeg(worldRotation);
|
float cos = MathUtils.cosDeg(worldRotation);
|
||||||
float sin = MathUtils.sinDeg(worldRotation);
|
float sin = MathUtils.sinDeg(worldRotation);
|
||||||
|
worldCos = cos;
|
||||||
|
worldSin = sin;
|
||||||
m00 = cos * worldScaleX;
|
m00 = cos * worldScaleX;
|
||||||
m10 = sin * worldScaleX;
|
m10 = sin * worldScaleX;
|
||||||
m01 = -sin * worldScaleY;
|
m01 = -sin * worldScaleY;
|
||||||
@ -110,6 +115,7 @@ public class Bone {
|
|||||||
x = data.x;
|
x = data.x;
|
||||||
y = data.y;
|
y = data.y;
|
||||||
rotation = data.rotation;
|
rotation = data.rotation;
|
||||||
|
rotationIK = rotation;
|
||||||
scaleX = data.scaleX;
|
scaleX = data.scaleX;
|
||||||
scaleY = data.scaleY;
|
scaleY = data.scaleY;
|
||||||
}
|
}
|
||||||
@ -143,6 +149,7 @@ public class Bone {
|
|||||||
this.y = y;
|
this.y = y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns the forward kinetics rotation. */
|
||||||
public float getRotation () {
|
public float getRotation () {
|
||||||
return rotation;
|
return rotation;
|
||||||
}
|
}
|
||||||
@ -151,6 +158,15 @@ public class Bone {
|
|||||||
this.rotation = rotation;
|
this.rotation = rotation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns the inverse kinetics rotation, as calculated by any IK constraints. */
|
||||||
|
public float getRotationIK () {
|
||||||
|
return rotationIK;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRotationIK (float rotationIK) {
|
||||||
|
this.rotationIK = rotationIK;
|
||||||
|
}
|
||||||
|
|
||||||
public float getScaleX () {
|
public float getScaleX () {
|
||||||
return scaleX;
|
return scaleX;
|
||||||
}
|
}
|
||||||
@ -205,6 +221,14 @@ public class Bone {
|
|||||||
return worldRotation;
|
return worldRotation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public float getWorldCos () {
|
||||||
|
return worldCos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getWorldSin () {
|
||||||
|
return worldSin;
|
||||||
|
}
|
||||||
|
|
||||||
public float getWorldScaleX () {
|
public float getWorldScaleX () {
|
||||||
return worldScaleX;
|
return worldScaleX;
|
||||||
}
|
}
|
||||||
@ -228,6 +252,23 @@ public class Bone {
|
|||||||
return worldTransform;
|
return worldTransform;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Vector2 worldToLocal (Vector2 world) {
|
||||||
|
float x = world.x - worldX;
|
||||||
|
float y = world.y - worldY;
|
||||||
|
float cos = worldCos;
|
||||||
|
float sin = -worldSin;
|
||||||
|
world.x = (x * cos - y * sin) / worldScaleX;
|
||||||
|
world.y = (x * sin + y * cos) / worldScaleY;
|
||||||
|
return world;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2 localToWorld (Vector2 local) {
|
||||||
|
float x = local.x, y = local.y;
|
||||||
|
local.x = x * m00 + y * m01 + worldX;
|
||||||
|
local.y = x * m10 + y * m11 + worldY;
|
||||||
|
return local;
|
||||||
|
}
|
||||||
|
|
||||||
public String toString () {
|
public String toString () {
|
||||||
return data.name;
|
return data.name;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -98,6 +98,11 @@ public class BoneData {
|
|||||||
this.y = y;
|
this.y = y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setPosition (float x, float y) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
public float getRotation () {
|
public float getRotation () {
|
||||||
return rotation;
|
return rotation;
|
||||||
}
|
}
|
||||||
@ -122,6 +127,11 @@ public class BoneData {
|
|||||||
this.scaleY = scaleY;
|
this.scaleY = scaleY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setScale (float scaleX, float scaleY) {
|
||||||
|
this.scaleX = scaleX;
|
||||||
|
this.scaleY = scaleY;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean getInheritScale () {
|
public boolean getInheritScale () {
|
||||||
return inheritScale;
|
return inheritScale;
|
||||||
}
|
}
|
||||||
|
|||||||
132
spine-libgdx/src/com/esotericsoftware/spine/IkConstraint.java
Normal file
132
spine-libgdx/src/com/esotericsoftware/spine/IkConstraint.java
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
|
||||||
|
package com.esotericsoftware.spine;
|
||||||
|
|
||||||
|
import com.badlogic.gdx.math.MathUtils;
|
||||||
|
import com.badlogic.gdx.math.Vector2;
|
||||||
|
import com.badlogic.gdx.utils.Array;
|
||||||
|
|
||||||
|
public class IkConstraint {
|
||||||
|
static private final Vector2 temp = new Vector2();
|
||||||
|
|
||||||
|
final IkConstraintData data;
|
||||||
|
final Array<Bone> bones;
|
||||||
|
Bone target;
|
||||||
|
float mix = 1;
|
||||||
|
int bendDirection;
|
||||||
|
|
||||||
|
public IkConstraint (IkConstraintData data, Skeleton skeleton) {
|
||||||
|
this.data = data;
|
||||||
|
mix = data.mix;
|
||||||
|
bendDirection = data.bendDirection;
|
||||||
|
|
||||||
|
bones = new Array(data.bones.size);
|
||||||
|
if (skeleton != null) {
|
||||||
|
for (BoneData boneData : data.bones)
|
||||||
|
bones.add(skeleton.findBone(boneData.name));
|
||||||
|
target = skeleton.findBone(data.target.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Copy constructor. */
|
||||||
|
public IkConstraint (IkConstraint ikConstraint) {
|
||||||
|
data = ikConstraint.data;
|
||||||
|
bones = new Array(ikConstraint.bones);
|
||||||
|
target = ikConstraint.target;
|
||||||
|
mix = ikConstraint.mix;
|
||||||
|
bendDirection = ikConstraint.bendDirection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void apply () {
|
||||||
|
Bone target = this.target;
|
||||||
|
Array<Bone> bones = this.bones;
|
||||||
|
apply(bones.first(), bones.get(1), target.worldX, target.worldY, bendDirection, mix);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Array<Bone> getBones () {
|
||||||
|
return bones;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bone getTarget () {
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTarget (Bone target) {
|
||||||
|
this.target = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getMix () {
|
||||||
|
return mix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMix (float mix) {
|
||||||
|
this.mix = mix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBendDirection () {
|
||||||
|
return bendDirection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBendDirection (int bendDirection) {
|
||||||
|
this.bendDirection = bendDirection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IkConstraintData getData () {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString () {
|
||||||
|
return data.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Adjusts the parent and child bone rotations so the tip of the child is as close to the target position as possible. The
|
||||||
|
* target is specified in the world coordinate system.
|
||||||
|
* @param child Any descendant bone of the parent. */
|
||||||
|
static public void apply (Bone parent, Bone child, float targetX, float targetY, int bendDirection, float alpha) {
|
||||||
|
float childRotation = child.rotation, parentRotation = parent.rotation;
|
||||||
|
if (alpha == 0) {
|
||||||
|
child.rotationIK = childRotation;
|
||||||
|
parent.rotationIK = parentRotation;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Vector2 position = temp;
|
||||||
|
Bone parentParent = parent.parent;
|
||||||
|
if (parentParent != null) {
|
||||||
|
parentParent.worldToLocal(position.set(targetX, targetY));
|
||||||
|
targetX = (position.x - parent.x) * parentParent.worldScaleX;
|
||||||
|
targetY = (position.y - parent.y) * parentParent.worldScaleY;
|
||||||
|
} else {
|
||||||
|
targetX -= parent.x;
|
||||||
|
targetY -= parent.y;
|
||||||
|
}
|
||||||
|
if (child.parent == parent)
|
||||||
|
position.set(child.x, child.y);
|
||||||
|
else
|
||||||
|
parent.worldToLocal(child.parent.localToWorld(position.set(child.x, child.y)));
|
||||||
|
float childX = position.x * parent.worldScaleX, childY = position.y * parent.worldScaleY;
|
||||||
|
float offset = (float)Math.atan2(childY, childX);
|
||||||
|
float len1 = (float)Math.sqrt(childX * childX + childY * childY), len2 = child.data.length * child.worldScaleX;
|
||||||
|
// Based on code by Ryan Juckett with permission: Copyright (c) 2008-2009 Ryan Juckett, http://www.ryanjuckett.com/
|
||||||
|
float cosDenom = 2 * len1 * len2;
|
||||||
|
if (cosDenom < 0.0001f) {
|
||||||
|
child.rotationIK = childRotation
|
||||||
|
+ ((float)Math.atan2(targetY, targetX) * MathUtils.radDeg - parentRotation - childRotation) * alpha;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
float cos = MathUtils.clamp((targetX * targetX + targetY * targetY - len1 * len1 - len2 * len2) / cosDenom, -1, 1);
|
||||||
|
float childAngle = (float)Math.acos(cos) * bendDirection;
|
||||||
|
float adjacent = len1 + len2 * cos, opposite = len2 * MathUtils.sin(childAngle);
|
||||||
|
float parentAngle = (float)Math.atan2(targetY * adjacent - targetX * opposite, targetX * adjacent + targetY * opposite);
|
||||||
|
float rotation = (parentAngle - offset) * MathUtils.radDeg - parentRotation;
|
||||||
|
if (rotation > 180)
|
||||||
|
rotation -= 360;
|
||||||
|
else if (rotation < -180) //
|
||||||
|
rotation += 360;
|
||||||
|
parent.rotationIK = parentRotation + rotation * alpha;
|
||||||
|
rotation = (childAngle + offset) * MathUtils.radDeg - childRotation;
|
||||||
|
if (rotation > 180)
|
||||||
|
rotation -= 360;
|
||||||
|
else if (rotation < -180) //
|
||||||
|
rotation += 360;
|
||||||
|
child.rotationIK = childRotation + (rotation + parent.worldRotation - child.parent.worldRotation) * alpha;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
|
||||||
|
package com.esotericsoftware.spine;
|
||||||
|
|
||||||
|
import com.badlogic.gdx.utils.Array;
|
||||||
|
|
||||||
|
public class IkConstraintData {
|
||||||
|
final String name;
|
||||||
|
final Array<BoneData> bones = new Array();
|
||||||
|
BoneData target;
|
||||||
|
int bendDirection = 1;
|
||||||
|
float mix = 1;
|
||||||
|
|
||||||
|
public IkConstraintData (String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName () {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Array<BoneData> getBones () {
|
||||||
|
return bones;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BoneData getTarget () {
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTarget (BoneData target) {
|
||||||
|
this.target = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBendDirection () {
|
||||||
|
return bendDirection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBendDirection (int bendDirection) {
|
||||||
|
this.bendDirection = bendDirection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getMix () {
|
||||||
|
return mix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMix (float mix) {
|
||||||
|
this.mix = mix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString () {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -39,6 +39,8 @@ public class Skeleton {
|
|||||||
final SkeletonData data;
|
final SkeletonData data;
|
||||||
final Array<Bone> bones;
|
final Array<Bone> bones;
|
||||||
final Array<Slot> slots;
|
final Array<Slot> slots;
|
||||||
|
final Array<IkConstraint> ikConstraints;
|
||||||
|
private final Array<Array<Bone>> updateBonesCache = new Array();
|
||||||
Array<Slot> drawOrder;
|
Array<Slot> drawOrder;
|
||||||
Skin skin;
|
Skin skin;
|
||||||
final Color color;
|
final Color color;
|
||||||
@ -65,7 +67,13 @@ public class Skeleton {
|
|||||||
drawOrder.add(slot);
|
drawOrder.add(slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ikConstraints = new Array(data.ikConstraints.size);
|
||||||
|
for (IkConstraintData ikConstraintData : data.ikConstraints)
|
||||||
|
ikConstraints.add(new IkConstraint(ikConstraintData, this));
|
||||||
|
|
||||||
color = new Color(1, 1, 1, 1);
|
color = new Color(1, 1, 1, 1);
|
||||||
|
|
||||||
|
updateCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Copy constructor. */
|
/** Copy constructor. */
|
||||||
@ -82,26 +90,84 @@ public class Skeleton {
|
|||||||
slots = new Array(skeleton.slots.size);
|
slots = new Array(skeleton.slots.size);
|
||||||
for (Slot slot : skeleton.slots) {
|
for (Slot slot : skeleton.slots) {
|
||||||
Bone bone = bones.get(skeleton.bones.indexOf(slot.bone, true));
|
Bone bone = bones.get(skeleton.bones.indexOf(slot.bone, true));
|
||||||
Slot newSlot = new Slot(slot, this, bone);
|
slots.add(new Slot(slot, this, bone));
|
||||||
slots.add(newSlot);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
drawOrder = new Array(slots.size);
|
drawOrder = new Array(slots.size);
|
||||||
for (Slot slot : skeleton.drawOrder)
|
for (Slot slot : skeleton.drawOrder)
|
||||||
drawOrder.add(slots.get(skeleton.slots.indexOf(slot, true)));
|
drawOrder.add(slots.get(skeleton.slots.indexOf(slot, true)));
|
||||||
|
|
||||||
|
ikConstraints = new Array(skeleton.ikConstraints.size);
|
||||||
|
for (IkConstraint ikConstraint : skeleton.ikConstraints)
|
||||||
|
ikConstraints.add(new IkConstraint(ikConstraint));
|
||||||
|
|
||||||
skin = skeleton.skin;
|
skin = skeleton.skin;
|
||||||
color = new Color(skeleton.color);
|
color = new Color(skeleton.color);
|
||||||
time = skeleton.time;
|
time = skeleton.time;
|
||||||
|
|
||||||
|
updateCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Updates the world transform for each bone. */
|
/** Caches information about bones and IK constraints. Must be called if bones or IK constraints are added or removed. */
|
||||||
|
public void updateCache () {
|
||||||
|
Array<Array<Bone>> updateBonesCache = this.updateBonesCache;
|
||||||
|
Array<IkConstraint> ikConstraints = this.ikConstraints;
|
||||||
|
int ikConstraintsCount = ikConstraints.size;
|
||||||
|
|
||||||
|
int arrayCount = ikConstraintsCount + 1;
|
||||||
|
updateBonesCache.truncate(arrayCount);
|
||||||
|
for (int i = 0, n = updateBonesCache.size; i < n; i++)
|
||||||
|
updateBonesCache.get(i).clear();
|
||||||
|
while (updateBonesCache.size < arrayCount)
|
||||||
|
updateBonesCache.add(new Array());
|
||||||
|
|
||||||
|
Array<Bone> nonIkBones = updateBonesCache.first();
|
||||||
|
|
||||||
|
outer:
|
||||||
|
for (int i = 0, n = bones.size; i < n; i++) {
|
||||||
|
Bone bone = bones.get(i);
|
||||||
|
Bone current = bone;
|
||||||
|
do {
|
||||||
|
for (int ii = 0; ii < ikConstraintsCount; ii++) {
|
||||||
|
IkConstraint ikConstraint = ikConstraints.get(ii);
|
||||||
|
Bone parent = ikConstraint.bones.first();
|
||||||
|
Bone child = ikConstraint.bones.peek();
|
||||||
|
while (true) {
|
||||||
|
if (current == child) {
|
||||||
|
updateBonesCache.get(ii).add(bone);
|
||||||
|
updateBonesCache.get(ii + 1).add(bone);
|
||||||
|
continue outer;
|
||||||
|
}
|
||||||
|
if (child == parent) break;
|
||||||
|
child = child.parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
current = current.parent;
|
||||||
|
} while (current != null);
|
||||||
|
nonIkBones.add(bone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Updates the world transform for each bone and applies IK constraints. */
|
||||||
public void updateWorldTransform () {
|
public void updateWorldTransform () {
|
||||||
|
Array<Bone> bones = this.bones;
|
||||||
|
for (int i = 0, nn = bones.size; i < nn; i++) {
|
||||||
|
Bone bone = bones.get(i);
|
||||||
|
bone.rotationIK = bone.rotation;
|
||||||
|
}
|
||||||
boolean flipX = this.flipX;
|
boolean flipX = this.flipX;
|
||||||
boolean flipY = this.flipY;
|
boolean flipY = this.flipY;
|
||||||
Array<Bone> bones = this.bones;
|
Array<Array<Bone>> updateBonesCache = this.updateBonesCache;
|
||||||
for (int i = 0, n = bones.size; i < n; i++)
|
Array<IkConstraint> ikConstraints = this.ikConstraints;
|
||||||
bones.get(i).updateWorldTransform(flipX, flipY);
|
int i = 0, last = updateBonesCache.size - 1;
|
||||||
|
while (true) {
|
||||||
|
Array<Bone> updateBones = updateBonesCache.get(i);
|
||||||
|
for (int ii = 0, nn = updateBones.size; ii < nn; ii++)
|
||||||
|
updateBones.get(ii).updateWorldTransform(flipX, flipY);
|
||||||
|
if (i == last) break;
|
||||||
|
ikConstraints.get(i).apply();
|
||||||
|
i++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sets the bones and slots to their setup pose values. */
|
/** Sets the bones and slots to their setup pose values. */
|
||||||
@ -114,6 +180,13 @@ public class Skeleton {
|
|||||||
Array<Bone> bones = this.bones;
|
Array<Bone> bones = this.bones;
|
||||||
for (int i = 0, n = bones.size; i < n; i++)
|
for (int i = 0, n = bones.size; i < n; i++)
|
||||||
bones.get(i).setToSetupPose();
|
bones.get(i).setToSetupPose();
|
||||||
|
|
||||||
|
Array<IkConstraint> ikConstraints = this.ikConstraints;
|
||||||
|
for (int i = 0, n = ikConstraints.size; i < n; i++) {
|
||||||
|
IkConstraint ikConstraint = ikConstraints.get(i);
|
||||||
|
ikConstraint.bendDirection = ikConstraint.data.bendDirection;
|
||||||
|
ikConstraint.mix = ikConstraint.data.mix;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSlotsToSetupPose () {
|
public void setSlotsToSetupPose () {
|
||||||
@ -263,6 +336,21 @@ public class Skeleton {
|
|||||||
throw new IllegalArgumentException("Slot not found: " + slotName);
|
throw new IllegalArgumentException("Slot not found: " + slotName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Array<IkConstraint> getIkConstraints () {
|
||||||
|
return ikConstraints;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return May be null. */
|
||||||
|
public IkConstraint findIkConstraint (String ikConstraintName) {
|
||||||
|
if (ikConstraintName == null) throw new IllegalArgumentException("ikConstraintName cannot be null.");
|
||||||
|
Array<IkConstraint> ikConstraints = this.ikConstraints;
|
||||||
|
for (int i = 0, n = ikConstraints.size; i < n; i++) {
|
||||||
|
IkConstraint ikConstraint = ikConstraints.get(i);
|
||||||
|
if (ikConstraint.data.name.equals(ikConstraintName)) return ikConstraint;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public Color getColor () {
|
public Color getColor () {
|
||||||
return color;
|
return color;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -69,6 +69,7 @@ public class SkeletonBinary {
|
|||||||
static public final int TIMELINE_EVENT = 5;
|
static public final int TIMELINE_EVENT = 5;
|
||||||
static public final int TIMELINE_DRAWORDER = 6;
|
static public final int TIMELINE_DRAWORDER = 6;
|
||||||
static public final int TIMELINE_FFD = 7;
|
static public final int TIMELINE_FFD = 7;
|
||||||
|
static public final int TIMELINE_IK = 8;
|
||||||
|
|
||||||
static public final int CURVE_LINEAR = 0;
|
static public final int CURVE_LINEAR = 0;
|
||||||
static public final int CURVE_STEPPED = 1;
|
static public final int CURVE_STEPPED = 1;
|
||||||
@ -123,7 +124,7 @@ public class SkeletonBinary {
|
|||||||
boneData.inheritScale = input.readBoolean();
|
boneData.inheritScale = input.readBoolean();
|
||||||
boneData.inheritRotation = input.readBoolean();
|
boneData.inheritRotation = input.readBoolean();
|
||||||
if (nonessential) Color.rgba8888ToColor(boneData.getColor(), input.readInt());
|
if (nonessential) Color.rgba8888ToColor(boneData.getColor(), input.readInt());
|
||||||
skeletonData.addBone(boneData);
|
skeletonData.getBones().add(boneData);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Slots.
|
// Slots.
|
||||||
@ -134,19 +135,19 @@ public class SkeletonBinary {
|
|||||||
Color.rgba8888ToColor(slotData.getColor(), input.readInt());
|
Color.rgba8888ToColor(slotData.getColor(), input.readInt());
|
||||||
slotData.attachmentName = input.readString();
|
slotData.attachmentName = input.readString();
|
||||||
slotData.additiveBlending = input.readBoolean();
|
slotData.additiveBlending = input.readBoolean();
|
||||||
skeletonData.addSlot(slotData);
|
skeletonData.getSlots().add(slotData);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default skin.
|
// Default skin.
|
||||||
Skin defaultSkin = readSkin(input, "default", nonessential);
|
Skin defaultSkin = readSkin(input, "default", nonessential);
|
||||||
if (defaultSkin != null) {
|
if (defaultSkin != null) {
|
||||||
skeletonData.defaultSkin = defaultSkin;
|
skeletonData.defaultSkin = defaultSkin;
|
||||||
skeletonData.addSkin(defaultSkin);
|
skeletonData.getSkins().add(defaultSkin);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skins.
|
// Skins.
|
||||||
for (int i = 0, n = input.readInt(true); i < n; i++)
|
for (int i = 0, n = input.readInt(true); i < n; i++)
|
||||||
skeletonData.addSkin(readSkin(input, input.readString(), nonessential));
|
skeletonData.getSkins().add(readSkin(input, input.readString(), nonessential));
|
||||||
|
|
||||||
// Events.
|
// Events.
|
||||||
for (int i = 0, n = input.readInt(true); i < n; i++) {
|
for (int i = 0, n = input.readInt(true); i < n; i++) {
|
||||||
@ -154,7 +155,7 @@ public class SkeletonBinary {
|
|||||||
eventData.intValue = input.readInt(false);
|
eventData.intValue = input.readInt(false);
|
||||||
eventData.floatValue = input.readFloat();
|
eventData.floatValue = input.readFloat();
|
||||||
eventData.stringValue = input.readString();
|
eventData.stringValue = input.readString();
|
||||||
skeletonData.addEvent(eventData);
|
skeletonData.getEvents().add(eventData);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Animations.
|
// Animations.
|
||||||
@ -497,7 +498,7 @@ public class SkeletonBinary {
|
|||||||
}
|
}
|
||||||
|
|
||||||
timelines.shrink();
|
timelines.shrink();
|
||||||
skeletonData.addAnimation(new Animation(name, timelines, duration));
|
skeletonData.getAnimations().add(new Animation(name, timelines, duration));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readCurve (DataInput input, int frameIndex, CurveTimeline timeline) throws IOException {
|
private void readCurve (DataInput input, int frameIndex, CurveTimeline timeline) throws IOException {
|
||||||
|
|||||||
@ -40,23 +40,10 @@ public class SkeletonData {
|
|||||||
Skin defaultSkin;
|
Skin defaultSkin;
|
||||||
final Array<EventData> events = new Array();
|
final Array<EventData> events = new Array();
|
||||||
final Array<Animation> animations = new Array();
|
final Array<Animation> animations = new Array();
|
||||||
|
final Array<IkConstraintData> ikConstraints = new Array();
|
||||||
public void clear () {
|
|
||||||
bones.clear();
|
|
||||||
slots.clear();
|
|
||||||
skins.clear();
|
|
||||||
defaultSkin = null;
|
|
||||||
events.clear();
|
|
||||||
animations.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Bones.
|
// --- Bones.
|
||||||
|
|
||||||
public void addBone (BoneData bone) {
|
|
||||||
if (bone == null) throw new IllegalArgumentException("bone cannot be null.");
|
|
||||||
bones.add(bone);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Array<BoneData> getBones () {
|
public Array<BoneData> getBones () {
|
||||||
return bones;
|
return bones;
|
||||||
}
|
}
|
||||||
@ -83,11 +70,6 @@ public class SkeletonData {
|
|||||||
|
|
||||||
// --- Slots.
|
// --- Slots.
|
||||||
|
|
||||||
public void addSlot (SlotData slot) {
|
|
||||||
if (slot == null) throw new IllegalArgumentException("slot cannot be null.");
|
|
||||||
slots.add(slot);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Array<SlotData> getSlots () {
|
public Array<SlotData> getSlots () {
|
||||||
return slots;
|
return slots;
|
||||||
}
|
}
|
||||||
@ -124,11 +106,6 @@ public class SkeletonData {
|
|||||||
this.defaultSkin = defaultSkin;
|
this.defaultSkin = defaultSkin;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addSkin (Skin skin) {
|
|
||||||
if (skin == null) throw new IllegalArgumentException("skin cannot be null.");
|
|
||||||
skins.add(skin);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return May be null. */
|
/** @return May be null. */
|
||||||
public Skin findSkin (String skinName) {
|
public Skin findSkin (String skinName) {
|
||||||
if (skinName == null) throw new IllegalArgumentException("skinName cannot be null.");
|
if (skinName == null) throw new IllegalArgumentException("skinName cannot be null.");
|
||||||
@ -144,11 +121,6 @@ public class SkeletonData {
|
|||||||
|
|
||||||
// --- Events.
|
// --- Events.
|
||||||
|
|
||||||
public void addEvent (EventData eventData) {
|
|
||||||
if (eventData == null) throw new IllegalArgumentException("eventData cannot be null.");
|
|
||||||
events.add(eventData);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return May be null. */
|
/** @return May be null. */
|
||||||
public EventData findEvent (String eventDataName) {
|
public EventData findEvent (String eventDataName) {
|
||||||
if (eventDataName == null) throw new IllegalArgumentException("eventDataName cannot be null.");
|
if (eventDataName == null) throw new IllegalArgumentException("eventDataName cannot be null.");
|
||||||
@ -163,11 +135,6 @@ public class SkeletonData {
|
|||||||
|
|
||||||
// --- Animations.
|
// --- Animations.
|
||||||
|
|
||||||
public void addAnimation (Animation animation) {
|
|
||||||
if (animation == null) throw new IllegalArgumentException("animation cannot be null.");
|
|
||||||
animations.add(animation);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Array<Animation> getAnimations () {
|
public Array<Animation> getAnimations () {
|
||||||
return animations;
|
return animations;
|
||||||
}
|
}
|
||||||
@ -183,6 +150,23 @@ public class SkeletonData {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- IK
|
||||||
|
|
||||||
|
public Array<IkConstraintData> getIkConstraints () {
|
||||||
|
return ikConstraints;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return May be null. */
|
||||||
|
public IkConstraintData findIkConstraint (String ikConstraintName) {
|
||||||
|
if (ikConstraintName == null) throw new IllegalArgumentException("ikConstraintName cannot be null.");
|
||||||
|
Array<IkConstraintData> ikConstraints = this.ikConstraints;
|
||||||
|
for (int i = 0, n = ikConstraints.size; i < n; i++) {
|
||||||
|
IkConstraintData ikConstraint = ikConstraints.get(i);
|
||||||
|
if (ikConstraint.name.equals(ikConstraintName)) return ikConstraint;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
/** @return May be null. */
|
/** @return May be null. */
|
||||||
|
|||||||
@ -111,7 +111,7 @@ public class SkeletonJson {
|
|||||||
String color = boneMap.getString("color", null);
|
String color = boneMap.getString("color", null);
|
||||||
if (color != null) boneData.getColor().set(Color.valueOf(color));
|
if (color != null) boneData.getColor().set(Color.valueOf(color));
|
||||||
|
|
||||||
skeletonData.addBone(boneData);
|
skeletonData.getBones().add(boneData);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Slots.
|
// Slots.
|
||||||
@ -129,7 +129,7 @@ public class SkeletonJson {
|
|||||||
|
|
||||||
slotData.additiveBlending = slotMap.getBoolean("additive", false);
|
slotData.additiveBlending = slotMap.getBoolean("additive", false);
|
||||||
|
|
||||||
skeletonData.addSlot(slotData);
|
skeletonData.getSlots().add(slotData);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skins.
|
// Skins.
|
||||||
@ -143,7 +143,7 @@ public class SkeletonJson {
|
|||||||
if (attachment != null) skin.addAttachment(slotIndex, entry.name, attachment);
|
if (attachment != null) skin.addAttachment(slotIndex, entry.name, attachment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
skeletonData.addSkin(skin);
|
skeletonData.getSkins().add(skin);
|
||||||
if (skin.name.equals("default")) skeletonData.defaultSkin = skin;
|
if (skin.name.equals("default")) skeletonData.defaultSkin = skin;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,7 +153,7 @@ public class SkeletonJson {
|
|||||||
eventData.intValue = eventMap.getInt("int", 0);
|
eventData.intValue = eventMap.getInt("int", 0);
|
||||||
eventData.floatValue = eventMap.getFloat("float", 0f);
|
eventData.floatValue = eventMap.getFloat("float", 0f);
|
||||||
eventData.stringValue = eventMap.getString("string", null);
|
eventData.stringValue = eventMap.getString("string", null);
|
||||||
skeletonData.addEvent(eventData);
|
skeletonData.getEvents().add(eventData);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Animations.
|
// Animations.
|
||||||
@ -461,7 +461,7 @@ public class SkeletonJson {
|
|||||||
}
|
}
|
||||||
|
|
||||||
timelines.shrink();
|
timelines.shrink();
|
||||||
skeletonData.addAnimation(new Animation(name, timelines, duration));
|
skeletonData.getAnimations().add(new Animation(name, timelines, duration));
|
||||||
}
|
}
|
||||||
|
|
||||||
void readCurve (CurveTimeline timeline, int frameIndex, JsonValue valueMap) {
|
void readCurve (CurveTimeline timeline, int frameIndex, JsonValue valueMap) {
|
||||||
|
|||||||
@ -38,7 +38,7 @@ public interface AttachmentLoader {
|
|||||||
|
|
||||||
/** @return May be null to not load any attachment. */
|
/** @return May be null to not load any attachment. */
|
||||||
public MeshAttachment newMeshAttachment (Skin skin, String name, String path);
|
public MeshAttachment newMeshAttachment (Skin skin, String name, String path);
|
||||||
|
|
||||||
/** @return May be null to not load any attachment. */
|
/** @return May be null to not load any attachment. */
|
||||||
public SkinnedMeshAttachment newSkinnedMeshAttachment (Skin skin, String name, String path);
|
public SkinnedMeshAttachment newSkinnedMeshAttachment (Skin skin, String name, String path);
|
||||||
|
|
||||||
|
|||||||
@ -34,7 +34,7 @@ import com.esotericsoftware.spine.Bone;
|
|||||||
import com.esotericsoftware.spine.Skeleton;
|
import com.esotericsoftware.spine.Skeleton;
|
||||||
import com.esotericsoftware.spine.Slot;
|
import com.esotericsoftware.spine.Slot;
|
||||||
|
|
||||||
import static com.badlogic.gdx.graphics.g2d.SpriteBatch.*;
|
import static com.badlogic.gdx.graphics.g2d.Batch.*;
|
||||||
|
|
||||||
import com.badlogic.gdx.graphics.Color;
|
import com.badlogic.gdx.graphics.Color;
|
||||||
import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
|
import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user