diff --git a/.gitignore b/.gitignore index 4478e9e1f..d0064313e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ spine-cpp/Debug/* spine-libgdx/bin/* +spine-libgdx/libs/gdx-backend-lwjgl-natives.jar +spine-libgdx/libs/gdx-backend-lwjgl.jar +spine-libgdx/libs/gdx-natives.jar +spine-libgdx/libs/gdx.jar diff --git a/spine-libgdx/.classpath b/spine-libgdx/.classpath new file mode 100644 index 000000000..70260f375 --- /dev/null +++ b/spine-libgdx/.classpath @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/spine-libgdx/.project b/spine-libgdx/.project new file mode 100644 index 000000000..7d65046eb --- /dev/null +++ b/spine-libgdx/.project @@ -0,0 +1,17 @@ + + + spine-libgdx + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/spine-libgdx/.settings/org.eclipse.jdt.core.prefs b/spine-libgdx/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..089aa9ace --- /dev/null +++ b/spine-libgdx/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,95 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore +org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault +org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable +org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.5 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.autoboxing=ignore +org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning +org.eclipse.jdt.core.compiler.problem.deadCode=ignore +org.eclipse.jdt.core.compiler.problem.deprecation=ignore +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=warning +org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore +org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore +org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled +org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore +org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning +org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore +org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning +org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled +org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=ignore +org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore +org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error +org.eclipse.jdt.core.compiler.problem.nullReference=warning +org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error +org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning +org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning +org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore +org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning +org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore +org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore +org.eclipse.jdt.core.compiler.problem.rawTypeReference=ignore +org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning +org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore +org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore +org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore +org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore +org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning +org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=warning +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=ignore +org.eclipse.jdt.core.compiler.problem.unclosedCloseable=ignore +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=enabled +org.eclipse.jdt.core.compiler.problem.unusedImport=ignore +org.eclipse.jdt.core.compiler.problem.unusedLabel=warning +org.eclipse.jdt.core.compiler.problem.unusedLocal=ignore +org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=enabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=enabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=ignore +org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning +org.eclipse.jdt.core.compiler.source=1.5 diff --git a/spine-libgdx/src/com/esotericsoftware/spine/Animation.java b/spine-libgdx/src/com/esotericsoftware/spine/Animation.java new file mode 100644 index 000000000..3148feb2d --- /dev/null +++ b/spine-libgdx/src/com/esotericsoftware/spine/Animation.java @@ -0,0 +1,477 @@ + +package com.esotericsoftware.spine; + +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.math.MathUtils; +import com.badlogic.gdx.utils.Array; + +public class Animation { + private final Array timelines; + private float duration; + + public Animation (Array timelines, float duration) { + if (timelines == null) throw new IllegalArgumentException("timelines cannot be null."); + this.timelines = timelines; + this.duration = duration; + } + + public Array getTimelines () { + return timelines; + } + + /** Returns the duration of the animation in seconds. Defaults to the max {@link Timeline#getDuration() duration} of the + * timelines. */ + public float getDuration () { + return duration; + } + + public void setDuration (float duration) { + this.duration = duration; + } + + /** Poses the skeleton at the specified time for this animation. */ + public void apply (Skeleton skeleton, float time, boolean loop) { + if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); + + if (loop && duration != 0) time %= duration; + + Array timeline = this.timelines; + for (int i = 0, n = timeline.size; i < n; i++) + timeline.get(i).apply(skeleton, time, 1); + } + + /** Poses the skeleton at the specified time for this animation mixed with the current pose. + * @param alpha The amount of this animation that affects the current pose. */ + public void mix (Skeleton skeleton, float time, boolean loop, float alpha) { + if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); + + if (loop && duration != 0) time %= duration; + + Array timeline = this.timelines; + for (int i = 0, n = timeline.size; i < n; i++) + timeline.get(i).apply(skeleton, time, alpha); + } + + /** @param target After the first and before the last entry. */ + static int binarySearch (float[] values, float target, int step) { + int low = 0; + int high = values.length / step - 2; + if (high == 0) return step; + int current = high >>> 1; + while (true) { + if (values[(current + 1) * step] <= target) + low = current + 1; + else + high = current; + if (low == high) return (low + 1) * step; + current = (low + high) >>> 1; + } + } + + static int linearSearch (float[] values, float target, int step) { + for (int i = 0, last = values.length - step; i <= last; i += step) { + if (values[i] <= target) continue; + return i; + } + return -1; + } + + /** The keyframes for a single animation timeline. */ + static public interface Timeline { + /** Returns the time in seconds of the last keyframe. */ + public float getDuration (); + + public int getKeyframeCount (); + + /** Sets the value(s) for the specified time. */ + public void apply (Skeleton skeleton, float time, float alpha); + } + + /** Base class for frames that use an interpolation bezier curve. */ + static public abstract class CurveTimeline implements Timeline { + static private final float LINEAR = 0; + static private final float STEPPED = -1; + static private final int BEZIER_SEGMENTS = 10; + + private final float[] curves; // dfx, dfy, ddfx, ddfy, dddfx, dddfy, ... + + public CurveTimeline (int keyframeCount) { + curves = new float[(keyframeCount - 1) * 6]; + } + + public void setLinear (int keyframeIndex) { + curves[keyframeIndex * 6] = LINEAR; + } + + public void setStepped (int keyframeIndex) { + curves[keyframeIndex * 6] = STEPPED; + } + + /** Sets the control handle positions for an interpolation bezier curve used to transition from this keyframe to the next. + * cx1 and cx2 are from 0 to 1, representing the percent of time between the two keyframes. cy1 and cy2 are the percent of + * the difference between the keyframe's values. */ + public void setCurve (int keyframeIndex, float cx1, float cy1, float cx2, float cy2) { + float subdiv_step = 1f / BEZIER_SEGMENTS; + float subdiv_step2 = subdiv_step * subdiv_step; + float subdiv_step3 = subdiv_step2 * subdiv_step; + float pre1 = 3 * subdiv_step; + float pre2 = 3 * subdiv_step2; + float pre4 = 6 * subdiv_step2; + float pre5 = 6 * subdiv_step3; + float tmp1x = -cx1 * 2 + cx2; + float tmp1y = -cy1 * 2 + cy2; + float tmp2x = (cx1 - cx2) * 3 + 1; + float tmp2y = (cy1 - cy2) * 3 + 1; + int i = keyframeIndex * 6; + float[] curves = this.curves; + curves[i] = cx1 * pre1 + tmp1x * pre2 + tmp2x * subdiv_step3; + curves[i + 1] = cy1 * pre1 + tmp1y * pre2 + tmp2y * subdiv_step3; + curves[i + 2] = tmp1x * pre4 + tmp2x * pre5; + curves[i + 3] = tmp1y * pre4 + tmp2y * pre5; + curves[i + 4] = tmp2x * pre5; + curves[i + 5] = tmp2y * pre5; + } + + public float getCurvePercent (int keyframeIndex, float percent) { + int curveIndex = keyframeIndex * 6; + float[] curves = this.curves; + float dfx = curves[curveIndex]; + if (dfx == LINEAR) return percent; + if (dfx == STEPPED) return 0; + float dfy = curves[curveIndex + 1]; + float ddfx = curves[curveIndex + 2]; + float ddfy = curves[curveIndex + 3]; + float dddfx = curves[curveIndex + 4]; + float dddfy = curves[curveIndex + 5]; + float x = dfx, y = dfy; + int i = BEZIER_SEGMENTS - 2; + while (true) { + if (x >= percent) { + float lastX = x - dfx; + float lastY = y - dfy; + return lastY + (y - lastY) * (percent - lastX) / (x - lastX); + } + if (i == 0) break; + i--; + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + x += dfx; + y += dfy; + } + return y + (1 - y) * (percent - x) / (1 - x); // Last point is 1,1. + } + } + + static public class RotateTimeline extends CurveTimeline { + static private final int LAST_FRAME_TIME = -2; + static private final int FRAME_VALUE = 1; + + private int boneIndex; + private final float[] frames; // time, value, ... + + public RotateTimeline (int keyframeCount) { + super(keyframeCount); + frames = new float[keyframeCount * 2]; + } + + public float getDuration () { + return frames[frames.length - 2]; + } + + public int getKeyframeCount () { + return frames.length / 2; + } + + public void setBoneIndex (int boneIndex) { + this.boneIndex = boneIndex; + } + + public int getBoneIndex () { + return boneIndex; + } + + public float[] getKeyframes () { + return frames; + } + + /** Sets the time and value of the specified keyframe. */ + public void setKeyframe (int keyframeIndex, float time, float value) { + keyframeIndex *= 2; + frames[keyframeIndex] = time; + frames[keyframeIndex + 1] = value; + } + + public void apply (Skeleton skeleton, float time, float alpha) { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + Bone bone = skeleton.bones.get(boneIndex); + + if (time >= frames[frames.length - 2]) { // Time is after last frame. + float amount = bone.data.rotation + frames[frames.length - 1] - bone.rotation; + while (amount > 180) + amount -= 360; + while (amount < -180) + amount += 360; + bone.rotation += amount * alpha; + return; + } + + // Interpolate between the last frame and the current frame. + int frameIndex = binarySearch(frames, time, 2); + float lastFrameValue = frames[frameIndex - 1]; + float frameTime = frames[frameIndex]; + float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime), 0, 1); + percent = getCurvePercent(frameIndex / 2 - 1, percent); + + float amount = frames[frameIndex + FRAME_VALUE] - lastFrameValue; + while (amount > 180) + amount -= 360; + while (amount < -180) + amount += 360; + amount = bone.data.rotation + (lastFrameValue + amount * percent) - bone.rotation; + while (amount > 180) + amount -= 360; + while (amount < -180) + amount += 360; + bone.rotation += amount * alpha; + } + } + + static public class TranslateTimeline extends CurveTimeline { + static final int LAST_FRAME_TIME = -3; + static final int FRAME_X = 1; + static final int FRAME_Y = 2; + + int boneIndex; + final float[] frames; // time, value, value, ... + + public TranslateTimeline (int keyframeCount) { + super(keyframeCount); + frames = new float[keyframeCount * 3]; + } + + public float getDuration () { + return frames[frames.length - 3]; + } + + public int getKeyframeCount () { + return frames.length / 3; + } + + public void setBoneIndex (int boneIndex) { + this.boneIndex = boneIndex; + } + + public int getBoneIndex () { + return boneIndex; + } + + public float[] getKeyframes () { + return frames; + } + + /** Sets the time and value of the specified keyframe. */ + public void setKeyframe (int keyframeIndex, float time, float x, float y) { + keyframeIndex *= 3; + frames[keyframeIndex] = time; + frames[keyframeIndex + 1] = x; + frames[keyframeIndex + 2] = y; + } + + public void apply (Skeleton skeleton, float time, float alpha) { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + Bone bone = skeleton.bones.get(boneIndex); + + if (time >= frames[frames.length - 3]) { // Time is after last frame. + bone.x += (bone.data.x + frames[frames.length - 2] - bone.x) * alpha; + bone.y += (bone.data.y + frames[frames.length - 1] - bone.y) * alpha; + return; + } + + // Interpolate between the last frame and the current frame. + int frameIndex = binarySearch(frames, time, 3); + float lastFrameX = frames[frameIndex - 2]; + float lastFrameY = frames[frameIndex - 1]; + float frameTime = frames[frameIndex]; + float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime), 0, 1); + percent = getCurvePercent(frameIndex / 3 - 1, percent); + + bone.x += (bone.data.x + lastFrameX + (frames[frameIndex + FRAME_X] - lastFrameX) * percent - bone.x) * alpha; + bone.y += (bone.data.y + lastFrameY + (frames[frameIndex + FRAME_Y] - lastFrameY) * percent - bone.y) * alpha; + } + } + + static public class ScaleTimeline extends TranslateTimeline { + public ScaleTimeline (int keyframeCount) { + super(keyframeCount); + } + + public void apply (Skeleton skeleton, float time, float alpha) { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + Bone bone = skeleton.bones.get(boneIndex); + if (time >= frames[frames.length - 3]) { // Time is after last frame. + bone.scaleX += (bone.data.scaleX - 1 + frames[frames.length - 2] - bone.scaleX) * alpha; + bone.scaleY += (bone.data.scaleY - 1 + frames[frames.length - 1] - bone.scaleY) * alpha; + return; + } + + // Interpolate between the last frame and the current frame. + int frameIndex = binarySearch(frames, time, 3); + float lastFrameX = frames[frameIndex - 2]; + float lastFrameY = frames[frameIndex - 1]; + float frameTime = frames[frameIndex]; + float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime), 0, 1); + percent = getCurvePercent(frameIndex / 3 - 1, percent); + + bone.scaleX += (bone.data.scaleX - 1 + lastFrameX + (frames[frameIndex + FRAME_X] - lastFrameX) * percent - bone.scaleX) + * alpha; + bone.scaleY += (bone.data.scaleY - 1 + lastFrameY + (frames[frameIndex + FRAME_Y] - lastFrameY) * percent - bone.scaleY) + * alpha; + } + } + + static public class ColorTimeline extends CurveTimeline { + static private final int LAST_FRAME_TIME = -5; + static private final int FRAME_R = 1; + static private final int FRAME_G = 2; + static private final int FRAME_B = 3; + static private final int FRAME_A = 4; + + private int slotIndex; + private final float[] frames; // time, r, g, b, a, ... + + public ColorTimeline (int keyframeCount) { + super(keyframeCount); + frames = new float[keyframeCount * 5]; + } + + public float getDuration () { + return frames[frames.length - 5]; + } + + public int getKeyframeCount () { + return frames.length / 5; + } + + public void setSlotIndex (int slotIndex) { + this.slotIndex = slotIndex; + } + + public int getSlotIndex () { + return slotIndex; + } + + public float[] getKeyframes () { + return frames; + } + + /** Sets the time and value of the specified keyframe. */ + public void setKeyframe (int keyframeIndex, float time, float r, float g, float b, float a) { + keyframeIndex *= 5; + frames[keyframeIndex] = time; + frames[keyframeIndex + 1] = r; + frames[keyframeIndex + 2] = g; + frames[keyframeIndex + 3] = b; + frames[keyframeIndex + 4] = a; + } + + public void apply (Skeleton skeleton, float time, float alpha) { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + Color color = skeleton.slots.get(slotIndex).color; + + if (time >= frames[frames.length - 5]) { // Time is after last frame. + int i = frames.length - 1; + float r = frames[i - 3]; + float g = frames[i - 2]; + float b = frames[i - 1]; + float a = frames[i]; + color.set(r, g, b, a); + return; + } + + // Interpolate between the last frame and the current frame. + int frameIndex = binarySearch(frames, time, 5); + float lastFrameR = frames[frameIndex - 4]; + float lastFrameG = frames[frameIndex - 3]; + float lastFrameB = frames[frameIndex - 2]; + float lastFrameA = frames[frameIndex - 1]; + float frameTime = frames[frameIndex]; + float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime), 0, 1); + percent = getCurvePercent(frameIndex / 5 - 1, percent); + + float r = lastFrameR + (frames[frameIndex + FRAME_R] - lastFrameR) * percent; + float g = lastFrameG + (frames[frameIndex + FRAME_G] - lastFrameG) * percent; + float b = lastFrameB + (frames[frameIndex + FRAME_B] - lastFrameB) * percent; + float a = lastFrameA + (frames[frameIndex + FRAME_A] - lastFrameA) * percent; + if (alpha < 1) + color.add((r - color.r) * alpha, (g - color.g) * alpha, (b - color.b) * alpha, (a - color.a) * alpha); + else + color.set(r, g, b, a); + } + } + + static public class AttachmentTimeline implements Timeline { + private int slotIndex; + private final float[] frames; // time, ... + private final String[] attachmentNames; + + public AttachmentTimeline (int keyframeCount) { + frames = new float[keyframeCount]; + attachmentNames = new String[keyframeCount]; + } + + public float getDuration () { + return frames[frames.length - 1]; + } + + public int getKeyframeCount () { + return frames.length; + } + + public int getSlotIndex () { + return slotIndex; + } + + public void setSlotIndex (int slotIndex) { + this.slotIndex = slotIndex; + } + + public float[] getKeyframes () { + return frames; + } + + public String[] getAttachmentNames () { + return attachmentNames; + } + + /** Sets the time and value of the specified keyframe. */ + public void setKeyframe (int keyframeIndex, float time, String attachmentName) { + frames[keyframeIndex] = time; + attachmentNames[keyframeIndex] = attachmentName; + } + + public void apply (Skeleton skeleton, float time, float alpha) { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + int frameIndex; + if (time >= frames[frames.length - 1]) // Time is after last frame. + frameIndex = frames.length - 1; + else + frameIndex = binarySearch(frames, time, 1) - 1; + + String attachmentName = attachmentNames[frameIndex]; + skeleton.slots.get(slotIndex).setAttachment( + attachmentName == null ? null : skeleton.getAttachment(slotIndex, attachmentName)); + } + } +} diff --git a/spine-libgdx/src/com/esotericsoftware/spine/Attachment.java b/spine-libgdx/src/com/esotericsoftware/spine/Attachment.java new file mode 100644 index 000000000..aaa7528a2 --- /dev/null +++ b/spine-libgdx/src/com/esotericsoftware/spine/Attachment.java @@ -0,0 +1,91 @@ + +package com.esotericsoftware.spine; + +import com.badlogic.gdx.graphics.g2d.SpriteBatch; + +abstract public class Attachment { + final String name; + boolean resolved; + private float x, y, scaleX, scaleY, rotation, width, height; + + public Attachment (String name) { + if (name == null) throw new IllegalArgumentException("name cannot be null."); + this.name = name; + } + + abstract public void updateOffset (); + + abstract public void draw (SpriteBatch batch, Slot slot); + + public float getX () { + return x; + } + + public void setX (float x) { + this.x = x; + } + + public float getY () { + return y; + } + + public void setY (float y) { + this.y = y; + } + + public float getScaleX () { + return scaleX; + } + + public void setScaleX (float scaleX) { + this.scaleX = scaleX; + } + + public float getScaleY () { + return scaleY; + } + + public void setScaleY (float scaleY) { + this.scaleY = scaleY; + } + + public float getRotation () { + return rotation; + } + + public void setRotation (float rotation) { + this.rotation = rotation; + } + + public float getWidth () { + return width; + } + + public void setWidth (float width) { + this.width = width; + } + + public float getHeight () { + return height; + } + + public void setHeight (float height) { + this.height = height; + } + + public boolean isResolved () { + return resolved; + } + + public void setResolved (boolean resolved) { + this.resolved = resolved; + } + + public String getName () { + return name; + } + + public String toString () { + return name; + } +} diff --git a/spine-libgdx/src/com/esotericsoftware/spine/AttachmentResolver.java b/spine-libgdx/src/com/esotericsoftware/spine/AttachmentResolver.java new file mode 100644 index 000000000..4d668b8d9 --- /dev/null +++ b/spine-libgdx/src/com/esotericsoftware/spine/AttachmentResolver.java @@ -0,0 +1,6 @@ + +package com.esotericsoftware.spine; + +public interface AttachmentResolver { + public void resolve (Attachment attachment); +} diff --git a/spine-libgdx/src/com/esotericsoftware/spine/Bone.java b/spine-libgdx/src/com/esotericsoftware/spine/Bone.java new file mode 100644 index 000000000..b0d4b7ec8 --- /dev/null +++ b/spine-libgdx/src/com/esotericsoftware/spine/Bone.java @@ -0,0 +1,185 @@ + +package com.esotericsoftware.spine; + +import static com.badlogic.gdx.math.Matrix3.*; + +import com.badlogic.gdx.math.MathUtils; +import com.badlogic.gdx.math.Matrix3; + +public class Bone { + final BoneData data; + final Bone parent; + float x, y; + float rotation; + float scaleX = 1, scaleY = 1; + + float m00, m01, worldX; // a b x + float m10, m11, worldY; // c d y + float worldRotation; + float worldScaleX, worldScaleY; + + /** @param parent May be null. */ + public Bone (BoneData data, Bone parent) { + if (data == null) throw new IllegalArgumentException("data cannot be null."); + this.data = data; + this.parent = parent; + setToBindPose(); + } + + /** Copy constructor. + * @param parent May be null. */ + public Bone (Bone bone, Bone parent) { + if (bone == null) throw new IllegalArgumentException("bone cannot be null."); + this.parent = parent; + data = bone.data; + x = bone.x; + y = bone.y; + rotation = bone.rotation; + scaleX = bone.scaleX; + scaleY = bone.scaleY; + } + + /** Computes the world SRT using the parent bone and the local SRT. */ + public void updateWorldTransform (boolean flipX, boolean flipY) { + Bone parent = this.parent; + if (parent != null) { + worldX = x * parent.m00 + y * parent.m01 + parent.worldX; + worldY = x * parent.m10 + y * parent.m11 + parent.worldY; + worldScaleX = parent.worldScaleX * scaleX; + worldScaleY = parent.worldScaleY * scaleY; + worldRotation = parent.worldRotation + rotation; + } else { + worldX = x; + worldY = y; + worldScaleX = scaleX; + worldScaleY = scaleY; + worldRotation = rotation; + } + float cos = MathUtils.cosDeg(worldRotation); + float sin = MathUtils.sinDeg(worldRotation); + m00 = cos * worldScaleX; + m10 = sin * worldScaleX; + m01 = -sin * worldScaleY; + m11 = cos * worldScaleY; + if (flipX) { + m00 = -m00; + m01 = -m01; + } + if (flipY) { + m10 = -m10; + m11 = -m11; + } + } + + public void setToBindPose () { + BoneData data = this.data; + x = data.x; + y = data.y; + rotation = data.rotation; + scaleX = data.scaleX; + scaleY = data.scaleY; + } + + public BoneData getData () { + return data; + } + + public Bone getParent () { + return parent; + } + + public float getX () { + return x; + } + + public void setX (float x) { + this.x = x; + } + + public float getY () { + return y; + } + + public void setY (float y) { + this.y = y; + } + + public float getRotation () { + return rotation; + } + + public void setRotation (float rotation) { + this.rotation = rotation; + } + + public float getScaleX () { + return scaleX; + } + + public void setScaleX (float scaleX) { + this.scaleX = scaleX; + } + + public float getScaleY () { + return scaleY; + } + + public void setScaleY (float scaleY) { + this.scaleY = scaleY; + } + + public float getM00 () { + return m00; + } + + public float getM01 () { + return m01; + } + + public float getM10 () { + return m10; + } + + public float getM11 () { + return m11; + } + + public float getWorldX () { + return worldX; + } + + public float getWorldY () { + return worldY; + } + + public float getWorldRotation () { + return worldRotation; + } + + public float getWorldScaleX () { + return worldScaleX; + } + + public float getWorldScaleY () { + return worldScaleY; + } + + public Matrix3 getWorldTransform (Matrix3 worldTransform) { + if (worldTransform == null) throw new IllegalArgumentException("worldTransform cannot be null."); + float[] val = worldTransform.val; + val[M00] = m00; + val[M01] = m01; + val[M02] = worldX; + val[M10] = m10; + val[M11] = m11; + val[M12] = worldY; + val[M20] = 0; + val[M21] = 0; + val[M22] = 1; + return worldTransform; + } + + public String toString () { + return data.name; + } +} diff --git a/spine-libgdx/src/com/esotericsoftware/spine/BoneData.java b/spine-libgdx/src/com/esotericsoftware/spine/BoneData.java new file mode 100644 index 000000000..dbf1069f1 --- /dev/null +++ b/spine-libgdx/src/com/esotericsoftware/spine/BoneData.java @@ -0,0 +1,93 @@ + +package com.esotericsoftware.spine; + +public class BoneData { + final BoneData parent; + final String name; + float length; + float x, y; + float rotation; + float scaleX = 1, scaleY = 1; + + /** @param parent May be null. */ + public BoneData (String name, BoneData parent) { + if (name == null) throw new IllegalArgumentException("name cannot be null."); + this.name = name; + this.parent = parent; + } + + /** Copy constructor. + * @param parent May be null. */ + public BoneData (BoneData bone, BoneData parent) { + if (bone == null) throw new IllegalArgumentException("bone cannot be null."); + this.parent = parent; + name = bone.name; + length = bone.length; + x = bone.x; + y = bone.y; + rotation = bone.rotation; + scaleX = bone.scaleX; + scaleY = bone.scaleY; + } + + /** @return May be null. */ + public BoneData getParent () { + return parent; + } + + public String getName () { + return name; + } + + public float getLength () { + return length; + } + + public void setLength (float length) { + this.length = length; + } + + public float getX () { + return x; + } + + public void setX (float x) { + this.x = x; + } + + public float getY () { + return y; + } + + public void setY (float y) { + this.y = y; + } + + public float getRotation () { + return rotation; + } + + public void setRotation (float rotation) { + this.rotation = rotation; + } + + public float getScaleX () { + return scaleX; + } + + public void setScaleX (float scaleX) { + this.scaleX = scaleX; + } + + public float getScaleY () { + return scaleY; + } + + public void setScaleY (float scaleY) { + this.scaleY = scaleY; + } + + public String toString () { + return name; + } +} diff --git a/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java b/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java new file mode 100644 index 000000000..64cfe3ac9 --- /dev/null +++ b/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java @@ -0,0 +1,279 @@ + +package com.esotericsoftware.spine; + +import com.esotericsoftware.spine.Skin.Key; + +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType; +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.ObjectMap.Entry; + +public class Skeleton { + final SkeletonData data; + final Array bones; + final Array slots; + final Array drawOrder; + Skin skin; + final Color color; + float time; + boolean flipX, flipY; + + public Skeleton (SkeletonData data) { + if (data == null) throw new IllegalArgumentException("data cannot be null."); + this.data = data; + + bones = new Array(data.bones.size); + for (BoneData boneData : data.bones) { + Bone parent = boneData.parent == null ? null : bones.get(data.bones.indexOf(boneData.parent, true)); + bones.add(new Bone(boneData, parent)); + } + + slots = new Array(data.slots.size); + drawOrder = new Array(data.slots.size); + for (SlotData slotData : data.slots) { + Bone bone = bones.get(data.bones.indexOf(slotData.boneData, true)); + Slot slot = new Slot(slotData, this, bone); + slots.add(slot); + drawOrder.add(slot); + } + + color = new Color(1, 1, 1, 1); + } + + /** Copy constructor. */ + public Skeleton (Skeleton skeleton) { + if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); + data = skeleton.data; + + bones = new Array(skeleton.bones.size); + for (Bone bone : skeleton.bones) { + Bone parent = bones.get(skeleton.bones.indexOf(bone.parent, true)); + bones.add(new Bone(bone, parent)); + } + + slots = new Array(skeleton.slots.size); + for (Slot slot : skeleton.slots) { + Bone bone = bones.get(skeleton.bones.indexOf(slot.bone, true)); + Slot newSlot = new Slot(slot, this, bone); + slots.add(newSlot); + } + + drawOrder = new Array(slots.size); + for (Slot slot : skeleton.drawOrder) + drawOrder.add(slots.get(skeleton.slots.indexOf(slot, true))); + + skin = skeleton.skin; + color = new Color(skeleton.color); + time = skeleton.time; + } + + /** Updates the world transform for each bone. */ + public void updateWorldTransform () { + boolean flipX = this.flipX; + boolean flipY = this.flipY; + Array bones = this.bones; + for (int i = 0, n = bones.size; i < n; i++) + bones.get(i).updateWorldTransform(flipX, flipY); + } + + /** Sets the bones and slots to their bind pose values. */ + public void setToBindPose () { + setBonesToBindPose(); + setSlotsToBindPose(); + } + + public void setBonesToBindPose () { + Array bones = this.bones; + for (int i = 0, n = bones.size; i < n; i++) + bones.get(i).setToBindPose(); + } + + public void setSlotsToBindPose () { + Array slots = this.slots; + for (int i = 0, n = slots.size; i < n; i++) + slots.get(i).setToBindPose(i); + } + + public void draw (SpriteBatch batch) { + Array drawOrder = this.drawOrder; + for (int i = 0, n = drawOrder.size; i < n; i++) { + Slot slot = drawOrder.get(i); + Attachment attachment = slot.attachment; + if (attachment != null) { + if (!attachment.resolved) data.attachmentResolver.resolve(attachment); + attachment.updateOffset(); + attachment.draw(batch, slot); + } + } + } + + public void drawDebug (ShapeRenderer renderer) { + renderer.setColor(Color.RED); + renderer.begin(ShapeType.Line); + for (int i = 0, n = bones.size; i < n; i++) { + Bone bone = bones.get(i); + if (bone.parent == null) continue; + float x = bone.data.length * bone.m00 + bone.worldX; + float y = bone.data.length * bone.m10 + bone.worldY; + renderer.line(bone.worldX, bone.worldY, x, y); + } + renderer.end(); + + renderer.setColor(Color.GREEN); + renderer.begin(ShapeType.Filled); + for (int i = 0, n = bones.size; i < n; i++) { + Bone bone = bones.get(i); + renderer.setColor(Color.GREEN); + renderer.circle(bone.worldX, bone.worldY, 3); + } + renderer.end(); + } + + public SkeletonData getData () { + return data; + } + + public Array getBones () { + return bones; + } + + /** @return May return null. */ + public Bone getRootBone () { + if (bones.size == 0) return null; + return bones.first(); + } + + /** @return May be null. */ + public Bone findBone (String boneName) { + if (boneName == null) throw new IllegalArgumentException("boneName cannot be null."); + Array bones = this.bones; + for (int i = 0, n = bones.size; i < n; i++) { + Bone bone = bones.get(i); + if (bone.data.name.equals(boneName)) return bone; + } + return null; + } + + /** @return -1 if the bone was not found. */ + public int findBoneIndex (String boneName) { + if (boneName == null) throw new IllegalArgumentException("boneName cannot be null."); + Array bones = this.bones; + for (int i = 0, n = bones.size; i < n; i++) + if (bones.get(i).data.name.equals(boneName)) return i; + return -1; + } + + public Array getSlots () { + return slots; + } + + /** @return May be null. */ + public Slot findSlot (String slotName) { + if (slotName == null) throw new IllegalArgumentException("slotName cannot be null."); + Array slots = this.slots; + for (int i = 0, n = slots.size; i < n; i++) { + Slot slot = slots.get(i); + if (slot.data.name.equals(slotName)) return slot; + } + return null; + } + + /** @return -1 if the bone was not found. */ + public int findSlotIndex (String slotName) { + if (slotName == null) throw new IllegalArgumentException("slotName cannot be null."); + Array slots = this.slots; + for (int i = 0, n = slots.size; i < n; i++) + if (slots.get(i).data.name.equals(slotName)) return i; + return -1; + } + + /** Returns the slots in the order they will be drawn. The returned array may be modified to change the draw order. */ + public Array getDrawOrder () { + return drawOrder; + } + + /** @return May be null. */ + public Skin getSkin () { + return skin; + } + + /** Sets a skin by name. + * @see #setSkin(Skin) */ + public void setSkin (String skinName) { + Skin skin = data.findSkin(skinName); + if (skin == null) throw new IllegalArgumentException("Skin not found: " + skinName); + setSkin(skin); + } + + /** Sets the skin used to look up attachments not found in the {@link SkeletonData#getDefaultSkin() default skin}. Attachments + * from the new skin are attached if the corresponding attachment from the old skin is currently attached. + * @param newSkin May be null. */ + public void setSkin (Skin newSkin) { + if (skin != null && newSkin != null) newSkin.attachAll(this, skin); + skin = newSkin; + } + + /** @return May be null. */ + public Attachment getAttachment (String slotName, String attachmentName) { + return getAttachment(data.findSlotIndex(slotName), attachmentName); + } + + /** @return May be null. */ + public Attachment getAttachment (int slotIndex, String attachmentName) { + if (attachmentName == null) throw new IllegalArgumentException("attachmentName cannot be null."); + if (data.defaultSkin != null) { + Attachment attachment = data.defaultSkin.getAttachment(slotIndex, attachmentName); + if (attachment != null) return attachment; + } + if (skin != null) return skin.getAttachment(slotIndex, attachmentName); + return null; + } + + /** @param attachmentName May be null. */ + public void setAttachment (String slotName, String attachmentName) { + if (slotName == null) throw new IllegalArgumentException("slotName cannot be null."); + if (attachmentName == null) throw new IllegalArgumentException("attachmentName cannot be null."); + for (int i = 0, n = slots.size; i < n; i++) { + Slot slot = slots.get(i); + if (slot.data.name.equals(slotName)) { + slot.setAttachment(getAttachment(i, attachmentName)); + return; + } + } + throw new IllegalArgumentException("Slot not found: " + slotName); + } + + public Color getColor () { + return color; + } + + public boolean getFlipX () { + return flipX; + } + + public void setFlipX (boolean flipX) { + this.flipX = flipX; + } + + public boolean getFlipY () { + return flipY; + } + + public void setFlipY (boolean flipY) { + this.flipY = flipY; + } + + public float getTime () { + return time; + } + + public void setTime (float time) { + this.time = time; + } + + public void update (float delta) { + time += delta; + } +} diff --git a/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java b/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java new file mode 100644 index 000000000..5480b536b --- /dev/null +++ b/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java @@ -0,0 +1,270 @@ + +package com.esotericsoftware.spine; + +import com.esotericsoftware.spine.Animation.AttachmentTimeline; +import com.esotericsoftware.spine.Animation.ColorTimeline; +import com.esotericsoftware.spine.Animation.CurveTimeline; +import com.esotericsoftware.spine.Animation.RotateTimeline; +import com.esotericsoftware.spine.Animation.ScaleTimeline; +import com.esotericsoftware.spine.Animation.Timeline; +import com.esotericsoftware.spine.Animation.TranslateTimeline; +import com.esotericsoftware.spine.attachments.RegionAttachment; +import com.esotericsoftware.spine.attachments.RegionSequenceAttachment; +import com.esotericsoftware.spine.attachments.RegionSequenceAttachment.Mode; +import com.esotericsoftware.spine.attachments.TextureAtlasAttachmentResolver; + +import com.badlogic.gdx.files.FileHandle; +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.g2d.TextureAtlas; +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.DataInput; +import com.badlogic.gdx.utils.SerializationException; + +import java.io.IOException; + +public class SkeletonBinary { + static public final int TIMELINE_SCALE = 0; + static public final int TIMELINE_ROTATE = 1; + static public final int TIMELINE_TRANSLATE = 2; + static public final int TIMELINE_ATTACHMENT = 3; + static public final int TIMELINE_COLOR = 4; + + static public final int ATTACHMENT_REGION = 0; + static public final int ATTACHMENT_REGION_SEQUENCE = 1; + + static public final int CURVE_LINEAR = 0; + static public final int CURVE_STEPPED = 1; + static public final int CURVE_BEZIER = 2; + + private final AttachmentResolver attachmentResolver; + private float scale = 1; + + public SkeletonBinary (TextureAtlas atlas) { + attachmentResolver = new TextureAtlasAttachmentResolver(atlas); + } + + public SkeletonBinary (AttachmentResolver attachmentResolver) { + this.attachmentResolver = attachmentResolver; + } + + public float getScale () { + return scale; + } + + /** Scales the bones, images, and animations as they are loaded. */ + public void setScale (float scale) { + this.scale = scale; + } + + public SkeletonData readSkeletonData (FileHandle file) { + if (file == null) throw new IllegalArgumentException("file cannot be null."); + + SkeletonData skeletonData = new SkeletonData(attachmentResolver); + DataInput input = new DataInput(file.read(512)); + try { + // Bones. + for (int i = 0, n = input.readInt(true); i < n; i++) { + String name = input.readString(); + BoneData parent = null; + String parentName = input.readString(); + if (parentName != null) { + parent = skeletonData.findBone(parentName); + if (parent == null) throw new SerializationException("Bone not found: " + parentName); + } + BoneData boneData = new BoneData(name, parent); + boneData.x = input.readFloat() * scale; + boneData.y = input.readFloat() * scale; + boneData.scaleX = input.readFloat(); + boneData.scaleY = input.readFloat(); + boneData.rotation = input.readFloat(); + boneData.length = input.readFloat() * scale; + skeletonData.addBone(boneData); + } + + // Slots. + for (int i = 0, n = input.readInt(true); i < n; i++) { + String slotName = input.readString(); + String boneName = input.readString(); + BoneData boneData = skeletonData.findBone(boneName); + if (boneData == null) throw new SerializationException("Bone not found: " + boneName); + SlotData slotData = new SlotData(slotName, boneData); + Color.rgba8888ToColor(slotData.getColor(), input.readInt()); + slotData.setAttachmentName(input.readString()); + skeletonData.addSlot(slotData); + } + + // Default skin. + Skin defaultSkin = readSkin(input, "default"); + if (defaultSkin != null) { + skeletonData.setDefaultSkin(defaultSkin); + skeletonData.addSkin(defaultSkin); + } + + // Skins. + for (int i = 0, n = input.readInt(true); i < n; i++) + skeletonData.addSkin(readSkin(input, input.readString())); + + input.close(); + } catch (IOException ex) { + throw new SerializationException("Error reading skeleton file.", ex); + } + + skeletonData.bones.shrink(); + skeletonData.slots.shrink(); + skeletonData.skins.shrink(); + return skeletonData; + } + + private Skin readSkin (DataInput input, String skinName) throws IOException { + int slotCount = input.readInt(true); + if (slotCount == 0) return null; + Skin skin = new Skin(skinName); + for (int i = 0; i < slotCount; i++) { + int slotIndex = input.readInt(true); + int attachmentCount = input.readInt(true); + for (int ii = 0; ii < attachmentCount; ii++) { + String name = input.readString(); + skin.addAttachment(slotIndex, name, readAttachment(input, name)); + } + } + return skin; + } + + private Attachment readAttachment (DataInput input, String attachmentName) throws IOException { + String name = input.readString(); + if (name == null) name = attachmentName; + + Attachment attachment; + int type = input.readByte(); + switch (type) { + case ATTACHMENT_REGION: + attachment = new RegionAttachment(name); + break; + case ATTACHMENT_REGION_SEQUENCE: + float fps = input.readFloat(); + Mode mode = Mode.values()[input.readInt(true)]; + attachment = new RegionSequenceAttachment(name, 1 / fps, mode); + break; + default: + throw new SerializationException("Unknown attachment type: " + type + " (" + name + ")"); + } + + attachment.setX(input.readFloat() * scale); + attachment.setY(input.readFloat() * scale); + attachment.setScaleX(input.readFloat()); + attachment.setScaleY(input.readFloat()); + attachment.setRotation(input.readFloat()); + attachment.setWidth(input.readFloat() * scale); + attachment.setHeight(input.readFloat() * scale); + return attachment; + } + + public Animation readAnimation (FileHandle file, SkeletonData skeleton) { + if (file == null) throw new IllegalArgumentException("file cannot be null."); + if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); + + Array timelines = new Array(); + float duration = 0; + + DataInput input = new DataInput(file.read(512)); + try { + int boneCount = input.readInt(true); + for (int i = 0; i < boneCount; i++) { + String boneName = input.readString(); + int boneIndex = skeleton.findBoneIndex(boneName); + if (boneIndex == -1) throw new SerializationException("Bone not found: " + boneName); + int itemCount = input.readInt(true); + for (int ii = 0; ii < itemCount; ii++) { + int timelineType = input.readByte(); + int keyCount = input.readInt(true); + switch (timelineType) { + case TIMELINE_ROTATE: { + RotateTimeline timeline = new RotateTimeline(keyCount); + timeline.setBoneIndex(boneIndex); + for (int keyframeIndex = 0; keyframeIndex < keyCount; keyframeIndex++) { + timeline.setKeyframe(keyframeIndex, input.readFloat(), input.readFloat()); + if (keyframeIndex < keyCount - 1) readCurve(input, keyframeIndex, timeline); + } + timelines.add(timeline); + duration = Math.max(duration, timeline.getDuration()); + break; + } + case TIMELINE_TRANSLATE: + case TIMELINE_SCALE: + TranslateTimeline timeline; + float timelineScale = 1; + if (timelineType == TIMELINE_SCALE) + timeline = new ScaleTimeline(keyCount); + else { + timeline = new TranslateTimeline(keyCount); + timelineScale = scale; + } + timeline.setBoneIndex(boneIndex); + for (int keyframeIndex = 0; keyframeIndex < keyCount; keyframeIndex++) { + timeline.setKeyframe(keyframeIndex, input.readFloat(), input.readFloat() * timelineScale, input.readFloat() + * timelineScale); + if (keyframeIndex < keyCount - 1) readCurve(input, keyframeIndex, timeline); + } + timelines.add(timeline); + duration = Math.max(duration, timeline.getDuration()); + break; + default: + throw new RuntimeException("Invalid timeline type for a bone: " + timelineType + " (" + boneName + ")"); + } + } + } + + int slotCount = input.readInt(true); + for (int i = 0; i < slotCount; i++) { + String slotName = input.readString(); + int slotIndex = skeleton.findSlotIndex(slotName); + int itemCount = input.readInt(true); + for (int ii = 0; ii < itemCount; ii++) { + int timelineType = input.readByte(); + int keyCount = input.readInt(true); + switch (timelineType) { + case TIMELINE_COLOR: { + ColorTimeline timeline = new ColorTimeline(keyCount); + timeline.setSlotIndex(slotIndex); + for (int keyframeIndex = 0; keyframeIndex < keyCount; keyframeIndex++) { + float time = input.readFloat(); + Color.rgba8888ToColor(Color.tmp, input.readInt()); + timeline.setKeyframe(keyframeIndex, time, Color.tmp.r, Color.tmp.g, Color.tmp.b, Color.tmp.a); + if (keyframeIndex < keyCount - 1) readCurve(input, keyframeIndex, timeline); + } + timelines.add(timeline); + duration = Math.max(duration, timeline.getDuration()); + break; + } + case TIMELINE_ATTACHMENT: + AttachmentTimeline timeline = new AttachmentTimeline(keyCount); + timeline.setSlotIndex(slotIndex); + for (int keyframeIndex = 0; keyframeIndex < keyCount; keyframeIndex++) + timeline.setKeyframe(keyframeIndex, input.readFloat(), input.readString()); + timelines.add(timeline); + duration = Math.max(duration, timeline.getDuration()); + break; + default: + throw new RuntimeException("Invalid timeline type for a slot: " + timelineType + " (" + slotName + ")"); + } + } + } + } catch (IOException ex) { + throw new SerializationException("Error reading skeleton file.", ex); + } + + timelines.shrink(); + return new Animation(timelines, duration); + } + + private void readCurve (DataInput input, int keyframeIndex, CurveTimeline timeline) throws IOException { + switch (input.readByte()) { + case CURVE_STEPPED: + timeline.setStepped(keyframeIndex); + break; + case CURVE_BEZIER: + timeline.setCurve(keyframeIndex, input.readFloat(), input.readFloat(), input.readFloat(), input.readFloat()); + break; + } + } +} diff --git a/spine-libgdx/src/com/esotericsoftware/spine/SkeletonData.java b/spine-libgdx/src/com/esotericsoftware/spine/SkeletonData.java new file mode 100644 index 000000000..a98d17a47 --- /dev/null +++ b/spine-libgdx/src/com/esotericsoftware/spine/SkeletonData.java @@ -0,0 +1,120 @@ + +package com.esotericsoftware.spine; + +import com.badlogic.gdx.utils.Array; + +public class SkeletonData { + final Array bones = new Array(); // Ordered parents first. + final Array slots = new Array(); // Bind pose draw order. + final Array skins = new Array(); + Skin defaultSkin; + final AttachmentResolver attachmentResolver; + + public SkeletonData (AttachmentResolver attachmentResolver) { + if (attachmentResolver == null) throw new IllegalArgumentException("attachmentResolver cannot be null."); + this.attachmentResolver = attachmentResolver; + } + + public void clear () { + bones.clear(); + slots.clear(); + skins.clear(); + defaultSkin = null; + } + + public AttachmentResolver getAttachmentResolver () { + return attachmentResolver; + } + + // --- Bones. + + public void addBone (BoneData bone) { + if (bone == null) throw new IllegalArgumentException("bone cannot be null."); + bones.add(bone); + } + + public Array getBones () { + return bones; + } + + /** @return May be null. */ + public BoneData findBone (String boneName) { + if (boneName == null) throw new IllegalArgumentException("boneName cannot be null."); + Array bones = this.bones; + for (int i = 0, n = bones.size; i < n; i++) { + BoneData bone = bones.get(i); + if (bone.name.equals(boneName)) return bone; + } + return null; + } + + /** @return -1 if the bone was not found. */ + public int findBoneIndex (String boneName) { + if (boneName == null) throw new IllegalArgumentException("boneName cannot be null."); + Array bones = this.bones; + for (int i = 0, n = bones.size; i < n; i++) + if (bones.get(i).name.equals(boneName)) return i; + return -1; + } + + // --- Slots. + + public void addSlot (SlotData slot) { + if (slot == null) throw new IllegalArgumentException("slot cannot be null."); + slots.add(slot); + } + + public Array getSlots () { + return slots; + } + + /** @return May be null. */ + public SlotData findSlot (String slotName) { + if (slotName == null) throw new IllegalArgumentException("slotName cannot be null."); + Array slots = this.slots; + for (int i = 0, n = slots.size; i < n; i++) { + SlotData slot = slots.get(i); + if (slot.name.equals(slotName)) return slot; + } + return null; + } + + /** @return -1 if the bone was not found. */ + public int findSlotIndex (String slotName) { + if (slotName == null) throw new IllegalArgumentException("slotName cannot be null."); + Array slots = this.slots; + for (int i = 0, n = slots.size; i < n; i++) + if (slots.get(i).name.equals(slotName)) return i; + return -1; + } + + // --- Skins. + + /** @return May be null. */ + public Skin getDefaultSkin () { + return defaultSkin; + } + + /** @param defaultSkin May be null. */ + public void setDefaultSkin (Skin 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. */ + public Skin findSkin (String skinName) { + if (skinName == null) throw new IllegalArgumentException("skinName cannot be null."); + for (Skin skin : skins) + if (skin.name.equals(skinName)) return skin; + return null; + } + + /** Returns all skins, including the default skin. */ + public Array getSkins () { + return skins; + } +} diff --git a/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java b/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java new file mode 100644 index 000000000..0dcd5f04e --- /dev/null +++ b/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java @@ -0,0 +1,277 @@ + +package com.esotericsoftware.spine; + +import com.esotericsoftware.spine.Animation.AttachmentTimeline; +import com.esotericsoftware.spine.Animation.ColorTimeline; +import com.esotericsoftware.spine.Animation.CurveTimeline; +import com.esotericsoftware.spine.Animation.RotateTimeline; +import com.esotericsoftware.spine.Animation.ScaleTimeline; +import com.esotericsoftware.spine.Animation.Timeline; +import com.esotericsoftware.spine.Animation.TranslateTimeline; +import com.esotericsoftware.spine.attachments.RegionSequenceAttachment; +import com.esotericsoftware.spine.attachments.RegionSequenceAttachment.Mode; +import com.esotericsoftware.spine.attachments.RegionAttachment; +import com.esotericsoftware.spine.attachments.TextureAtlasAttachmentResolver; + +import com.badlogic.gdx.files.FileHandle; +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.g2d.TextureAtlas; +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.Json; +import com.badlogic.gdx.utils.ObjectMap.Entry; +import com.badlogic.gdx.utils.OrderedMap; +import com.badlogic.gdx.utils.SerializationException; + +public class SkeletonJson { + static public final String TIMELINE_SCALE = "scale"; + static public final String TIMELINE_ROTATE = "rotate"; + static public final String TIMELINE_TRANSLATE = "translate"; + static public final String TIMELINE_ATTACHMENT = "attachment"; + static public final String TIMELINE_COLOR = "color"; + + static public final String ATTACHMENT_REGION = "region"; + static public final String ATTACHMENT_REGION_SEQUENCE = "regionSequence"; + + private final Json json = new Json(); + private final AttachmentResolver attachmentResolver; + private float scale = 1; + + public SkeletonJson (TextureAtlas atlas) { + attachmentResolver = new TextureAtlasAttachmentResolver(atlas); + } + + public SkeletonJson (AttachmentResolver attachmentResolver) { + this.attachmentResolver = attachmentResolver; + } + + public float getScale () { + return scale; + } + + /** Scales the bones, images, and animations as they are loaded. */ + public void setScale (float scale) { + this.scale = scale; + } + + public SkeletonData readSkeletonData (FileHandle file) { + if (file == null) throw new IllegalArgumentException("file cannot be null."); + + SkeletonData skeletonData = new SkeletonData(attachmentResolver); + + OrderedMap root = json.fromJson(OrderedMap.class, file); + + // Bones. + for (OrderedMap boneMap : (Array)root.get("bones")) { + BoneData parent = null; + String parentName = (String)boneMap.get("parent"); + if (parentName != null) { + parent = skeletonData.findBone(parentName); + if (parent == null) throw new SerializationException("Parent bone not found: " + parentName); + } + BoneData boneData = new BoneData((String)boneMap.get("name"), parent); + boneData.length = getFloat(boneMap, "length", 0) * scale; + boneData.x = getFloat(boneMap, "x", 0) * scale; + boneData.y = getFloat(boneMap, "y", 0) * scale; + boneData.rotation = getFloat(boneMap, "rotation", 0); + boneData.scaleX = getFloat(boneMap, "scaleX", 1); + boneData.scaleY = getFloat(boneMap, "scaleY", 1); + skeletonData.addBone(boneData); + } + + // Slots. + Array slots = (Array)root.get("slots"); + if (slots != null) { + for (OrderedMap slotMap : slots) { + String slotName = (String)slotMap.get("name"); + String boneName = (String)slotMap.get("bone"); + BoneData boneData = skeletonData.findBone(boneName); + if (boneData == null) throw new SerializationException("Slot bone not found: " + boneName); + SlotData slotData = new SlotData(slotName, boneData); + + String color = (String)slotMap.get("color"); + if (color != null) slotData.getColor().set(Color.valueOf(color)); + + slotData.setAttachmentName((String)slotMap.get("attachment")); + + skeletonData.addSlot(slotData); + } + } + + // Skins. + OrderedMap slotMap = (OrderedMap)root.get("skins"); + if (slotMap != null) { + for (Entry entry : slotMap.entries()) { + Skin skin = new Skin(entry.key); + for (Entry slotEntry : ((OrderedMap)entry.value).entries()) { + int slotIndex = skeletonData.findSlotIndex(slotEntry.key); + for (Entry attachmentEntry : ((OrderedMap)slotEntry.value).entries()) { + Attachment attachment = readAttachment(attachmentEntry.key, attachmentEntry.value); + skin.addAttachment(slotIndex, attachmentEntry.key, attachment); + } + } + skeletonData.addSkin(skin); + if (skin.name.equals("default")) skeletonData.setDefaultSkin(skin); + } + } + + skeletonData.bones.shrink(); + skeletonData.slots.shrink(); + skeletonData.skins.shrink(); + return skeletonData; + } + + private Attachment readAttachment (String name, OrderedMap map) { + name = (String)map.get("name", name); + Attachment attachment; + String type = (String)map.get("type"); + if (type == null) type = ATTACHMENT_REGION; + if (type.equals(ATTACHMENT_REGION)) { + attachment = new RegionAttachment(name); + + } else if (type.equals(ATTACHMENT_REGION_SEQUENCE)) { + Float fps = (Float)map.get("fps"); + if (fps == null) throw new SerializationException("Region sequence attachment missing fps: " + name); + + String modeString = (String)map.get("mode"); + Mode mode = modeString == null ? Mode.forward : Mode.valueOf(modeString); + + attachment = new RegionSequenceAttachment(name, 1 / fps, mode); + + } else + throw new SerializationException("Unknown attachment type: " + type + " (" + name + ")"); + + attachment.setX(getFloat(map, "x", 0) * scale); + attachment.setY(getFloat(map, "y", 0) * scale); + attachment.setScaleX(getFloat(map, "scaleX", 1)); + attachment.setScaleY(getFloat(map, "scaleY", 1)); + attachment.setRotation(getFloat(map, "rotation", 0)); + attachment.setWidth(getFloat(map, "width", 32) * scale); + attachment.setHeight(getFloat(map, "height", 32) * scale); + return attachment; + } + + private float getFloat (OrderedMap map, String name, float defaultValue) { + Object value = map.get(name); + if (value == null) return defaultValue; + return (Float)value; + } + + public Animation readAnimation (FileHandle file, SkeletonData skeletonData) { + if (file == null) throw new IllegalArgumentException("file cannot be null."); + if (skeletonData == null) throw new IllegalArgumentException("skeletonData cannot be null."); + + OrderedMap map = json.fromJson(OrderedMap.class, file); + + Array timelines = new Array(); + float duration = 0; + + OrderedMap bonesMap = (OrderedMap)map.get("bones"); + for (Entry entry : bonesMap.entries()) { + String boneName = entry.key; + int boneIndex = skeletonData.findBoneIndex(boneName); + if (boneIndex == -1) throw new SerializationException("Bone not found: " + boneName); + OrderedMap propertyMap = (OrderedMap)entry.value; + + for (Entry propertyEntry : propertyMap.entries()) { + Array values = (Array)propertyEntry.value; + String timelineType = (String)propertyEntry.key; + if (timelineType.equals(TIMELINE_ROTATE)) { + RotateTimeline timeline = new RotateTimeline(values.size); + timeline.setBoneIndex(boneIndex); + + int keyframeIndex = 0; + for (OrderedMap valueMap : values) { + float time = (Float)valueMap.get("time"); + timeline.setKeyframe(keyframeIndex, time, (Float)valueMap.get("angle")); + readCurve(timeline, keyframeIndex, valueMap); + keyframeIndex++; + } + timelines.add(timeline); + duration = Math.max(duration, timeline.getDuration()); + + } else if (timelineType.equals(TIMELINE_TRANSLATE) || timelineType.equals(TIMELINE_SCALE)) { + TranslateTimeline timeline; + float timelineScale = 1; + if (timelineType.equals(TIMELINE_SCALE)) + timeline = new ScaleTimeline(values.size); + else { + timeline = new TranslateTimeline(values.size); + timelineScale = scale; + } + timeline.setBoneIndex(boneIndex); + + int keyframeIndex = 0; + for (OrderedMap valueMap : values) { + float time = (Float)valueMap.get("time"); + Float x = (Float)valueMap.get("x"), y = (Float)valueMap.get("y"); + timeline.setKeyframe(keyframeIndex, time, x == null ? 0 : (x * timelineScale), y == null ? 0 + : (y * timelineScale)); + readCurve(timeline, keyframeIndex, valueMap); + keyframeIndex++; + } + timelines.add(timeline); + duration = Math.max(duration, timeline.getDuration()); + + } else + throw new RuntimeException("Invalid timeline type for a bone: " + timelineType + " (" + boneName + ")"); + } + } + + OrderedMap slotsMap = (OrderedMap)map.get("slots"); + if (slotsMap != null) { + for (Entry entry : slotsMap.entries()) { + String slotName = entry.key; + int slotIndex = skeletonData.findSlotIndex(slotName); + OrderedMap propertyMap = (OrderedMap)entry.value; + + for (Entry propertyEntry : propertyMap.entries()) { + Array values = (Array)propertyEntry.value; + String timelineType = (String)propertyEntry.key; + if (timelineType.equals(TIMELINE_COLOR)) { + ColorTimeline timeline = new ColorTimeline(values.size); + timeline.setSlotIndex(slotIndex); + + int keyframeIndex = 0; + for (OrderedMap valueMap : values) { + float time = (Float)valueMap.get("time"); + Color color = Color.valueOf((String)valueMap.get("color")); + timeline.setKeyframe(keyframeIndex, time, color.r, color.g, color.b, color.a); + readCurve(timeline, keyframeIndex, valueMap); + keyframeIndex++; + } + timelines.add(timeline); + duration = Math.max(duration, timeline.getDuration()); + + } else if (timelineType.equals(TIMELINE_ATTACHMENT)) { + AttachmentTimeline timeline = new AttachmentTimeline(values.size); + timeline.setSlotIndex(slotIndex); + + int keyframeIndex = 0; + for (OrderedMap valueMap : values) { + float time = (Float)valueMap.get("time"); + timeline.setKeyframe(keyframeIndex++, time, (String)valueMap.get("name")); + } + timelines.add(timeline); + duration = Math.max(duration, timeline.getDuration()); + + } else + throw new RuntimeException("Invalid timeline type for a slot: " + timelineType + " (" + slotName + ")"); + } + } + } + + timelines.shrink(); + return new Animation(timelines, duration); + } + + private void readCurve (CurveTimeline timeline, int keyframeIndex, OrderedMap valueMap) { + Object curveObject = valueMap.get("curve"); + if (curveObject == null) return; + if (curveObject.equals("stepped")) + timeline.setStepped(keyframeIndex); + else if (curveObject instanceof Array) { + Array curve = (Array)curveObject; + timeline.setCurve(keyframeIndex, (Float)curve.get(0), (Float)curve.get(1), (Float)curve.get(2), (Float)curve.get(3)); + } + } +} diff --git a/spine-libgdx/src/com/esotericsoftware/spine/Skin.java b/spine-libgdx/src/com/esotericsoftware/spine/Skin.java new file mode 100644 index 000000000..d18b3cd6f --- /dev/null +++ b/spine-libgdx/src/com/esotericsoftware/spine/Skin.java @@ -0,0 +1,97 @@ + +package com.esotericsoftware.spine; + +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.ObjectMap; +import com.badlogic.gdx.utils.ObjectMap.Entry; + +/** Stores attachments by slot index and attachment name. */ +public class Skin { + static private final Key lookup = new Key(); + + final String name; + final ObjectMap attachments = new ObjectMap(); + + public Skin (String name) { + if (name == null) throw new IllegalArgumentException("name cannot be null."); + this.name = name; + } + + public void addAttachment (int slotIndex, String name, Attachment attachment) { + if (attachment == null) throw new IllegalArgumentException("attachment cannot be null."); + Key key = new Key(); + key.set(slotIndex, name); + attachments.put(key, attachment); + } + + /** @return May be null. */ + public Attachment getAttachment (int slotIndex, String name) { + lookup.set(slotIndex, name); + return attachments.get(lookup); + } + + public void findNamesForSlot (int slotIndex, Array names) { + if (names == null) throw new IllegalArgumentException("names cannot be null."); + for (Key key : attachments.keys()) + if (key.slotIndex == slotIndex) names.add(key.name); + } + + public void findAttachmentsForSlot (int slotIndex, Array attachments) { + if (attachments == null) throw new IllegalArgumentException("attachments cannot be null."); + for (Entry entry : this.attachments.entries()) + if (entry.key.slotIndex == slotIndex) attachments.add(entry.value); + } + + public void clear () { + attachments.clear(); + } + + public String getName () { + return name; + } + + public String toString () { + return name; + } + + static class Key { + int slotIndex; + String name; + int hashCode; + + public void set (int slotName, String name) { + if (name == null) throw new IllegalArgumentException("attachmentName cannot be null."); + this.slotIndex = slotName; + this.name = name; + hashCode = 31 * (31 + name.hashCode()) + slotIndex; + } + + public int hashCode () { + return hashCode; + } + + public boolean equals (Object object) { + if (object == null) return false; + Key other = (Key)object; + if (slotIndex != other.slotIndex) return false; + if (!name.equals(other.name)) return false; + return true; + } + + public String toString () { + return slotIndex + ":" + name; + } + } + + /** Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached. */ + void attachAll (Skeleton skeleton, Skin oldSkin) { + for (Entry entry : oldSkin.attachments.entries()) { + int slotIndex = entry.key.slotIndex; + Slot slot = skeleton.slots.get(slotIndex); + if (slot.attachment == entry.value) { + Attachment attachment = getAttachment(slotIndex, entry.key.name); + if (attachment != null) slot.setAttachment(attachment); + } + } + } +} diff --git a/spine-libgdx/src/com/esotericsoftware/spine/Slot.java b/spine-libgdx/src/com/esotericsoftware/spine/Slot.java new file mode 100644 index 000000000..4d8b3eb73 --- /dev/null +++ b/spine-libgdx/src/com/esotericsoftware/spine/Slot.java @@ -0,0 +1,93 @@ + +package com.esotericsoftware.spine; + +import com.badlogic.gdx.graphics.Color; + +public class Slot { + final SlotData data; + final Bone bone; + private final Skeleton skeleton; + final Color color; + Attachment attachment; + private float attachmentTime; + + Slot () { + data = null; + bone = null; + skeleton = null; + color = new Color(1, 1, 1, 1); + } + + public Slot (SlotData data, Skeleton skeleton, Bone bone) { + if (data == null) throw new IllegalArgumentException("data cannot be null."); + if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); + if (bone == null) throw new IllegalArgumentException("bone cannot be null."); + this.data = data; + this.skeleton = skeleton; + this.bone = bone; + color = new Color(1, 1, 1, 1); + } + + /** Copy constructor. */ + public Slot (Slot slot, Skeleton skeleton, Bone bone) { + if (slot == null) throw new IllegalArgumentException("slot cannot be null."); + if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); + if (bone == null) throw new IllegalArgumentException("bone cannot be null."); + data = slot.data; + this.skeleton = skeleton; + this.bone = bone; + color = new Color(slot.color); + attachment = slot.attachment; + attachmentTime = slot.attachmentTime; + } + + public SlotData getData () { + return data; + } + + public Skeleton getSkeleton () { + return skeleton; + } + + public Bone getBone () { + return bone; + } + + public Color getColor () { + return color; + } + + /** @return May be null. */ + public Attachment getAttachment () { + return attachment; + } + + /** Sets the attachment and resets {@link #getAttachmentTime()}. + * @param attachment May be null. */ + public void setAttachment (Attachment attachment) { + this.attachment = attachment; + attachmentTime = skeleton.time; + } + + public void setAttachmentTime (float time) { + attachmentTime = skeleton.time - time; + } + + /** Returns the time since the attachment was set. */ + public float getAttachmentTime () { + return skeleton.time - attachmentTime; + } + + void setToBindPose (int slotIndex) { + color.set(data.color); + setAttachment(data.attachmentName == null ? null : skeleton.getAttachment(slotIndex, data.attachmentName)); + } + + public void setToBindPose () { + setToBindPose(skeleton.slots.indexOf(this, true)); + } + + public String toString () { + return data.name; + } +} diff --git a/spine-libgdx/src/com/esotericsoftware/spine/SlotData.java b/spine-libgdx/src/com/esotericsoftware/spine/SlotData.java new file mode 100644 index 000000000..f5c9ce203 --- /dev/null +++ b/spine-libgdx/src/com/esotericsoftware/spine/SlotData.java @@ -0,0 +1,49 @@ + +package com.esotericsoftware.spine; + +import com.badlogic.gdx.graphics.Color; + +public class SlotData { + final String name; + final BoneData boneData; + final Color color = new Color(1, 1, 1, 1); + String attachmentName; + + SlotData () { + name = null; + boneData = null; + } + + public SlotData (String name, BoneData boneData) { + if (name == null) throw new IllegalArgumentException("name cannot be null."); + if (boneData == null) throw new IllegalArgumentException("boneData cannot be null."); + this.name = name; + this.boneData = boneData; + } + + public String getName () { + return name; + } + + public BoneData getBoneData () { + return boneData; + } + + public Color getColor () { + return color; + } + + /** @param attachmentName May be null. */ + public void setAttachmentName (String attachmentName) { + this.attachmentName = attachmentName; + } + + /** @return May be null. */ + public String getAttachmentName () { + return attachmentName; + } + + public String toString () { + return name; + } +} diff --git a/spine-libgdx/src/com/esotericsoftware/spine/attachments/RegionAttachment.java b/spine-libgdx/src/com/esotericsoftware/spine/attachments/RegionAttachment.java new file mode 100644 index 000000000..a5cf355da --- /dev/null +++ b/spine-libgdx/src/com/esotericsoftware/spine/attachments/RegionAttachment.java @@ -0,0 +1,153 @@ + +package com.esotericsoftware.spine.attachments; + +import com.esotericsoftware.spine.Attachment; +import com.esotericsoftware.spine.Bone; +import com.esotericsoftware.spine.Slot; + +import static com.badlogic.gdx.graphics.g2d.SpriteBatch.*; + +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion; +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.math.MathUtils; +import com.badlogic.gdx.utils.NumberUtils; + +/** Attachment that displays a texture region. */ +public class RegionAttachment extends Attachment { + private TextureRegion region; + private final float[] vertices = new float[20]; + private final float[] offset = new float[8]; + + public RegionAttachment (String name) { + super(name); + } + + public void updateOffset () { + float width = getWidth(); + float height = getHeight(); + float localX2 = width / 2; + float localY2 = height / 2; + float localX = -localX2; + float localY = -localY2; + if (region instanceof AtlasRegion) { + AtlasRegion region = (AtlasRegion)this.region; + if (region.rotate) { + localX += region.offsetX / region.originalWidth * height; + localY += region.offsetY / region.originalHeight * width; + localX2 -= (region.originalWidth - region.offsetX - region.packedHeight) / region.originalWidth * width; + localY2 -= (region.originalHeight - region.offsetY - region.packedWidth) / region.originalHeight * height; + } else { + localX += region.offsetX / region.originalWidth * width; + localY += region.offsetY / region.originalHeight * height; + localX2 -= (region.originalWidth - region.offsetX - region.packedWidth) / region.originalWidth * width; + localY2 -= (region.originalHeight - region.offsetY - region.packedHeight) / region.originalHeight * height; + } + } + float scaleX = getScaleX(); + float scaleY = getScaleY(); + localX *= scaleX; + localY *= scaleY; + localX2 *= scaleX; + localY2 *= scaleY; + float rotation = getRotation(); + float cos = MathUtils.cosDeg(rotation); + float sin = MathUtils.sinDeg(rotation); + float x = getX(); + float y = getY(); + float localXCos = localX * cos + x; + float localXSin = localX * sin; + float localYCos = localY * cos + y; + float localYSin = localY * sin; + float localX2Cos = localX2 * cos + x; + float localX2Sin = localX2 * sin; + float localY2Cos = localY2 * cos + y; + float localY2Sin = localY2 * sin; + float[] offset = this.offset; + offset[0] = localXCos - localYSin; + offset[1] = localYCos + localXSin; + offset[2] = localXCos - localY2Sin; + offset[3] = localY2Cos + localXSin; + offset[4] = localX2Cos - localY2Sin; + offset[5] = localY2Cos + localX2Sin; + offset[6] = localX2Cos - localYSin; + offset[7] = localYCos + localX2Sin; + } + + public void setRegion (TextureRegion region) { + if (region == null) throw new IllegalArgumentException("region cannot be null."); + TextureRegion oldRegion = this.region; + this.region = region; + float[] vertices = this.vertices; + if (region instanceof AtlasRegion && ((AtlasRegion)region).rotate) { + vertices[U2] = region.getU(); + vertices[V2] = region.getV2(); + vertices[U3] = region.getU(); + vertices[V3] = region.getV(); + vertices[U4] = region.getU2(); + vertices[V4] = region.getV(); + vertices[U1] = region.getU2(); + vertices[V1] = region.getV2(); + } else { + vertices[U1] = region.getU(); + vertices[V1] = region.getV2(); + vertices[U2] = region.getU(); + vertices[V2] = region.getV(); + vertices[U3] = region.getU2(); + vertices[V3] = region.getV(); + vertices[U4] = region.getU2(); + vertices[V4] = region.getV2(); + } + updateOffset(); + } + + public TextureRegion getRegion () { + if (region == null) throw new IllegalStateException("RegionAttachment is not resolved: " + this); + return region; + } + + public void draw (SpriteBatch batch, Slot slot) { + if (region == null) throw new IllegalStateException("RegionAttachment is not resolved: " + this); + + Color skeletonColor = slot.getSkeleton().getColor(); + Color slotColor = slot.getColor(); + float color = NumberUtils.intToFloatColor( // + ((int)(255 * skeletonColor.a * slotColor.a) << 24) // + | ((int)(255 * skeletonColor.b * slotColor.b) << 16) // + | ((int)(255 * skeletonColor.g * slotColor.g) << 8) // + | ((int)(255 * skeletonColor.r * slotColor.r))); + float[] vertices = this.vertices; + vertices[C1] = color; + vertices[C2] = color; + vertices[C3] = color; + vertices[C4] = color; + + updateWorldVertices(slot.getBone()); + + batch.draw(region.getTexture(), vertices, 0, vertices.length); + } + + public void updateWorldVertices (Bone bone) { + float[] vertices = this.vertices; + float[] offset = this.offset; + float x = bone.getWorldX(); + float y = bone.getWorldY(); + float m00 = bone.getM00(); + float m01 = bone.getM01(); + float m10 = bone.getM10(); + float m11 = bone.getM11(); + vertices[X1] = offset[0] * m00 + offset[1] * m01 + x; + vertices[Y1] = offset[0] * m10 + offset[1] * m11 + y; + vertices[X2] = offset[2] * m00 + offset[3] * m01 + x; + vertices[Y2] = offset[2] * m10 + offset[3] * m11 + y; + vertices[X3] = offset[4] * m00 + offset[5] * m01 + x; + vertices[Y3] = offset[4] * m10 + offset[5] * m11 + y; + vertices[X4] = offset[6] * m00 + offset[7] * m01 + x; + vertices[Y4] = offset[6] * m10 + offset[7] * m11 + y; + } + + public float[] getWorldVertices () { + return vertices; + } +} diff --git a/spine-libgdx/src/com/esotericsoftware/spine/attachments/RegionSequenceAttachment.java b/spine-libgdx/src/com/esotericsoftware/spine/attachments/RegionSequenceAttachment.java new file mode 100644 index 000000000..80acea639 --- /dev/null +++ b/spine-libgdx/src/com/esotericsoftware/spine/attachments/RegionSequenceAttachment.java @@ -0,0 +1,68 @@ + +package com.esotericsoftware.spine.attachments; + +import com.esotericsoftware.spine.Slot; + +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.math.MathUtils; + +/** Attachment that displays various texture regions over time. */ +public class RegionSequenceAttachment extends RegionAttachment { + private final Mode mode; + private float frameTime; + private TextureRegion[] regions; + + /** @param frameTime Time in seconds each frame is shown. */ + public RegionSequenceAttachment (String name, float frameTime, Mode mode) { + super(name); + if (mode == null) throw new IllegalArgumentException("mode cannot be null."); + + this.frameTime = frameTime; + this.mode = mode; + } + + public void draw (SpriteBatch batch, Slot slot) { + if (regions == null) throw new IllegalStateException("RegionSequenceAttachment is not resolved: " + this); + + int frameIndex = (int)(slot.getAttachmentTime() / frameTime); + switch (mode) { + case forward: + frameIndex = Math.min(regions.length - 1, frameIndex); + break; + case forwardLoop: + frameIndex = frameIndex % regions.length; + break; + case pingPong: + frameIndex = frameIndex % (regions.length * 2); + if (frameIndex >= regions.length) frameIndex = regions.length - 1 - (frameIndex - regions.length); + break; + case random: + frameIndex = MathUtils.random(regions.length - 1); + break; + case backward: + frameIndex = Math.max(regions.length - frameIndex - 1, 0); + break; + case backwardLoop: + frameIndex = frameIndex % regions.length; + frameIndex = regions.length - frameIndex - 1; + break; + } + setRegion(regions[frameIndex]); + super.draw(batch, slot); + } + + /** May be null if the attachment is not resolved. */ + public TextureRegion[] getRegions () { + if (regions == null) throw new IllegalStateException("RegionSequenceAttachment is not resolved: " + this); + return regions; + } + + public void setRegions (TextureRegion[] regions) { + this.regions = regions; + } + + static public enum Mode { + forward, backward, forwardLoop, backwardLoop, pingPong, random + } +} diff --git a/spine-libgdx/src/com/esotericsoftware/spine/attachments/TextureAtlasAttachmentResolver.java b/spine-libgdx/src/com/esotericsoftware/spine/attachments/TextureAtlasAttachmentResolver.java new file mode 100644 index 000000000..ed5b70530 --- /dev/null +++ b/spine-libgdx/src/com/esotericsoftware/spine/attachments/TextureAtlasAttachmentResolver.java @@ -0,0 +1,29 @@ + +package com.esotericsoftware.spine.attachments; + +import com.esotericsoftware.spine.Attachment; +import com.esotericsoftware.spine.AttachmentResolver; + +import com.badlogic.gdx.graphics.g2d.TextureAtlas; +import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion; + +public class TextureAtlasAttachmentResolver implements AttachmentResolver { + private TextureAtlas atlas; + + public TextureAtlasAttachmentResolver (TextureAtlas atlas) { + if (atlas == null) throw new IllegalArgumentException("atlas cannot be null."); + this.atlas = atlas; + } + + public void resolve (Attachment attachment) { + if (attachment instanceof RegionAttachment) { + AtlasRegion region = atlas.findRegion(attachment.getName()); + if (region == null) throw new RuntimeException("Region not found in atlas: " + attachment); + ((RegionAttachment)attachment).setRegion(region); + attachment.setResolved(true); + return; + } + + throw new IllegalArgumentException("Unable to resolve attachment of type: " + attachment.getClass().getName()); + } +} diff --git a/spine-libgdx/test/com/esotericsoftware/spine/MixTest.java b/spine-libgdx/test/com/esotericsoftware/spine/MixTest.java new file mode 100644 index 000000000..cb78495b0 --- /dev/null +++ b/spine-libgdx/test/com/esotericsoftware/spine/MixTest.java @@ -0,0 +1,124 @@ + +package com.esotericsoftware.spine; + +import com.badlogic.gdx.ApplicationAdapter; +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.Input.Keys; +import com.badlogic.gdx.InputAdapter; +import com.badlogic.gdx.backends.lwjgl.LwjglApplication; +import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration; +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.GL10; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.graphics.g2d.TextureAtlas; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; + +public class MixTest extends ApplicationAdapter { + SpriteBatch batch; + float time; + ShapeRenderer renderer; + + SkeletonData skeletonData; + Skeleton skeleton; + Animation walkAnimation; + Animation jumpAnimation; + + public void create () { + batch = new SpriteBatch(); + renderer = new ShapeRenderer(); + + final String name = "spineboy"; + + TextureAtlas atlas = new TextureAtlas(Gdx.files.internal(name + ".atlas")); + + if (true) { + SkeletonJson json = new SkeletonJson(atlas); + // json.setScale(2); + skeletonData = json.readSkeletonData(Gdx.files.internal(name + "-skeleton.json")); + walkAnimation = json.readAnimation(Gdx.files.internal(name + "-walk.json"), skeletonData); + jumpAnimation = json.readAnimation(Gdx.files.internal(name + "-jump.json"), skeletonData); + } else { + SkeletonBinary binary = new SkeletonBinary(atlas); + // binary.setScale(2); + skeletonData = binary.readSkeletonData(Gdx.files.internal(name + ".skel")); + walkAnimation = binary.readAnimation(Gdx.files.internal(name + "-walk.anim"), skeletonData); + jumpAnimation = binary.readAnimation(Gdx.files.internal(name + "-jump.anim"), skeletonData); + } + + skeleton = new Skeleton(skeletonData); + skeleton.setToBindPose(); + + final Bone root = skeleton.getRootBone(); + root.x = -50; + root.y = 20; + root.scaleX = 1f; + root.scaleY = 1f; + skeleton.updateWorldTransform(); + } + + public void render () { + float delta = Gdx.graphics.getDeltaTime() * 0.25f; // Reduced to make mixing easier to see. + + float jump = jumpAnimation.getDuration(); + float beforeJump = 1f; + float blendIn = 0.4f; + float blendOut = 0.4f; + float blendOutStart = beforeJump + jump - blendOut; + float total = 3.75f; + + time += delta; + + Bone root = skeleton.getRootBone(); + float speed = 180; + if (time > beforeJump + blendIn && time < blendOutStart) speed = 360; + root.setX(root.getX() + speed * delta); + + Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT); + batch.begin(); + batch.setColor(Color.GRAY); + + if (time > total) { + // restart + time = 0; + root.setX(-50); + } else if (time > beforeJump + jump) { + // just walk after jump + walkAnimation.apply(skeleton, time, true); + } else if (time > blendOutStart) { + // blend out jump + walkAnimation.apply(skeleton, time, true); + jumpAnimation.mix(skeleton, time - beforeJump, false, 1 - (time - blendOutStart) / blendOut); + } else if (time > beforeJump + blendIn) { + // just jump + jumpAnimation.apply(skeleton, time - beforeJump, false); + } else if (time > beforeJump) { + // blend in jump + walkAnimation.apply(skeleton, time, true); + jumpAnimation.mix(skeleton, time - beforeJump, false, (time - beforeJump) / blendIn); + } else { + // just walk before jump + walkAnimation.apply(skeleton, time, true); + } + + skeleton.updateWorldTransform(); + skeleton.update(Gdx.graphics.getDeltaTime()); + skeleton.draw(batch); + + batch.end(); + + // skeleton.drawDebug(renderer); + } + + public void resize (int width, int height) { + batch.getProjectionMatrix().setToOrtho2D(0, 0, width, height); + renderer.setProjectionMatrix(batch.getProjectionMatrix()); + } + + public static void main (String[] args) throws Exception { + LwjglApplicationConfiguration config = new LwjglApplicationConfiguration(); + config.title = "Mix Test"; + config.width = 640; + config.height = 480; + new LwjglApplication(new MixTest(), config); + } +} diff --git a/spine-libgdx/test/com/esotericsoftware/spine/SkeletonTest.java b/spine-libgdx/test/com/esotericsoftware/spine/SkeletonTest.java new file mode 100644 index 000000000..7367dabba --- /dev/null +++ b/spine-libgdx/test/com/esotericsoftware/spine/SkeletonTest.java @@ -0,0 +1,123 @@ + +package com.esotericsoftware.spine; + +import com.badlogic.gdx.ApplicationAdapter; +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.Input.Keys; +import com.badlogic.gdx.InputAdapter; +import com.badlogic.gdx.backends.lwjgl.LwjglApplication; +import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration; +import com.badlogic.gdx.files.FileHandle; +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.GL10; +import com.badlogic.gdx.graphics.Pixmap; +import com.badlogic.gdx.graphics.Pixmap.Format; +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.graphics.g2d.TextureAtlas; +import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion; +import com.badlogic.gdx.graphics.g2d.TextureAtlas.TextureAtlasData; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; + +public class SkeletonTest extends ApplicationAdapter { + SpriteBatch batch; + float time; + ShapeRenderer renderer; + + SkeletonData skeletonData; + Skeleton skeleton; + Animation animation; + + public void create () { + batch = new SpriteBatch(); + renderer = new ShapeRenderer(); + + final String name = "goblins"; + + // A regular texture atlas would normally usually be used. This returns a white image for images not found in the atlas. + Pixmap pixmap = new Pixmap(32, 32, Format.RGBA8888); + pixmap.setColor(Color.WHITE); + pixmap.fill(); + final AtlasRegion fake = new AtlasRegion(new Texture(pixmap), 0, 0, 32, 32); + pixmap.dispose(); + FileHandle atlasFile = Gdx.files.internal(name + ".atlas"); + TextureAtlasData data = !atlasFile.exists() ? null : new TextureAtlasData(atlasFile, atlasFile.parent(), false); + TextureAtlas atlas = new TextureAtlas(data) { + public AtlasRegion findRegion (String name) { + AtlasRegion region = super.findRegion(name); + return region != null ? region : fake; + } + }; + + if (true) { + SkeletonJson json = new SkeletonJson(atlas); + // json.setScale(2); + skeletonData = json.readSkeletonData(Gdx.files.internal(name + "-skeleton.json")); + animation = json.readAnimation(Gdx.files.internal(name + "-walk.json"), skeletonData); + } else { + SkeletonBinary binary = new SkeletonBinary(atlas); + // binary.setScale(2); + skeletonData = binary.readSkeletonData(Gdx.files.internal(name + ".skel")); + animation = binary.readAnimation(Gdx.files.internal(name + "-walk.anim"), skeletonData); + } + + skeleton = new Skeleton(skeletonData); + if (name.equals("goblins")) skeleton.setSkin("goblin"); + skeleton.setToBindPose(); + + Bone root = skeleton.getRootBone(); + root.x = 50; + root.y = 20; + root.scaleX = 1f; + root.scaleY = 1f; + skeleton.updateWorldTransform(); + + Gdx.input.setInputProcessor(new InputAdapter() { + public boolean keyDown (int keycode) { + if (keycode == Keys.SPACE) { + if (name.equals("goblins")) { + skeleton.setSkin(skeleton.getSkin().getName().equals("goblin") ? "goblingirl" : "goblin"); + skeleton.setSlotsToBindPose(); + } + } + return true; + } + }); + } + + public void render () { + time += Gdx.graphics.getDeltaTime(); + + Bone root = skeleton.getRootBone(); + float x = root.getX() + 160 * Gdx.graphics.getDeltaTime() * (skeleton.getFlipX() ? -1 : 1); + if (x > Gdx.graphics.getWidth()) skeleton.setFlipX(true); + if (x < 0) skeleton.setFlipX(false); + root.setX(x); + + Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT); + batch.begin(); + batch.setColor(Color.GRAY); + + animation.apply(skeleton, time, true); + skeleton.updateWorldTransform(); + skeleton.update(Gdx.graphics.getDeltaTime()); + skeleton.draw(batch); + + batch.end(); + + skeleton.drawDebug(renderer); + } + + public void resize (int width, int height) { + batch.getProjectionMatrix().setToOrtho2D(0, 0, width, height); + renderer.setProjectionMatrix(batch.getProjectionMatrix()); + } + + public static void main (String[] args) throws Exception { + LwjglApplicationConfiguration config = new LwjglApplicationConfiguration(); + config.title = "Skeleton Test"; + config.width = 640; + config.height = 480; + new LwjglApplication(new SkeletonTest(), config); + } +} diff --git a/spine-libgdx/test/goblins-skeleton.json b/spine-libgdx/test/goblins-skeleton.json new file mode 100644 index 000000000..be335a530 --- /dev/null +++ b/spine-libgdx/test/goblins-skeleton.json @@ -0,0 +1,202 @@ +{ +"bones": [ + { "name": "root", "length": 0 }, + { "name": "hip", "parent": "root", "length": 0, "x": 0.64, "y": 114.41 }, + { "name": "left upper leg", "parent": "hip", "length": 50.39, "x": 14.45, "y": 2.81, "rotation": -89.09 }, + { "name": "left lower leg", "parent": "left upper leg", "length": 49.89, "x": 56.34, "y": 0.98, "rotation": -16.65 }, + { "name": "left foot", "parent": "left lower leg", "length": 46.5, "x": 58.94, "y": -7.61, "rotation": 102.43 }, + { "name": "right upper leg", "parent": "hip", "length": 42.45, "x": -20.07, "y": -6.83, "rotation": -97.49 }, + { "name": "right lower leg", "parent": "right upper leg", "length": 58.52, "x": 42.99, "y": -0.61, "rotation": -14.34 }, + { "name": "right foot", "parent": "right lower leg", "length": 45.45, "x": 64.88, "y": 0.04, "rotation": 110.3 }, + { "name": "torso", "parent": "hip", "length": 85.82, "x": -6.42, "y": 1.97, "rotation": 93.92 }, + { "name": "neck", "parent": "torso", "length": 18.38, "x": 81.67, "y": -6.34, "rotation": -1.51 }, + { "name": "head", "parent": "neck", "length": 68.28, "x": 20.93, "y": 11.59, "rotation": -13.92 }, + { "name": "right shoulder", "parent": "torso", "length": 37.24, "x": 76.02, "y": 18.14, "rotation": 133.88 }, + { "name": "right arm", "parent": "right shoulder", "length": 36.74, "x": 37.6, "y": 0.31, "rotation": 36.32 }, + { "name": "right hand", "parent": "right arm", "length": 15.32, "x": 36.9, "y": 0.34, "rotation": 2.35 }, + { "name": "left shoulder", "parent": "torso", "length": 35.43, "x": 74.04, "y": -20.38, "rotation": -156.96 }, + { "name": "left arm", "parent": "left shoulder", "length": 35.62, "x": 37.85, "y": -2.34, "rotation": 28.16 }, + { "name": "left hand", "parent": "left arm", "length": 11.52, "x": 35.62, "y": 0.07, "rotation": 2.7 }, + { "name": "pelvis", "parent": "hip", "length": 0, "x": 1.41, "y": -6.57 } +], +"slots": [ + { "name": "left shoulder", "bone": "left shoulder", "attachment": "left shoulder" }, + { "name": "left arm", "bone": "left arm", "attachment": "left arm" }, + { "name": "left hand item", "bone": "left hand", "attachment": "left hand item" }, + { "name": "left hand", "bone": "left hand", "attachment": "left hand" }, + { "name": "left foot", "bone": "left foot", "attachment": "left foot" }, + { "name": "left lower leg", "bone": "left lower leg", "attachment": "left lower leg" }, + { "name": "left upper leg", "bone": "left upper leg", "attachment": "left upper leg" }, + { "name": "neck", "bone": "neck", "attachment": "neck" }, + { "name": "torso", "bone": "torso", "attachment": "torso" }, + { "name": "pelvis", "bone": "pelvis", "attachment": "pelvis" }, + { "name": "right foot", "bone": "right foot", "attachment": "right foot" }, + { "name": "right lower leg", "bone": "right lower leg", "attachment": "right lower leg" }, + { "name": "undie straps", "bone": "pelvis", "attachment": "undie straps" }, + { "name": "undies", "bone": "pelvis", "attachment": "undies" }, + { "name": "right upper leg", "bone": "right upper leg", "attachment": "right upper leg" }, + { "name": "head", "bone": "head", "attachment": "head" }, + { "name": "eyes", "bone": "head", "attachment": "eyes open" }, + { "name": "right shoulder", "bone": "right shoulder", "attachment": "right shoulder" }, + { "name": "right arm", "bone": "right arm", "attachment": "right arm" }, + { "name": "right hand item 2", "bone": "right hand", "attachment": "right hand item 2" }, + { "name": "right hand", "bone": "right hand", "attachment": "right hand" }, + { "name": "right hand item", "bone": "right hand", "attachment": "right hand item" } +], +"skins": { + "goblin": { + "neck": { + "neck": { "name": "goblin/neck", "x": 10.1, "y": 0.42, "rotation": -93.69, "width": 36, "height": 41 } + }, + "undies": { + "undies": { "name": "goblin/undies", "x": 6.3, "y": 0.12, "rotation": 0.91, "width": 36, "height": 29 } + }, + "right hand": { + "right hand": { "name": "goblin/right-hand", "x": 7.88, "y": 2.78, "rotation": 91.96, "width": 36, "height": 37 } + }, + "right arm": { + "right arm": { "name": "goblin/right-arm", "x": 16.44, "y": -1.04, "rotation": 94.32, "width": 23, "height": 50 } + }, + "head": { + "head": { "name": "goblin/head", "x": 25.73, "y": 2.33, "rotation": -92.29, "width": 103, "height": 66 } + }, + "left shoulder": { + "left shoulder": { "name": "goblin/left-shoulder", "x": 15.56, "y": -2.26, "rotation": 62.01, "width": 29, "height": 44 } + }, + "left arm": { + "left arm": { + "name": "goblin/left-arm", + "x": 16.7, + "y": -1.69, + "scaleX": 1.057, + "scaleY": 1.057, + "rotation": 33.84, + "width": 37, + "height": 35 + } + }, + "left hand": { + "left hand": { + "name": "goblin/left-hand", + "x": 3.47, + "y": 3.41, + "scaleX": 0.892, + "scaleY": 0.892, + "rotation": 31.14, + "width": 36, + "height": 41 + } + }, + "right lower leg": { + "right lower leg": { "name": "goblin/right-lower-leg", "x": 25.68, "y": -3.15, "rotation": 111.83, "width": 36, "height": 76 } + }, + "right upper leg": { + "right upper leg": { "name": "goblin/right-upper-leg", "x": 20.35, "y": 1.47, "rotation": 97.49, "width": 34, "height": 63 } + }, + "pelvis": { + "pelvis": { "name": "goblin/pelvis", "x": -5.61, "y": 0.76, "width": 62, "height": 43 } + }, + "left lower leg": { + "left lower leg": { "name": "goblin/left-lower-leg", "x": 23.58, "y": -2.06, "rotation": 105.75, "width": 33, "height": 70 } + }, + "left upper leg": { + "left upper leg": { "name": "goblin/left-upper-leg", "x": 29.68, "y": -3.87, "rotation": 89.09, "width": 33, "height": 73 } + }, + "torso": { + "torso": { "name": "goblin/torso", "x": 38.09, "y": -3.87, "rotation": -94.95, "width": 68, "height": 96 } + }, + "right shoulder": { + "right shoulder": { "name": "goblin/right-shoulder", "x": 15.68, "y": -1.03, "rotation": 130.65, "width": 39, "height": 45 } + }, + "right foot": { + "right foot": { "name": "goblin/right-foot", "x": 23.56, "y": 9.8, "rotation": 1.52, "width": 63, "height": 33 } + }, + "left foot": { + "left foot": { "name": "goblin/left-foot", "x": 24.85, "y": 8.74, "rotation": 3.32, "width": 65, "height": 31 } + }, + "right hand item": { + "right hand item": { "name": "goblin/shield", "x": -0.47, "y": 1.1, "rotation": 91.16, "width": 70, "height": 72 } + }, + "left hand item": { + "left hand item": { "name": "goblin/spear", "x": -4.55, "y": 39.2, "rotation": 13.04, "width": 22, "height": 368 } + }, + "undie straps": { + "undie straps": { "name": "goblin/undie-straps", "x": -3.87, "y": 13.1, "scaleX": 1.089, "width": 55, "height": 19 } + } + }, + "goblingirl": { + "left upper leg": { + "left upper leg": { "name": "goblingirl/left-upper-leg", "x": 30.21, "y": -2.95, "rotation": 89.09, "width": 33, "height": 70 } + }, + "left lower leg": { + "left lower leg": { "name": "goblingirl/left-lower-leg", "x": 25.02, "y": -0.6, "rotation": 105.75, "width": 33, "height": 70 } + }, + "left foot": { + "left foot": { "name": "goblingirl/left-foot", "x": 25.17, "y": 7.92, "rotation": 3.32, "width": 65, "height": 31 } + }, + "right upper leg": { + "right upper leg": { "name": "goblingirl/right-upper-leg", "x": 19.69, "y": 2.13, "rotation": 97.49, "width": 34, "height": 63 } + }, + "right lower leg": { + "right lower leg": { "name": "goblingirl/right-lower-leg", "x": 26.15, "y": -3.27, "rotation": 111.83, "width": 36, "height": 76 } + }, + "right foot": { + "right foot": { "name": "goblingirl/right-foot", "x": 23.46, "y": 9.66, "rotation": 1.52, "width": 63, "height": 33 } + }, + "torso": { + "torso": { "name": "goblingirl/torso", "x": 36.28, "y": -5.14, "rotation": -95.74, "width": 68, "height": 96 } + }, + "left shoulder": { + "left shoulder": { "name": "goblingirl/left-shoulder", "x": 19.8, "y": -0.42, "rotation": 61.21, "width": 28, "height": 46 } + }, + "left arm": { + "left arm": { "name": "goblingirl/left-arm", "x": 19.64, "y": -2.42, "rotation": 33.05, "width": 37, "height": 35 } + }, + "left hand": { + "left hand": { + "name": "goblingirl/left-hand", + "x": 4.34, + "y": 2.39, + "scaleX": 0.896, + "scaleY": 0.896, + "rotation": 30.34, + "width": 35, + "height": 40 + } + }, + "neck": { + "neck": { "name": "goblingirl/neck", "x": 6.16, "y": -3.14, "rotation": -98.86, "width": 35, "height": 41 } + }, + "head": { + "head": { "name": "goblingirl/head", "x": 27.71, "y": -4.32, "rotation": -85.58, "width": 103, "height": 81 } + }, + "right shoulder": { + "right shoulder": { "name": "goblingirl/right-shoulder", "x": 14.46, "y": 0.45, "rotation": 129.85, "width": 39, "height": 45 } + }, + "right arm": { + "right arm": { "name": "goblingirl/right-arm", "x": 16.85, "y": -0.66, "rotation": 93.52, "width": 28, "height": 50 } + }, + "right hand": { + "right hand": { "name": "goblingirl/right-hand", "x": 7.21, "y": 3.43, "rotation": 91.16, "width": 36, "height": 37 } + }, + "pelvis": { + "pelvis": { "name": "goblingirl/pelvis", "x": -3.87, "y": 3.18, "width": 62, "height": 43 } + }, + "undie straps": { + "undie straps": { "name": "goblingirl/undie-straps", "x": -1.51, "y": 14.18, "width": 55, "height": 19 } + }, + "undies": { + "undies": { "name": "goblingirl/undies", "x": 5.4, "y": 1.7, "width": 36, "height": 29 } + }, + "left hand item": { + "left hand item": { "name": "goblingirl/dagger", "x": 7.88, "y": -23.45, "rotation": 10.47, "width": 26, "height": 108 } + }, + "right hand item 2": { + "right hand item 2": { "name": "goblingirl/dagger", "x": 7.17, "y": -22.38, "rotation": -5.27, "width": 26, "height": 108 } + }, + "right hand item": { + "right hand item": { "name": "goblingirl/dagger-tip", "x": 13.45, "y": 22.07, "rotation": -3.23, "width": 17, "height": 17 } + } + } +} +} \ No newline at end of file diff --git a/spine-libgdx/test/goblins-walk.anim b/spine-libgdx/test/goblins-walk.anim new file mode 100644 index 000000000..f77b26afd Binary files /dev/null and b/spine-libgdx/test/goblins-walk.anim differ diff --git a/spine-libgdx/test/goblins-walk.json b/spine-libgdx/test/goblins-walk.json new file mode 100644 index 000000000..5dc23cf44 --- /dev/null +++ b/spine-libgdx/test/goblins-walk.json @@ -0,0 +1,288 @@ +{ +"bones": { + "left upper leg": { + "rotate": [ + { "time": 0, "angle": -26.55 }, + { "time": 0.1333, "angle": -8.78 }, + { "time": 0.2666, "angle": 9.51 }, + { "time": 0.4, "angle": 30.74 }, + { "time": 0.5333, "angle": 25.33 }, + { "time": 0.6666, "angle": 26.11 }, + { "time": 0.8, "angle": -7.7 }, + { "time": 0.9333, "angle": -21.19 }, + { "time": 1.0666, "angle": -26.55 } + ], + "translate": [ + { "time": 0, "x": -1.32, "y": 1.7 }, + { "time": 0.4, "x": -0.06, "y": 2.42 }, + { "time": 1.0666, "x": -1.32, "y": 1.7 } + ] + }, + "right upper leg": { + "rotate": [ + { "time": 0, "angle": 42.45 }, + { "time": 0.1333, "angle": 52.1 }, + { "time": 0.2666, "angle": 8.53 }, + { "time": 0.5333, "angle": -16.93 }, + { "time": 0.6666, "angle": 1.89 }, + { + "time": 0.8, + "angle": 28.06, + "curve": [ 0.462, 0.11, 1, 1 ] + }, + { + "time": 0.9333, + "angle": 58.68, + "curve": [ 0.5, 0.02, 1, 1 ] + }, + { "time": 1.0666, "angle": 42.45 } + ], + "translate": [ + { "time": 0, "x": 6.23, "y": 0 }, + { "time": 0.2666, "x": 2.14, "y": 2.4 }, + { "time": 0.5333, "x": 2.44, "y": 4.8 }, + { "time": 1.0666, "x": 6.23, "y": 0 } + ] + }, + "left lower leg": { + "rotate": [ + { "time": 0, "angle": -22.98 }, + { "time": 0.1333, "angle": -63.5 }, + { "time": 0.2666, "angle": -73.76 }, + { "time": 0.5333, "angle": 5.11 }, + { "time": 0.6666, "angle": -28.29 }, + { "time": 0.8, "angle": 4.08 }, + { "time": 0.9333, "angle": 3.53 }, + { "time": 1.0666, "angle": -22.98 } + ], + "translate": [ + { "time": 0, "x": 0, "y": 0 }, + { "time": 0.2666, "x": 2.55, "y": -0.47 }, + { "time": 0.5333, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 1.0666, "x": 0, "y": 0 } + ] + }, + "left foot": { + "rotate": [ + { "time": 0, "angle": -3.69 }, + { "time": 0.1333, "angle": -10.42 }, + { "time": 0.2666, "angle": -5.01 }, + { "time": 0.4, "angle": 3.87 }, + { "time": 0.5333, "angle": -3.87 }, + { "time": 0.6666, "angle": 2.78 }, + { "time": 0.8, "angle": 1.68 }, + { "time": 0.9333, "angle": -8.54 }, + { "time": 1.0666, "angle": -3.69 } + ] + }, + "right shoulder": { + "rotate": [ + { + "time": 0, + "angle": 5.29, + "curve": [ 0.264, 0, 0.75, 1 ] + }, + { "time": 0.6666, "angle": 6.65 }, + { "time": 1.0666, "angle": 5.29 } + ] + }, + "right arm": { + "rotate": [ + { + "time": 0, + "angle": -4.02, + "curve": [ 0.267, 0, 0.804, 0.99 ] + }, + { + "time": 0.6666, + "angle": 19.78, + "curve": [ 0.307, 0, 0.787, 0.99 ] + }, + { "time": 1.0666, "angle": -4.02 } + ] + }, + "right hand": { + "rotate": [ + { "time": 0, "angle": 8.98 }, + { "time": 0.6666, "angle": 0.51 }, + { "time": 1.0666, "angle": 8.98 } + ] + }, + "left shoulder": { + "rotate": [ + { + "time": 0, + "angle": 6.25, + "curve": [ 0.339, 0, 0.683, 1 ] + }, + { + "time": 0.5333, + "angle": -11.78, + "curve": [ 0.281, 0, 0.686, 0.99 ] + }, + { "time": 1.0666, "angle": 6.25 } + ], + "translate": [ + { "time": 0, "x": 1.15, "y": 0.23 } + ] + }, + "left hand": { + "rotate": [ + { + "time": 0, + "angle": -21.23, + "curve": [ 0.295, 0, 0.755, 0.98 ] + }, + { + "time": 0.5333, + "angle": -27.28, + "curve": [ 0.241, 0, 0.75, 0.97 ] + }, + { "time": 1.0666, "angle": -21.23 } + ] + }, + "left arm": { + "rotate": [ + { + "time": 0, + "angle": 28.37, + "curve": [ 0.339, 0, 0.683, 1 ] + }, + { + "time": 0.5333, + "angle": 60.09, + "curve": [ 0.281, 0, 0.686, 0.99 ] + }, + { "time": 1.0666, "angle": 28.37 } + ] + }, + "torso": { + "rotate": [ + { "time": 0, "angle": -10.28 }, + { + "time": 0.1333, + "angle": -15.38, + "curve": [ 0.545, 0, 0.818, 1 ] + }, + { + "time": 0.4, + "angle": -9.78, + "curve": [ 0.58, 0.17, 0.669, 0.99 ] + }, + { + "time": 0.6666, + "angle": -15.75, + "curve": [ 0.235, 0.01, 0.795, 1 ] + }, + { + "time": 0.9333, + "angle": -7.06, + "curve": [ 0.209, 0, 0.816, 0.98 ] + }, + { "time": 1.0666, "angle": -10.28 } + ], + "translate": [ + { "time": 0, "x": -1.29, "y": 1.68 } + ] + }, + "right foot": { + "rotate": [ + { "time": 0, "angle": -5.25 }, + { "time": 0.2666, "angle": -1.91 }, + { "time": 0.4, "angle": -6.45 }, + { "time": 0.5333, "angle": -5.39 }, + { "time": 0.8, "angle": -11.68 }, + { "time": 0.9333, "angle": 0.46 }, + { "time": 1.0666, "angle": -5.25 } + ] + }, + "right lower leg": { + "rotate": [ + { + "time": 0, + "angle": -3.39, + "curve": [ 0.316, 0.01, 0.741, 0.98 ] + }, + { + "time": 0.1333, + "angle": -45.53, + "curve": [ 0.229, 0, 0.738, 0.97 ] + }, + { "time": 0.2666, "angle": -4.83 }, + { "time": 0.5333, "angle": -19.53 }, + { "time": 0.6666, "angle": -64.8 }, + { + "time": 0.8, + "angle": -82.56, + "curve": [ 0.557, 0.18, 1, 1 ] + }, + { "time": 1.0666, "angle": -3.39 } + ], + "translate": [ + { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 0.5333, "x": 0, "y": 0 }, + { "time": 0.6666, "x": 2.18, "y": 0.21 }, + { "time": 1.0666, "x": 0, "y": 0 } + ] + }, + "hip": { + "rotate": [ + { "time": 0, "angle": 0, "curve": "stepped" }, + { "time": 1.0666, "angle": 0 } + ], + "translate": [ + { "time": 0, "x": 0, "y": -4.16 }, + { + "time": 0.1333, + "x": 0, + "y": -7.05, + "curve": [ 0.359, 0.47, 0.646, 0.74 ] + }, + { "time": 0.4, "x": 0, "y": 6.78 }, + { "time": 0.5333, "x": 0, "y": -6.13 }, + { + "time": 0.6666, + "x": 0, + "y": -7.05, + "curve": [ 0.359, 0.47, 0.646, 0.74 ] + }, + { "time": 0.9333, "x": 0, "y": 6.78 }, + { "time": 1.0666, "x": 0, "y": -4.16 } + ] + }, + "neck": { + "rotate": [ + { "time": 0, "angle": 3.6 }, + { "time": 0.1333, "angle": 17.49 }, + { "time": 0.2666, "angle": 6.1 }, + { "time": 0.4, "angle": 3.45 }, + { "time": 0.5333, "angle": 5.17 }, + { "time": 0.6666, "angle": 18.36 }, + { "time": 0.8, "angle": 6.09 }, + { "time": 0.9333, "angle": 2.28 }, + { "time": 1.0666, "angle": 3.6 } + ] + }, + "head": { + "rotate": [ + { + "time": 0, + "angle": 3.6, + "curve": [ 0, 0, 0.704, 1.17 ] + }, + { "time": 0.1333, "angle": -0.2 }, + { "time": 0.2666, "angle": 6.1 }, + { "time": 0.4, "angle": 3.45 }, + { + "time": 0.5333, + "angle": 5.17, + "curve": [ 0, 0, 0.704, 1.61 ] + }, + { "time": 0.7, "angle": 1.1 }, + { "time": 0.8, "angle": 6.09 }, + { "time": 0.9333, "angle": 2.28 }, + { "time": 1.0666, "angle": 3.6 } + ] + } +} +} \ No newline at end of file diff --git a/spine-libgdx/test/goblins.atlas b/spine-libgdx/test/goblins.atlas new file mode 100644 index 000000000..4638120bf --- /dev/null +++ b/spine-libgdx/test/goblins.atlas @@ -0,0 +1,297 @@ + +goblins.png +format: RGBA8888 +filter: Nearest,Nearest +repeat: none +goblin/spear + rotate: false + xy: 1, 123 + size: 22, 368 + orig: 22, 368 + offset: 0, 0 + index: -1 +goblin/head + rotate: false + xy: 25, 425 + size: 103, 66 + orig: 103, 66 + offset: 0, 0 + index: -1 +goblin/torso + rotate: false + xy: 25, 327 + size: 68, 96 + orig: 68, 96 + offset: 0, 0 + index: -1 +goblin/shield + rotate: false + xy: 25, 253 + size: 70, 72 + orig: 70, 72 + offset: 0, 0 + index: -1 +goblin/right-lower-leg + rotate: false + xy: 25, 175 + size: 36, 76 + orig: 36, 76 + offset: 0, 0 + index: -1 +goblin/left-upper-leg + rotate: false + xy: 95, 350 + size: 33, 73 + orig: 33, 73 + offset: 0, 0 + index: -1 +goblin/pelvis + rotate: false + xy: 130, 448 + size: 62, 43 + orig: 62, 43 + offset: 0, 0 + index: -1 +goblin/left-lower-leg + rotate: false + xy: 130, 376 + size: 33, 70 + orig: 33, 70 + offset: 0, 0 + index: -1 +goblin/right-upper-leg + rotate: false + xy: 63, 188 + size: 34, 63 + orig: 34, 63 + offset: 0, 0 + index: -1 +goblin/right-shoulder + rotate: false + xy: 194, 446 + size: 39, 45 + orig: 39, 45 + offset: 0, 0 + index: -1 +goblin/left-hand + rotate: false + xy: 25, 132 + size: 36, 41 + orig: 36, 41 + offset: 0, 0 + index: -1 +goblin/neck + rotate: false + xy: 63, 145 + size: 36, 41 + orig: 36, 41 + offset: 0, 0 + index: -1 +goblin/right-arm + rotate: false + xy: 165, 396 + size: 23, 50 + orig: 23, 50 + offset: 0, 0 + index: -1 +goblin/left-foot + rotate: false + xy: 190, 413 + size: 65, 31 + orig: 65, 31 + offset: 0, 0 + index: -1 +goblin/right-foot + rotate: false + xy: 190, 378 + size: 63, 33 + orig: 63, 33 + offset: 0, 0 + index: -1 +goblin/left-shoulder + rotate: false + xy: 1, 77 + size: 29, 44 + orig: 29, 44 + offset: 0, 0 + index: -1 +goblin/right-hand + rotate: false + xy: 1, 38 + size: 36, 37 + orig: 36, 37 + offset: 0, 0 + index: -1 +goblin/left-arm + rotate: false + xy: 1, 1 + size: 37, 35 + orig: 37, 35 + offset: 0, 0 + index: -1 +goblin/undies + rotate: false + xy: 130, 345 + size: 36, 29 + orig: 36, 29 + offset: 0, 0 + index: -1 +goblin/undie-straps + rotate: false + xy: 168, 357 + size: 55, 19 + orig: 55, 19 + offset: 0, 0 + index: -1 + +goblins2.png +format: RGBA8888 +filter: Nearest,Nearest +repeat: none +goblingirl/goblingirl + rotate: false + xy: 1, 217 + size: 148, 294 + orig: 148, 294 + offset: 0, 0 + index: -1 +goblingirl/head + rotate: false + xy: 1, 134 + size: 103, 81 + orig: 103, 81 + offset: 0, 0 + index: -1 +goblingirl/torso + rotate: false + xy: 151, 415 + size: 68, 96 + orig: 68, 96 + offset: 0, 0 + index: -1 +goblingirl/right-upper-leg + rotate: false + xy: 221, 448 + size: 34, 63 + orig: 34, 63 + offset: 0, 0 + index: -1 +goblingirl/dagger + rotate: false + xy: 1, 24 + size: 26, 108 + orig: 26, 108 + offset: 0, 0 + index: -1 +goblingirl/right-lower-leg + rotate: false + xy: 106, 139 + size: 36, 76 + orig: 36, 76 + offset: 0, 0 + index: -1 +goblingirl/pelvis + rotate: false + xy: 151, 370 + size: 62, 43 + orig: 62, 43 + offset: 0, 0 + index: -1 +goblingirl/left-lower-leg + rotate: false + xy: 29, 62 + size: 33, 70 + orig: 33, 70 + offset: 0, 0 + index: -1 +goblingirl/left-upper-leg + rotate: false + xy: 151, 298 + size: 33, 70 + orig: 33, 70 + offset: 0, 0 + index: -1 +goblingirl/right-shoulder + rotate: false + xy: 64, 87 + size: 39, 45 + orig: 39, 45 + offset: 0, 0 + index: -1 +goblingirl/right-arm + rotate: false + xy: 151, 246 + size: 28, 50 + orig: 28, 50 + offset: 0, 0 + index: -1 +goblingirl/left-shoulder + rotate: false + xy: 186, 322 + size: 28, 46 + orig: 28, 46 + offset: 0, 0 + index: -1 +goblingirl/left-hand + rotate: false + xy: 29, 20 + size: 35, 40 + orig: 35, 40 + offset: 0, 0 + index: -1 +goblingirl/undie-straps + rotate: false + xy: 64, 66 + size: 55, 19 + orig: 55, 19 + offset: 0, 0 + index: -1 +goblingirl/right-foot + rotate: false + xy: 66, 31 + size: 63, 33 + orig: 63, 33 + offset: 0, 0 + index: -1 +goblingirl/left-arm + rotate: false + xy: 151, 209 + size: 37, 35 + orig: 37, 35 + offset: 0, 0 + index: -1 +goblingirl/neck + rotate: false + xy: 144, 166 + size: 35, 41 + orig: 35, 41 + offset: 0, 0 + index: -1 +goblingirl/left-foot + rotate: false + xy: 144, 133 + size: 65, 31 + orig: 65, 31 + offset: 0, 0 + index: -1 +goblingirl/right-hand + rotate: false + xy: 186, 283 + size: 36, 37 + orig: 36, 37 + offset: 0, 0 + index: -1 +goblingirl/undies + rotate: false + xy: 181, 252 + size: 36, 29 + orig: 36, 29 + offset: 0, 0 + index: -1 +goblingirl/dagger-tip + rotate: false + xy: 1, 1 + size: 17, 17 + orig: 17, 17 + offset: 0, 0 + index: -1 diff --git a/spine-libgdx/test/goblins.png b/spine-libgdx/test/goblins.png new file mode 100644 index 000000000..05a15f299 Binary files /dev/null and b/spine-libgdx/test/goblins.png differ diff --git a/spine-libgdx/test/goblins.skel b/spine-libgdx/test/goblins.skel new file mode 100644 index 000000000..6b9e70b10 Binary files /dev/null and b/spine-libgdx/test/goblins.skel differ diff --git a/spine-libgdx/test/goblins2.png b/spine-libgdx/test/goblins2.png new file mode 100644 index 000000000..f90ce4753 Binary files /dev/null and b/spine-libgdx/test/goblins2.png differ diff --git a/spine-libgdx/test/spineboy-jump.anim b/spine-libgdx/test/spineboy-jump.anim new file mode 100644 index 000000000..966f8a03c Binary files /dev/null and b/spine-libgdx/test/spineboy-jump.anim differ diff --git a/spine-libgdx/test/spineboy-jump.json b/spine-libgdx/test/spineboy-jump.json new file mode 100644 index 000000000..4ec271f64 --- /dev/null +++ b/spine-libgdx/test/spineboy-jump.json @@ -0,0 +1,410 @@ +{ +"bones": { + "hip": { + "rotate": [ + { "time": 0, "angle": 0, "curve": "stepped" }, + { "time": 0.9333, "angle": 0, "curve": "stepped" }, + { "time": 1.3666, "angle": 0 } + ], + "translate": [ + { "time": 0, "x": -11.57, "y": -3 }, + { "time": 0.2333, "x": -16.2, "y": -19.43 }, + { + "time": 0.3333, + "x": 7.66, + "y": -8.48, + "curve": [ 0.057, 0.06, 0.712, 1 ] + }, + { "time": 0.3666, "x": 15.38, "y": 5.01 }, + { "time": 0.4666, "x": -7.84, "y": 57.22 }, + { + "time": 0.6, + "x": -10.81, + "y": 96.34, + "curve": [ 0.241, 0, 1, 1 ] + }, + { "time": 0.7333, "x": -7.01, "y": 54.7 }, + { "time": 0.8, "x": -10.58, "y": 32.2 }, + { "time": 0.9333, "x": -31.99, "y": 0.45 }, + { "time": 1.0666, "x": -12.48, "y": -29.47 }, + { "time": 1.3666, "x": -11.57, "y": -3 } + ], + "scale": [ + { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 1.3666, "x": 1, "y": 1 } + ] + }, + "left upper leg": { + "rotate": [ + { "time": 0, "angle": 17.13 }, + { "time": 0.2333, "angle": 44.35 }, + { "time": 0.3333, "angle": 16.46 }, + { "time": 0.4, "angle": -9.88 }, + { "time": 0.4666, "angle": -11.42 }, + { "time": 0.5666, "angle": 23.46 }, + { "time": 0.7666, "angle": 71.82 }, + { "time": 0.9333, "angle": 65.53 }, + { "time": 1.0666, "angle": 51.01 }, + { "time": 1.3666, "angle": 17.13 } + ], + "translate": [ + { "time": 0, "x": -3, "y": -2.25, "curve": "stepped" }, + { "time": 0.9333, "x": -3, "y": -2.25, "curve": "stepped" }, + { "time": 1.3666, "x": -3, "y": -2.25 } + ], + "scale": [ + { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 1.3666, "x": 1, "y": 1 } + ] + }, + "left lower leg": { + "rotate": [ + { "time": 0, "angle": -16.25 }, + { "time": 0.2333, "angle": -52.21 }, + { "time": 0.4, "angle": 15.04 }, + { "time": 0.4666, "angle": -8.95 }, + { "time": 0.5666, "angle": -39.53 }, + { "time": 0.7666, "angle": -27.27 }, + { "time": 0.9333, "angle": -3.52 }, + { "time": 1.0666, "angle": -61.92 }, + { "time": 1.3666, "angle": -16.25 } + ], + "translate": [ + { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 0.9333, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 1.3666, "x": 0, "y": 0 } + ], + "scale": [ + { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 1.3666, "x": 1, "y": 1 } + ] + }, + "left foot": { + "rotate": [ + { "time": 0, "angle": 0.33 }, + { "time": 0.2333, "angle": 6.2 }, + { "time": 0.3333, "angle": 14.73 }, + { "time": 0.4, "angle": -15.54 }, + { "time": 0.4333, "angle": -21.2 }, + { "time": 0.5666, "angle": -7.55 }, + { "time": 0.7666, "angle": -0.67 }, + { "time": 0.9333, "angle": -0.58 }, + { "time": 1.0666, "angle": 14.64 }, + { "time": 1.3666, "angle": 0.33 } + ], + "translate": [ + { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 0.9333, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 1.3666, "x": 0, "y": 0 } + ], + "scale": [ + { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 1.3666, "x": 1, "y": 1 } + ] + }, + "right upper leg": { + "rotate": [ + { "time": 0, "angle": 25.97 }, + { "time": 0.2333, "angle": 46.43 }, + { "time": 0.3333, "angle": 22.61 }, + { "time": 0.4, "angle": 2.13 }, + { + "time": 0.4666, + "angle": 0.04, + "curve": [ 0, 0, 0.637, 0.98 ] + }, + { "time": 0.6, "angle": 65.55 }, + { "time": 0.7666, "angle": 64.93 }, + { "time": 0.9333, "angle": 41.08 }, + { "time": 1.0666, "angle": 66.25 }, + { "time": 1.3666, "angle": 25.97 } + ], + "translate": [ + { "time": 0, "x": 5.74, "y": 0.61 }, + { "time": 0.2333, "x": 4.79, "y": 1.79 }, + { "time": 0.3333, "x": 6.05, "y": -4.55 }, + { "time": 0.9333, "x": 4.79, "y": 1.79, "curve": "stepped" }, + { "time": 1.0666, "x": 4.79, "y": 1.79 }, + { "time": 1.3666, "x": 5.74, "y": 0.61 } + ], + "scale": [ + { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 1.3666, "x": 1, "y": 1 } + ] + }, + "right lower leg": { + "rotate": [ + { "time": 0, "angle": -27.46 }, + { "time": 0.2333, "angle": -64.03 }, + { "time": 0.4, "angle": -48.36 }, + { "time": 0.5666, "angle": -76.86 }, + { "time": 0.7666, "angle": -26.89 }, + { "time": 0.9, "angle": -18.97 }, + { "time": 0.9333, "angle": -14.18 }, + { "time": 1.0666, "angle": -80.45 }, + { "time": 1.3666, "angle": -27.46 } + ], + "translate": [ + { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 0.9333, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 1.3666, "x": 0, "y": 0 } + ], + "scale": [ + { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 1.3666, "x": 1, "y": 1 } + ] + }, + "right foot": { + "rotate": [ + { "time": 0, "angle": 1.08 }, + { "time": 0.2333, "angle": 16.02 }, + { "time": 0.3, "angle": 12.94 }, + { "time": 0.3333, "angle": 15.16 }, + { "time": 0.4, "angle": -14.7 }, + { "time": 0.4333, "angle": -12.85 }, + { "time": 0.4666, "angle": -19.18 }, + { "time": 0.5666, "angle": -15.82 }, + { "time": 0.6, "angle": -3.59 }, + { "time": 0.7666, "angle": -3.56 }, + { "time": 0.9333, "angle": 1.86 }, + { "time": 1.0666, "angle": 16.02 }, + { "time": 1.3666, "angle": 1.08 } + ], + "translate": [ + { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 0.9333, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 1.3666, "x": 0, "y": 0 } + ], + "scale": [ + { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 1.3666, "x": 1, "y": 1 } + ] + }, + "torso": { + "rotate": [ + { "time": 0, "angle": -13.35 }, + { "time": 0.2333, "angle": -48.95 }, + { "time": 0.4333, "angle": -35.77 }, + { "time": 0.6, "angle": -4.59 }, + { "time": 0.7666, "angle": 14.61 }, + { "time": 0.9333, "angle": 15.74 }, + { "time": 1.0666, "angle": -32.44 }, + { "time": 1.3666, "angle": -13.35 } + ], + "translate": [ + { "time": 0, "x": -3.67, "y": 1.68, "curve": "stepped" }, + { "time": 0.9333, "x": -3.67, "y": 1.68, "curve": "stepped" }, + { "time": 1.3666, "x": -3.67, "y": 1.68 } + ], + "scale": [ + { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 1.3666, "x": 1, "y": 1 } + ] + }, + "neck": { + "rotate": [ + { "time": 0, "angle": 12.78 }, + { "time": 0.2333, "angle": 16.46 }, + { "time": 0.4, "angle": 26.49 }, + { "time": 0.6, "angle": 15.51 }, + { "time": 0.7666, "angle": 1.34 }, + { "time": 0.9333, "angle": 2.35 }, + { "time": 1.0666, "angle": 6.08 }, + { "time": 1.3, "angle": 21.23 }, + { "time": 1.3666, "angle": 12.78 } + ], + "translate": [ + { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 0.9333, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 1.3666, "x": 0, "y": 0 } + ], + "scale": [ + { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 1.3666, "x": 1, "y": 1 } + ] + }, + "head": { + "rotate": [ + { "time": 0, "angle": 5.19 }, + { "time": 0.2333, "angle": 20.27 }, + { "time": 0.4, "angle": 15.27 }, + { "time": 0.6, "angle": -24.69 }, + { "time": 0.7666, "angle": -11.02 }, + { "time": 0.9333, "angle": -24.38 }, + { "time": 1.0666, "angle": 11.99 }, + { "time": 1.3, "angle": 4.86 }, + { "time": 1.3666, "angle": 5.19 } + ], + "translate": [ + { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 0.9333, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 1.3666, "x": 0, "y": 0 } + ], + "scale": [ + { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 1.3666, "x": 1, "y": 1 } + ] + }, + "left shoulder": { + "rotate": [ + { + "time": 0, + "angle": 0.05, + "curve": [ 0, 0, 0.62, 1 ] + }, + { + "time": 0.2333, + "angle": 279.66, + "curve": [ 0.218, 0.67, 0.66, 0.99 ] + }, + { + "time": 0.5, + "angle": 62.27, + "curve": [ 0.462, 0, 0.764, 0.58 ] + }, + { "time": 0.9333, "angle": 28.91 }, + { "time": 1.0666, "angle": -8.62 }, + { "time": 1.1666, "angle": -18.43 }, + { "time": 1.3666, "angle": 0.05 } + ], + "translate": [ + { "time": 0, "x": -1.76, "y": 0.56, "curve": "stepped" }, + { "time": 0.9333, "x": -1.76, "y": 0.56, "curve": "stepped" }, + { "time": 1.3666, "x": -1.76, "y": 0.56 } + ], + "scale": [ + { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 1.3666, "x": 1, "y": 1 } + ] + }, + "left hand": { + "rotate": [ + { "time": 0, "angle": 11.58, "curve": "stepped" }, + { "time": 0.9333, "angle": 11.58, "curve": "stepped" }, + { "time": 1.3666, "angle": 11.58 } + ], + "translate": [ + { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 0.9333, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 1.3666, "x": 0, "y": 0 } + ], + "scale": [ + { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 1.3666, "x": 1, "y": 1 } + ] + }, + "left arm": { + "rotate": [ + { "time": 0, "angle": 0.51 }, + { "time": 0.4333, "angle": 12.82 }, + { "time": 0.6, "angle": 47.55 }, + { "time": 0.9333, "angle": 12.82 }, + { "time": 1.1666, "angle": -6.5 }, + { "time": 1.3666, "angle": 0.51 } + ], + "translate": [ + { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 0.9333, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 1.3666, "x": 0, "y": 0 } + ], + "scale": [ + { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 1.3666, "x": 1, "y": 1 } + ] + }, + "right shoulder": { + "rotate": [ + { + "time": 0, + "angle": 43.82, + "curve": [ 0, 0, 0.62, 1 ] + }, + { + "time": 0.2333, + "angle": -8.74, + "curve": [ 0.304, 0.58, 0.709, 0.97 ] + }, + { + "time": 0.5333, + "angle": -208.02, + "curve": [ 0.462, 0, 0.764, 0.58 ] + }, + { "time": 0.9333, "angle": -246.72 }, + { "time": 1.0666, "angle": -307.13 }, + { "time": 1.1666, "angle": 37.15 }, + { "time": 1.3666, "angle": 43.82 } + ], + "translate": [ + { "time": 0, "x": -7.84, "y": 7.19, "curve": "stepped" }, + { "time": 0.9333, "x": -7.84, "y": 7.19, "curve": "stepped" }, + { "time": 1.3666, "x": -7.84, "y": 7.19 } + ], + "scale": [ + { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 1.3666, "x": 1, "y": 1 } + ] + }, + "right arm": { + "rotate": [ + { "time": 0, "angle": -4.02 }, + { "time": 0.6, "angle": 17.5 }, + { "time": 0.9333, "angle": -4.02 }, + { "time": 1.1666, "angle": -16.72 }, + { "time": 1.3666, "angle": -4.02 } + ], + "translate": [ + { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 0.9333, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 1.3666, "x": 0, "y": 0 } + ], + "scale": [ + { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 1.3666, "x": 1, "y": 1 } + ] + }, + "right hand": { + "rotate": [ + { "time": 0, "angle": 22.92, "curve": "stepped" }, + { "time": 0.9333, "angle": 22.92, "curve": "stepped" }, + { "time": 1.3666, "angle": 22.92 } + ], + "translate": [ + { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 0.9333, "x": 0, "y": 0, "curve": "stepped" }, + { "time": 1.3666, "x": 0, "y": 0 } + ], + "scale": [ + { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 0.9333, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 1.3666, "x": 1, "y": 1 } + ] + }, + "root": { + "rotate": [ + { "time": 0, "angle": 0 }, + { "time": 0.4333, "angle": -14.52 }, + { "time": 0.8, "angle": 9.86 }, + { "time": 1.3666, "angle": 0 } + ], + "scale": [ + { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, + { "time": 1.3666, "x": 1, "y": 1 } + ] + } +} +} \ No newline at end of file diff --git a/spine-libgdx/test/spineboy-skeleton.json b/spine-libgdx/test/spineboy-skeleton.json new file mode 100644 index 000000000..a51a9feeb --- /dev/null +++ b/spine-libgdx/test/spineboy-skeleton.json @@ -0,0 +1,101 @@ +{ +"bones": [ + { "name": "root", "length": 0 }, + { "name": "hip", "parent": "root", "length": 0, "x": 0.64, "y": 114.41 }, + { "name": "left upper leg", "parent": "hip", "length": 50.39, "x": 14.45, "y": 2.81, "rotation": -89.09 }, + { "name": "left lower leg", "parent": "left upper leg", "length": 56.45, "x": 51.78, "y": 3.46, "rotation": -16.65 }, + { "name": "left foot", "parent": "left lower leg", "length": 46.5, "x": 64.02, "y": -8.67, "rotation": 102.43 }, + { "name": "right upper leg", "parent": "hip", "length": 45.76, "x": -18.27, "rotation": -101.13 }, + { "name": "right lower leg", "parent": "right upper leg", "length": 58.52, "x": 50.21, "y": 0.6, "rotation": -10.7 }, + { "name": "right foot", "parent": "right lower leg", "length": 45.45, "x": 64.88, "y": 0.04, "rotation": 110.3 }, + { "name": "torso", "parent": "hip", "length": 85.82, "x": -6.42, "y": 1.97, "rotation": 94.95 }, + { "name": "neck", "parent": "torso", "length": 18.38, "x": 83.64, "y": -1.78, "rotation": 0.9 }, + { "name": "head", "parent": "neck", "length": 68.28, "x": 19.09, "y": 6.97, "rotation": -8.94 }, + { "name": "right shoulder", "parent": "torso", "length": 49.95, "x": 81.9, "y": 6.79, "rotation": 130.6 }, + { "name": "right arm", "parent": "right shoulder", "length": 36.74, "x": 49.95, "y": -0.12, "rotation": 40.12 }, + { "name": "right hand", "parent": "right arm", "length": 15.32, "x": 36.9, "y": 0.34, "rotation": 2.35 }, + { "name": "left shoulder", "parent": "torso", "length": 44.19, "x": 78.96, "y": -15.75, "rotation": -156.96 }, + { "name": "left arm", "parent": "left shoulder", "length": 35.62, "x": 44.19, "y": -0.01, "rotation": 28.16 }, + { "name": "left hand", "parent": "left arm", "length": 11.52, "x": 35.62, "y": 0.07, "rotation": 2.7 }, + { "name": "pelvis", "parent": "hip", "length": 0, "x": 1.41, "y": -6.57 } +], +"slots": [ + { "name": "template", "bone": "root", "color": "ff898c86" }, + { "name": "left shoulder", "bone": "left shoulder", "attachment": "left-shoulder" }, + { "name": "left arm", "bone": "left arm", "attachment": "left-arm" }, + { "name": "left hand", "bone": "left hand", "attachment": "left-hand" }, + { "name": "left foot", "bone": "left foot", "attachment": "left-foot" }, + { "name": "left lower leg", "bone": "left lower leg", "attachment": "left-lower-leg" }, + { "name": "left upper leg", "bone": "left upper leg", "attachment": "left-upper-leg" }, + { "name": "pelvis", "bone": "pelvis", "attachment": "pelvis" }, + { "name": "right foot", "bone": "right foot", "attachment": "right-foot" }, + { "name": "right lower leg", "bone": "right lower leg", "attachment": "right-lower-leg" }, + { "name": "right upper leg", "bone": "right upper leg", "attachment": "right-upper-leg" }, + { "name": "torso", "bone": "torso", "attachment": "torso" }, + { "name": "neck", "bone": "neck", "attachment": "neck" }, + { "name": "head", "bone": "head", "attachment": "head" }, + { "name": "eyes", "bone": "head", "attachment": "eyes" }, + { "name": "right shoulder", "bone": "right shoulder", "attachment": "right-shoulder" }, + { "name": "right arm", "bone": "right arm", "attachment": "right-arm" }, + { "name": "right hand", "bone": "right hand", "attachment": "right-hand" } +], +"skins": { + "default": { + "template": { + "spineboy": { "y": 167.82, "width": 145, "height": 341 } + }, + "left shoulder": { + "left-shoulder": { "x": 23.74, "y": 0.11, "rotation": 62.01, "width": 34, "height": 53 } + }, + "left arm": { + "left-arm": { "x": 15.11, "y": -0.44, "rotation": 33.84, "width": 35, "height": 29 } + }, + "left hand": { + "left-hand": { "x": 0.75, "y": 1.86, "rotation": 31.14, "width": 35, "height": 38 } + }, + "left foot": { + "left-foot": { "x": 24.35, "y": 8.88, "rotation": 3.32, "width": 65, "height": 30 } + }, + "left lower leg": { + "left-lower-leg": { "x": 24.55, "y": -1.92, "rotation": 105.75, "width": 49, "height": 64 } + }, + "left upper leg": { + "left-upper-leg": { "x": 26.12, "y": -1.85, "rotation": 89.09, "width": 33, "height": 67 } + }, + "pelvis": { + "pelvis": { "x": -4.83, "y": 10.62, "width": 63, "height": 47 } + }, + "right foot": { + "right-foot": { "x": 19.02, "y": 8.47, "rotation": 1.52, "width": 67, "height": 30 } + }, + "right lower leg": { + "right-lower-leg": { "x": 23.28, "y": -2.59, "rotation": 111.83, "width": 51, "height": 64 } + }, + "right upper leg": { + "right-upper-leg": { "x": 23.03, "y": 0.25, "rotation": 101.13, "width": 44, "height": 70 } + }, + "torso": { + "torso": { "x": 44.57, "y": -7.08, "rotation": -94.95, "width": 68, "height": 92 } + }, + "neck": { + "neck": { "x": 9.42, "y": -3.66, "rotation": -100.15, "width": 34, "height": 28 } + }, + "head": { + "head": { "x": 53.94, "y": -5.75, "rotation": -86.9, "width": 121, "height": 132 } + }, + "eyes": { + "eyes": { "x": 28.94, "y": -32.92, "rotation": -86.9, "width": 34, "height": 27 }, + "eyes-closed": { "x": 28.77, "y": -32.86, "rotation": -86.9, "width": 34, "height": 27 } + }, + "right shoulder": { + "right-shoulder": { "x": 25.86, "y": 0.03, "rotation": 134.44, "width": 52, "height": 51 } + }, + "right arm": { + "right-arm": { "x": 18.34, "y": -2.64, "rotation": 94.32, "width": 21, "height": 45 } + }, + "right hand": { + "right-hand": { "x": 6.82, "y": 1.25, "rotation": 91.96, "width": 32, "height": 32 } + } + } +} +} \ No newline at end of file diff --git a/spine-libgdx/test/spineboy-walk.anim b/spine-libgdx/test/spineboy-walk.anim new file mode 100644 index 000000000..077f20f5a Binary files /dev/null and b/spine-libgdx/test/spineboy-walk.anim differ diff --git a/spine-libgdx/test/spineboy-walk.json b/spine-libgdx/test/spineboy-walk.json new file mode 100644 index 000000000..b40e53a95 --- /dev/null +++ b/spine-libgdx/test/spineboy-walk.json @@ -0,0 +1,278 @@ +{ +"bones": { + "left upper leg": { + "rotate": [ + { "time": 0, "angle": -26.55 }, + { "time": 0.1333, "angle": -8.78 }, + { "time": 0.2666, "angle": 9.51 }, + { "time": 0.4, "angle": 30.74 }, + { "time": 0.5333, "angle": 25.33 }, + { "time": 0.6666, "angle": 26.11 }, + { "time": 0.8, "angle": -7.7 }, + { "time": 0.9333, "angle": -21.19 }, + { "time": 1.0666, "angle": -26.55 } + ], + "translate": [ + { "time": 0, "x": -3, "y": -2.25 }, + { "time": 0.4, "x": -2.18, "y": -2.25 }, + { "time": 1.0666, "x": -3, "y": -2.25 } + ] + }, + "right upper leg": { + "rotate": [ + { "time": 0, "angle": 42.45 }, + { "time": 0.1333, "angle": 52.1 }, + { "time": 0.2666, "angle": 5.96 }, + { "time": 0.5333, "angle": -16.93 }, + { "time": 0.6666, "angle": 1.89 }, + { + "time": 0.8, + "angle": 28.06, + "curve": [ 0.462, 0.11, 1, 1 ] + }, + { + "time": 0.9333, + "angle": 58.68, + "curve": [ 0.5, 0.02, 1, 1 ] + }, + { "time": 1.0666, "angle": 42.45 } + ], + "translate": [ + { "time": 0, "x": 8.11, "y": -2.36 }, + { "time": 0.1333, "x": 10.03, "y": -2.56 }, + { "time": 0.4, "x": 2.76, "y": -2.97 }, + { "time": 0.5333, "x": 2.76, "y": -2.81 }, + { "time": 0.9333, "x": 8.67, "y": -2.54 }, + { "time": 1.0666, "x": 8.11, "y": -2.36 } + ] + }, + "left lower leg": { + "rotate": [ + { "time": 0, "angle": -10.21 }, + { "time": 0.1333, "angle": -55.64 }, + { "time": 0.2666, "angle": -68.12 }, + { "time": 0.5333, "angle": 5.11 }, + { "time": 0.6666, "angle": -28.29 }, + { "time": 0.8, "angle": 4.08 }, + { "time": 0.9333, "angle": 3.53 }, + { "time": 1.0666, "angle": -10.21 } + ] + }, + "left foot": { + "rotate": [ + { "time": 0, "angle": -3.69 }, + { "time": 0.1333, "angle": -10.42 }, + { "time": 0.2666, "angle": -17.14 }, + { "time": 0.4, "angle": -2.83 }, + { "time": 0.5333, "angle": -3.87 }, + { "time": 0.6666, "angle": 2.78 }, + { "time": 0.8, "angle": 1.68 }, + { "time": 0.9333, "angle": -8.54 }, + { "time": 1.0666, "angle": -3.69 } + ] + }, + "right shoulder": { + "rotate": [ + { + "time": 0, + "angle": 20.89, + "curve": [ 0.264, 0, 0.75, 1 ] + }, + { + "time": 0.1333, + "angle": 3.72, + "curve": [ 0.272, 0, 0.841, 1 ] + }, + { "time": 0.6666, "angle": -278.28 }, + { "time": 1.0666, "angle": 20.89 } + ], + "translate": [ + { "time": 0, "x": -7.84, "y": 7.19 }, + { "time": 0.1333, "x": -6.36, "y": 6.42 }, + { "time": 0.6666, "x": -11.07, "y": 5.25 }, + { "time": 1.0666, "x": -7.84, "y": 7.19 } + ] + }, + "right arm": { + "rotate": [ + { + "time": 0, + "angle": -4.02, + "curve": [ 0.267, 0, 0.804, 0.99 ] + }, + { + "time": 0.1333, + "angle": -13.99, + "curve": [ 0.341, 0, 1, 1 ] + }, + { + "time": 0.6666, + "angle": 36.54, + "curve": [ 0.307, 0, 0.787, 0.99 ] + }, + { "time": 1.0666, "angle": -4.02 } + ] + }, + "right hand": { + "rotate": [ + { "time": 0, "angle": 22.92 }, + { "time": 0.4, "angle": -8.97 }, + { "time": 0.6666, "angle": 0.51 }, + { "time": 1.0666, "angle": 22.92 } + ] + }, + "left shoulder": { + "rotate": [ + { "time": 0, "angle": -1.47 }, + { "time": 0.1333, "angle": 13.6 }, + { "time": 0.6666, "angle": 280.74 }, + { "time": 1.0666, "angle": -1.47 } + ], + "translate": [ + { "time": 0, "x": -1.76, "y": 0.56 }, + { "time": 0.6666, "x": -2.47, "y": 8.14 }, + { "time": 1.0666, "x": -1.76, "y": 0.56 } + ] + }, + "left hand": { + "rotate": [ + { + "time": 0, + "angle": 11.58, + "curve": [ 0.169, 0.37, 0.632, 1.55 ] + }, + { + "time": 0.1333, + "angle": 28.13, + "curve": [ 0.692, 0, 0.692, 0.99 ] + }, + { + "time": 0.6666, + "angle": -27.42, + "curve": [ 0.117, 0.41, 0.738, 1.76 ] + }, + { "time": 0.8, "angle": -36.32 }, + { "time": 1.0666, "angle": 11.58 } + ] + }, + "left arm": { + "rotate": [ + { "time": 0, "angle": -8.27 }, + { "time": 0.1333, "angle": 18.43 }, + { "time": 0.6666, "angle": 0.88 }, + { "time": 1.0666, "angle": -8.27 } + ] + }, + "torso": { + "rotate": [ + { "time": 0, "angle": -10.28 }, + { + "time": 0.1333, + "angle": -15.38, + "curve": [ 0.545, 0, 1, 1 ] + }, + { + "time": 0.4, + "angle": -9.78, + "curve": [ 0.58, 0.17, 1, 1 ] + }, + { "time": 0.6666, "angle": -15.75 }, + { "time": 0.9333, "angle": -7.06 }, + { "time": 1.0666, "angle": -10.28 } + ], + "translate": [ + { "time": 0, "x": -3.67, "y": 1.68 }, + { "time": 0.1333, "x": -3.67, "y": 0.68 }, + { "time": 0.4, "x": -3.67, "y": 1.97 }, + { "time": 0.6666, "x": -3.67, "y": -0.14 }, + { "time": 1.0666, "x": -3.67, "y": 1.68 } + ] + }, + "right foot": { + "rotate": [ + { "time": 0, "angle": -5.25 }, + { "time": 0.2666, "angle": -4.08 }, + { "time": 0.4, "angle": -6.45 }, + { "time": 0.5333, "angle": -5.39 }, + { "time": 0.8, "angle": -11.68 }, + { "time": 0.9333, "angle": 0.46 }, + { "time": 1.0666, "angle": -5.25 } + ] + }, + "right lower leg": { + "rotate": [ + { "time": 0, "angle": -3.39 }, + { "time": 0.1333, "angle": -45.53 }, + { "time": 0.2666, "angle": -2.59 }, + { "time": 0.5333, "angle": -19.53 }, + { "time": 0.6666, "angle": -64.8 }, + { + "time": 0.8, + "angle": -82.56, + "curve": [ 0.557, 0.18, 1, 1 ] + }, + { "time": 1.0666, "angle": -3.39 } + ] + }, + "hip": { + "rotate": [ + { "time": 0, "angle": 0, "curve": "stepped" }, + { "time": 1.0666, "angle": 0 } + ], + "translate": [ + { "time": 0, "x": 0, "y": 0 }, + { + "time": 0.1333, + "x": 0, + "y": -7.61, + "curve": [ 0.272, 0.86, 1, 1 ] + }, + { "time": 0.4, "x": 0, "y": 8.7 }, + { "time": 0.5333, "x": 0, "y": -0.41 }, + { + "time": 0.6666, + "x": 0, + "y": -7.05, + "curve": [ 0.235, 0.89, 1, 1 ] + }, + { "time": 0.8, "x": 0, "y": 2.92 }, + { "time": 0.9333, "x": 0, "y": 6.78 }, + { "time": 1.0666, "x": 0, "y": 0 } + ] + }, + "neck": { + "rotate": [ + { "time": 0, "angle": 3.6 }, + { "time": 0.1333, "angle": 17.49 }, + { "time": 0.2666, "angle": 6.1 }, + { "time": 0.4, "angle": 3.45 }, + { "time": 0.5333, "angle": 5.17 }, + { "time": 0.6666, "angle": 18.36 }, + { "time": 0.8, "angle": 6.09 }, + { "time": 0.9333, "angle": 2.28 }, + { "time": 1.0666, "angle": 3.6 } + ] + }, + "head": { + "rotate": [ + { + "time": 0, + "angle": 3.6, + "curve": [ 0, 0, 0.704, 1.61 ] + }, + { "time": 0.1666, "angle": -0.2 }, + { "time": 0.2666, "angle": 6.1 }, + { "time": 0.4, "angle": 3.45 }, + { + "time": 0.5333, + "angle": 5.17, + "curve": [ 0, 0, 0.704, 1.61 ] + }, + { "time": 0.7, "angle": 1.1 }, + { "time": 0.8, "angle": 6.09 }, + { "time": 0.9333, "angle": 2.28 }, + { "time": 1.0666, "angle": 3.6 } + ] + } +} +} \ No newline at end of file diff --git a/spine-libgdx/test/spineboy.atlas b/spine-libgdx/test/spineboy.atlas new file mode 100644 index 000000000..88fb3e0b5 --- /dev/null +++ b/spine-libgdx/test/spineboy.atlas @@ -0,0 +1,166 @@ + +spineboy.png +format: RGBA8888 +filter: Nearest,Nearest +repeat: none +head + rotate: false + xy: 1, 122 + size: 121, 132 + orig: 121, 132 + offset: 0, 0 + index: -1 +torso + rotate: false + xy: 1, 28 + size: 68, 92 + orig: 68, 92 + offset: 0, 0 + index: -1 +left-pant-bottom + rotate: false + xy: 1, 4 + size: 44, 22 + orig: 44, 22 + offset: 0, 0 + index: -1 +right-pant-bottom + rotate: false + xy: 47, 8 + size: 46, 18 + orig: 46, 18 + offset: 0, 0 + index: -1 +right-upper-leg + rotate: false + xy: 71, 50 + size: 44, 70 + orig: 44, 70 + offset: 0, 0 + index: -1 +pelvis + rotate: false + xy: 95, 1 + size: 63, 47 + orig: 63, 47 + offset: 0, 0 + index: -1 +left-upper-leg + rotate: false + xy: 117, 53 + size: 33, 67 + orig: 33, 67 + offset: 0, 0 + index: -1 +right-foot + rotate: false + xy: 160, 224 + size: 67, 30 + orig: 67, 30 + offset: 0, 0 + index: -1 +left-shoulder + rotate: false + xy: 124, 201 + size: 34, 53 + orig: 34, 53 + offset: 0, 0 + index: -1 +left-ankle + rotate: false + xy: 229, 222 + size: 25, 32 + orig: 25, 32 + offset: 0, 0 + index: -1 +left-foot + rotate: false + xy: 160, 192 + size: 65, 30 + orig: 65, 30 + offset: 0, 0 + index: -1 +neck + rotate: false + xy: 124, 171 + size: 34, 28 + orig: 34, 28 + offset: 0, 0 + index: -1 +right-arm + rotate: false + xy: 124, 124 + size: 21, 45 + orig: 21, 45 + offset: 0, 0 + index: -1 +right-ankle + rotate: false + xy: 227, 190 + size: 25, 30 + orig: 25, 30 + offset: 0, 0 + index: -1 +left-hand + rotate: false + xy: 147, 131 + size: 35, 38 + orig: 35, 38 + offset: 0, 0 + index: -1 +left-arm + rotate: false + xy: 184, 161 + size: 35, 29 + orig: 35, 29 + offset: 0, 0 + index: -1 +eyes-closed + rotate: false + xy: 221, 161 + size: 34, 27 + orig: 34, 27 + offset: 0, 0 + index: -1 +right-lower-leg + rotate: false + xy: 152, 65 + size: 51, 64 + orig: 51, 64 + offset: 0, 0 + index: -1 +right-foot-idle + rotate: false + xy: 184, 131 + size: 53, 28 + orig: 53, 28 + offset: 0, 0 + index: -1 +left-lower-leg + rotate: false + xy: 205, 65 + size: 49, 64 + orig: 49, 64 + offset: 0, 0 + index: -1 +right-shoulder + rotate: false + xy: 160, 12 + size: 52, 51 + orig: 52, 51 + offset: 0, 0 + index: -1 +eyes + rotate: false + xy: 214, 36 + size: 34, 27 + orig: 34, 27 + offset: 0, 0 + index: -1 +right-hand + rotate: false + xy: 214, 2 + size: 32, 32 + orig: 32, 32 + offset: 0, 0 + index: -1 diff --git a/spine-libgdx/test/spineboy.png b/spine-libgdx/test/spineboy.png new file mode 100644 index 000000000..b8b493dfd Binary files /dev/null and b/spine-libgdx/test/spineboy.png differ diff --git a/spine-libgdx/test/spineboy.skel b/spine-libgdx/test/spineboy.skel new file mode 100644 index 000000000..71f85c395 Binary files /dev/null and b/spine-libgdx/test/spineboy.skel differ