This commit is contained in:
NathanSweet 2014-01-15 04:16:36 +01:00
parent 525ba52a6c
commit 9f19d9af39
10 changed files with 394 additions and 278 deletions

View File

@ -28,9 +28,12 @@
package com.esotericsoftware.spine;
import com.esotericsoftware.spine.attachments.MeshAttachment;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.FloatArray;
public class Animation {
final String name;
@ -201,9 +204,9 @@ public class Animation {
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);
float prevX = x - dfx;
float prevY = y - dfy;
return prevY + (y - prevY) * (percent - prevX) / (x - prevX);
}
if (i == 0) break;
i--;
@ -219,7 +222,7 @@ public class Animation {
}
static public class RotateTimeline extends CurveTimeline {
static private final int LAST_FRAME_TIME = -2;
static private final int PREV_FRAME_TIME = -2;
static private final int FRAME_VALUE = 1;
int boneIndex;
@ -265,19 +268,19 @@ public class Animation {
return;
}
// Interpolate between the last frame and the current frame.
// Interpolate between the previous frame and the current frame.
int frameIndex = binarySearch(frames, time, 2);
float lastFrameValue = frames[frameIndex - 1];
float prevFrameValue = frames[frameIndex - 1];
float frameTime = frames[frameIndex];
float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime), 0, 1);
float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime), 0, 1);
percent = getCurvePercent(frameIndex / 2 - 1, percent);
float amount = frames[frameIndex + FRAME_VALUE] - lastFrameValue;
float amount = frames[frameIndex + FRAME_VALUE] - prevFrameValue;
while (amount > 180)
amount -= 360;
while (amount < -180)
amount += 360;
amount = bone.data.rotation + (lastFrameValue + amount * percent) - bone.rotation;
amount = bone.data.rotation + (prevFrameValue + amount * percent) - bone.rotation;
while (amount > 180)
amount -= 360;
while (amount < -180)
@ -287,7 +290,7 @@ public class Animation {
}
static public class TranslateTimeline extends CurveTimeline {
static final int LAST_FRAME_TIME = -3;
static final int PREV_FRAME_TIME = -3;
static final int FRAME_X = 1;
static final int FRAME_Y = 2;
@ -331,16 +334,16 @@ public class Animation {
return;
}
// Interpolate between the last frame and the current frame.
// Interpolate between the previous frame and the current frame.
int frameIndex = binarySearch(frames, time, 3);
float lastFrameX = frames[frameIndex - 2];
float lastFrameY = frames[frameIndex - 1];
float prevFrameX = frames[frameIndex - 2];
float prevFrameY = frames[frameIndex - 1];
float frameTime = frames[frameIndex];
float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime), 0, 1);
float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frameIndex + PREV_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;
bone.x += (bone.data.x + prevFrameX + (frames[frameIndex + FRAME_X] - prevFrameX) * percent - bone.x) * alpha;
bone.y += (bone.data.y + prevFrameY + (frames[frameIndex + FRAME_Y] - prevFrameY) * percent - bone.y) * alpha;
}
}
@ -360,23 +363,23 @@ public class Animation {
return;
}
// Interpolate between the last frame and the current frame.
// Interpolate between the previous frame and the current frame.
int frameIndex = binarySearch(frames, time, 3);
float lastFrameX = frames[frameIndex - 2];
float lastFrameY = frames[frameIndex - 1];
float prevFrameX = frames[frameIndex - 2];
float prevFrameY = frames[frameIndex - 1];
float frameTime = frames[frameIndex];
float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime), 0, 1);
float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frameIndex + PREV_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)
bone.scaleX += (bone.data.scaleX - 1 + prevFrameX + (frames[frameIndex + FRAME_X] - prevFrameX) * percent - bone.scaleX)
* alpha;
bone.scaleY += (bone.data.scaleY - 1 + lastFrameY + (frames[frameIndex + FRAME_Y] - lastFrameY) * percent - bone.scaleY)
bone.scaleY += (bone.data.scaleY - 1 + prevFrameY + (frames[frameIndex + FRAME_Y] - prevFrameY) * percent - bone.scaleY)
* alpha;
}
}
static public class ColorTimeline extends CurveTimeline {
static private final int LAST_FRAME_TIME = -5;
static private final int PREV_FRAME_TIME = -5;
static private final int FRAME_R = 1;
static private final int FRAME_G = 2;
static private final int FRAME_B = 3;
@ -428,20 +431,20 @@ public class Animation {
return;
}
// Interpolate between the last frame and the current frame.
// Interpolate between the previous 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 prevFrameR = frames[frameIndex - 4];
float prevFrameG = frames[frameIndex - 3];
float prevFrameB = frames[frameIndex - 2];
float prevFrameA = frames[frameIndex - 1];
float frameTime = frames[frameIndex];
float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime), 0, 1);
float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frameIndex + PREV_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;
float r = prevFrameR + (frames[frameIndex + FRAME_R] - prevFrameR) * percent;
float g = prevFrameG + (frames[frameIndex + FRAME_G] - prevFrameG) * percent;
float b = prevFrameB + (frames[frameIndex + FRAME_B] - prevFrameB) * percent;
float a = prevFrameA + (frames[frameIndex + FRAME_A] - prevFrameA) * percent;
if (alpha < 1)
color.add((r - color.r) * alpha, (g - color.g) * alpha, (b - color.b) * alpha, (a - color.a) * alpha);
else
@ -606,4 +609,84 @@ public class Animation {
}
}
}
static public class FfdTimeline extends CurveTimeline {
private final float[] frames; // time, ...
private final float[][] frameVertices;
int slotIndex;
MeshAttachment meshAttachment;
public FfdTimeline (int frameCount) {
super(frameCount);
frames = new float[frameCount];
frameVertices = new float[frameCount][];
}
public void setSlotIndex (int slotIndex) {
this.slotIndex = slotIndex;
}
public int getSlotIndex () {
return slotIndex;
}
public void setMeshAttachment (MeshAttachment attachment) {
this.meshAttachment = attachment;
}
public MeshAttachment getMeshAttachment () {
return meshAttachment;
}
public float[] getFrames () {
return frames;
}
public float[][] getVertices () {
return frameVertices;
}
/** Sets the time of the specified keyframe. */
public void setFrame (int frameIndex, float time, float[] vertices) {
frames[frameIndex] = time;
frameVertices[frameIndex] = vertices;
}
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> firedEvents, float alpha) {
Slot slot = skeleton.slots.get(slotIndex);
if (slot.getAttachment() != meshAttachment) return;
FloatArray verticesArray = slot.getAttachmentVertices();
verticesArray.size = 0;
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
float[][] frameVertices = this.frameVertices;
int vertexCount = frameVertices[0].length;
verticesArray.ensureCapacity(vertexCount);
verticesArray.size = vertexCount;
float[] vertices = verticesArray.items;
if (time >= frames[frames.length - 1]) { // Time is after last frame.
System.arraycopy(frameVertices[frames.length - 1], 0, vertices, 0, vertexCount);
return;
}
// Interpolate between the previous frame and the current frame.
int frameIndex = binarySearch(frames, time, 1);
float frameTime = frames[frameIndex];
float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frameIndex - 1] - frameTime), 0, 1);
percent = getCurvePercent(frameIndex - 1, percent);
float[] prevVertices = frameVertices[frameIndex - 1];
float[] nextVertices = frameVertices[frameIndex];
// BOZO - FFD, use alpha for mixing?
for (int i = 0; i < vertexCount; i++) {
float prev = prevVertices[i];
vertices[i] = prev + (nextVertices[i] - prev) * percent;
}
}
}
}

View File

@ -28,6 +28,8 @@
package com.esotericsoftware.spine;
import com.badlogic.gdx.graphics.Color;
public class BoneData {
final BoneData parent;
final String name;
@ -37,6 +39,9 @@ public class BoneData {
float scaleX = 1, scaleY = 1;
boolean inheritScale = true, inheritRotation = true;
// Nonessential.
final Color color = new Color(1, 1, 1, 1);
/** @param parent May be null. */
public BoneData (String name, BoneData parent) {
if (name == null) throw new IllegalArgumentException("name cannot be null.");
@ -131,6 +136,10 @@ public class BoneData {
this.inheritRotation = inheritRotation;
}
public Color getColor () {
return color;
}
public String toString () {
return name;
}

View File

@ -33,6 +33,7 @@ import com.esotericsoftware.spine.Animation.ColorTimeline;
import com.esotericsoftware.spine.Animation.CurveTimeline;
import com.esotericsoftware.spine.Animation.DrawOrderTimeline;
import com.esotericsoftware.spine.Animation.EventTimeline;
import com.esotericsoftware.spine.Animation.FfdTimeline;
import com.esotericsoftware.spine.Animation.RotateTimeline;
import com.esotericsoftware.spine.Animation.ScaleTimeline;
import com.esotericsoftware.spine.Animation.Timeline;
@ -62,6 +63,7 @@ public class SkeletonBinary {
static public final int TIMELINE_COLOR = 4;
static public final int TIMELINE_EVENT = 5;
static public final int TIMELINE_DRAWORDER = 6;
static public final int TIMELINE_FFD = 7;
static public final int CURVE_LINEAR = 0;
static public final int CURVE_STEPPED = 1;
@ -92,11 +94,14 @@ public class SkeletonBinary {
public SkeletonData readSkeletonData (FileHandle file) {
if (file == null) throw new IllegalArgumentException("file cannot be null.");
float scale = this.scale;
SkeletonData skeletonData = new SkeletonData();
skeletonData.name = file.nameWithoutExtension();
DataInput input = new DataInput(file.read(512));
try {
boolean nonessential = input.readBoolean();
// Bones.
for (int i = 0, n = input.readInt(true); i < n; i++) {
String name = input.readString();
@ -110,8 +115,9 @@ public class SkeletonBinary {
boneData.scaleY = input.readFloat();
boneData.rotation = input.readFloat();
boneData.length = input.readFloat() * scale;
boneData.inheritScale = input.readByte() == 1;
boneData.inheritRotation = input.readByte() == 1;
boneData.inheritScale = input.readBoolean();
boneData.inheritRotation = input.readBoolean();
if (nonessential) Color.rgba8888ToColor(boneData.getColor(), input.readInt());
skeletonData.addBone(boneData);
}
@ -122,12 +128,12 @@ public class SkeletonBinary {
SlotData slotData = new SlotData(slotName, boneData);
Color.rgba8888ToColor(slotData.getColor(), input.readInt());
slotData.attachmentName = input.readString();
slotData.additiveBlending = input.readByte() == 1;
slotData.additiveBlending = input.readBoolean();
skeletonData.addSlot(slotData);
}
// Default skin.
Skin defaultSkin = readSkin(input, "default");
Skin defaultSkin = readSkin(input, "default", nonessential);
if (defaultSkin != null) {
skeletonData.defaultSkin = defaultSkin;
skeletonData.addSkin(defaultSkin);
@ -135,7 +141,7 @@ public class SkeletonBinary {
// Skins.
for (int i = 0, n = input.readInt(true); i < n; i++)
skeletonData.addSkin(readSkin(input, input.readString()));
skeletonData.addSkin(readSkin(input, input.readString(), nonessential));
// Events.
for (int i = 0, n = input.readInt(true); i < n; i++) {
@ -165,7 +171,7 @@ public class SkeletonBinary {
return skeletonData;
}
private Skin readSkin (DataInput input, String skinName) throws IOException {
private Skin readSkin (DataInput input, String skinName, boolean nonessential) throws IOException {
int slotCount = input.readInt(true);
if (slotCount == 0) return null;
Skin skin = new Skin(skinName);
@ -173,13 +179,15 @@ public class SkeletonBinary {
int slotIndex = input.readInt(true);
for (int ii = 0, nn = input.readInt(true); ii < nn; ii++) {
String name = input.readString();
skin.addAttachment(slotIndex, name, readAttachment(input, skin, name));
skin.addAttachment(slotIndex, name, readAttachment(input, skin, name, nonessential));
}
}
return skin;
}
private Attachment readAttachment (DataInput input, Skin skin, String attachmentName) throws IOException {
private Attachment readAttachment (DataInput input, Skin skin, String attachmentName, boolean nonessential) throws IOException {
float scale = this.scale;
String name = input.readString();
if (name == null) name = attachmentName;
@ -214,8 +222,8 @@ public class SkeletonBinary {
short[] triangles = readShortArray(input);
float[] uvs = readFloatArray(input, 1);
Color.rgba8888ToColor(mesh.getColor(), input.readInt());
mesh.setEdges(readIntArray(input));
if (mesh.getEdges().length > 0) {
if (nonessential) {
mesh.setEdges(readIntArray(input));
mesh.setHullLength(input.readInt(true));
mesh.setWidth(input.readFloat() * scale);
mesh.setHeight(input.readFloat() * scale);
@ -253,25 +261,26 @@ public class SkeletonBinary {
private void readAnimation (String name, DataInput input, SkeletonData skeletonData) {
Array<Timeline> timelines = new Array();
float scale = this.scale;
float duration = 0;
try {
// SRT.
for (int i = 0, n = input.readInt(true); i < n; i++) {
int boneIndex = input.readInt(true);
int itemCount = input.readInt(true);
for (int ii = 0; ii < itemCount; ii++) {
for (int ii = 0, nn = input.readInt(true); ii < nn; ii++) {
int timelineType = input.readByte();
int keyCount = input.readInt(true);
int frameCount = input.readInt(true);
switch (timelineType) {
case TIMELINE_ROTATE: {
RotateTimeline timeline = new RotateTimeline(keyCount);
RotateTimeline timeline = new RotateTimeline(frameCount);
timeline.boneIndex = boneIndex;
for (int frameIndex = 0; frameIndex < keyCount; frameIndex++) {
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
timeline.setFrame(frameIndex, input.readFloat(), input.readFloat());
if (frameIndex < keyCount - 1) readCurve(input, frameIndex, timeline);
if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline);
}
timelines.add(timeline);
duration = Math.max(duration, timeline.getFrames()[keyCount * 2 - 2]);
duration = Math.max(duration, timeline.getFrames()[frameCount * 2 - 2]);
break;
}
case TIMELINE_TRANSLATE:
@ -279,56 +288,88 @@ public class SkeletonBinary {
TranslateTimeline timeline;
float timelineScale = 1;
if (timelineType == TIMELINE_SCALE)
timeline = new ScaleTimeline(keyCount);
timeline = new ScaleTimeline(frameCount);
else {
timeline = new TranslateTimeline(keyCount);
timeline = new TranslateTimeline(frameCount);
timelineScale = scale;
}
timeline.boneIndex = boneIndex;
for (int frameIndex = 0; frameIndex < keyCount; frameIndex++) {
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
timeline.setFrame(frameIndex, input.readFloat(), input.readFloat() * timelineScale, input.readFloat()
* timelineScale);
if (frameIndex < keyCount - 1) readCurve(input, frameIndex, timeline);
if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline);
}
timelines.add(timeline);
duration = Math.max(duration, timeline.getFrames()[keyCount * 3 - 3]);
duration = Math.max(duration, timeline.getFrames()[frameCount * 3 - 3]);
break;
}
}
}
// FFD.
for (int i = 0, n = input.readInt(true); i < n; i++) {
Skin skin = skeletonData.getSkins().get(input.readInt(true) + 1);
for (int ii = 0, nn = input.readInt(true); ii < nn; ii++) {
int slotIndex = input.readInt(true);
for (int iii = 0, nnn = input.readInt(true); iii < nnn; iii++) {
MeshAttachment mesh = (MeshAttachment)skin.getAttachment(slotIndex, input.readString());
int frameCount = input.readInt(true);
FfdTimeline timeline = new FfdTimeline(frameCount);
timeline.slotIndex = slotIndex;
timeline.meshAttachment = mesh;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
float time = input.readFloat();
float[] vertices;
int vertexCount = input.readInt(true);
if (vertexCount == 0)
vertices = mesh.getVertices();
else {
vertices = new float[vertexCount];
for (int vertex = 0; vertex < vertexCount; vertex++)
vertices[vertex] = input.readFloat() * scale;
}
timeline.setFrame(frameIndex, time, vertices);
if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline);
}
timelines.add(timeline);
duration = Math.max(duration, timeline.getFrames()[frameCount - 1]);
}
}
}
// Color, attachment.
for (int i = 0, n = input.readInt(true); i < n; i++) {
int slotIndex = input.readInt(true);
int itemCount = input.readInt(true);
for (int ii = 0; ii < itemCount; ii++) {
for (int ii = 0, nn = input.readInt(true); ii < nn; ii++) {
int timelineType = input.readByte();
int keyCount = input.readInt(true);
int frameCount = input.readInt(true);
switch (timelineType) {
case TIMELINE_COLOR: {
ColorTimeline timeline = new ColorTimeline(keyCount);
ColorTimeline timeline = new ColorTimeline(frameCount);
timeline.slotIndex = slotIndex;
for (int frameIndex = 0; frameIndex < keyCount; frameIndex++) {
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
float time = input.readFloat();
Color.rgba8888ToColor(tempColor, input.readInt());
timeline.setFrame(frameIndex, time, tempColor.r, tempColor.g, tempColor.b, tempColor.a);
if (frameIndex < keyCount - 1) readCurve(input, frameIndex, timeline);
if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline);
}
timelines.add(timeline);
duration = Math.max(duration, timeline.getFrames()[keyCount * 5 - 5]);
duration = Math.max(duration, timeline.getFrames()[frameCount * 5 - 5]);
break;
}
case TIMELINE_ATTACHMENT:
AttachmentTimeline timeline = new AttachmentTimeline(keyCount);
AttachmentTimeline timeline = new AttachmentTimeline(frameCount);
timeline.slotIndex = slotIndex;
for (int frameIndex = 0; frameIndex < keyCount; frameIndex++)
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++)
timeline.setFrame(frameIndex, input.readFloat(), input.readString());
timelines.add(timeline);
duration = Math.max(duration, timeline.getFrames()[keyCount - 1]);
duration = Math.max(duration, timeline.getFrames()[frameCount - 1]);
break;
}
}
}
// Events.
int eventCount = input.readInt(true);
if (eventCount > 0) {
EventTimeline timeline = new EventTimeline(eventCount);
@ -345,6 +386,7 @@ public class SkeletonBinary {
duration = Math.max(duration, timeline.getFrames()[eventCount - 1]);
}
// Draw order.
int drawOrderCount = input.readInt(true);
if (drawOrderCount > 0) {
DrawOrderTimeline timeline = new DrawOrderTimeline(drawOrderCount);

View File

@ -33,6 +33,7 @@ import com.esotericsoftware.spine.Animation.ColorTimeline;
import com.esotericsoftware.spine.Animation.CurveTimeline;
import com.esotericsoftware.spine.Animation.DrawOrderTimeline;
import com.esotericsoftware.spine.Animation.EventTimeline;
import com.esotericsoftware.spine.Animation.FfdTimeline;
import com.esotericsoftware.spine.Animation.RotateTimeline;
import com.esotericsoftware.spine.Animation.ScaleTimeline;
import com.esotericsoftware.spine.Animation.Timeline;
@ -54,12 +55,6 @@ import com.badlogic.gdx.utils.JsonValue;
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";
private final AttachmentLoader attachmentLoader;
private float scale = 1;
@ -83,13 +78,15 @@ public class SkeletonJson {
public SkeletonData readSkeletonData (FileHandle file) {
if (file == null) throw new IllegalArgumentException("file cannot be null.");
float scale = this.scale;
SkeletonData skeletonData = new SkeletonData();
skeletonData.name = file.nameWithoutExtension();
JsonValue root = new JsonReader().parse(file);
// Bones.
for (JsonValue boneMap = root.getChild("bones"); boneMap != null; boneMap = boneMap.next()) {
for (JsonValue boneMap = root.getChild("bones"); boneMap != null; boneMap = boneMap.next) {
BoneData parent = null;
String parentName = boneMap.getString("parent", null);
if (parentName != null) {
@ -105,11 +102,15 @@ public class SkeletonJson {
boneData.scaleY = boneMap.getFloat("scaleY", 1);
boneData.inheritScale = boneMap.getBoolean("inheritScale", true);
boneData.inheritRotation = boneMap.getBoolean("inheritRotation", true);
String color = boneMap.getString("color", null);
if (color != null) boneData.getColor().set(Color.valueOf(color));
skeletonData.addBone(boneData);
}
// Slots.
for (JsonValue slotMap = root.getChild("slots"); slotMap != null; slotMap = slotMap.next()) {
for (JsonValue slotMap = root.getChild("slots"); slotMap != null; slotMap = slotMap.next) {
String slotName = slotMap.getString("name");
String boneName = slotMap.getString("bone");
BoneData boneData = skeletonData.findBone(boneName);
@ -127,14 +128,14 @@ public class SkeletonJson {
}
// Skins.
for (JsonValue skinMap = root.getChild("skins"); skinMap != null; skinMap = skinMap.next()) {
Skin skin = new Skin(skinMap.name());
for (JsonValue slotEntry = skinMap.child(); slotEntry != null; slotEntry = slotEntry.next()) {
int slotIndex = skeletonData.findSlotIndex(slotEntry.name());
if (slotIndex == -1) throw new SerializationException("Slot not found: " + slotEntry.name());
for (JsonValue entry = slotEntry.child(); entry != null; entry = entry.next()) {
Attachment attachment = readAttachment(skin, entry.name(), entry);
if (attachment != null) skin.addAttachment(slotIndex, entry.name(), attachment);
for (JsonValue skinMap = root.getChild("skins"); skinMap != null; skinMap = skinMap.next) {
Skin skin = new Skin(skinMap.name);
for (JsonValue slotEntry = skinMap.child; slotEntry != null; slotEntry = slotEntry.next) {
int slotIndex = skeletonData.findSlotIndex(slotEntry.name);
if (slotIndex == -1) throw new SerializationException("Slot not found: " + slotEntry.name);
for (JsonValue entry = slotEntry.child; entry != null; entry = entry.next) {
Attachment attachment = readAttachment(skin, entry.name, entry);
if (attachment != null) skin.addAttachment(slotIndex, entry.name, attachment);
}
}
skeletonData.addSkin(skin);
@ -142,8 +143,8 @@ public class SkeletonJson {
}
// Events.
for (JsonValue eventMap = root.getChild("events"); eventMap != null; eventMap = eventMap.next()) {
EventData eventData = new EventData(eventMap.name());
for (JsonValue eventMap = root.getChild("events"); eventMap != null; eventMap = eventMap.next) {
EventData eventData = new EventData(eventMap.name);
eventData.intValue = eventMap.getInt("int", 0);
eventData.floatValue = eventMap.getFloat("float", 0f);
eventData.stringValue = eventMap.getString("string", null);
@ -151,8 +152,8 @@ public class SkeletonJson {
}
// Animations.
for (JsonValue animationMap = root.getChild("animations"); animationMap != null; animationMap = animationMap.next())
readAnimation(animationMap.name(), animationMap, skeletonData);
for (JsonValue animationMap = root.getChild("animations"); animationMap != null; animationMap = animationMap.next)
readAnimation(animationMap.name, animationMap, skeletonData);
skeletonData.bones.shrink();
skeletonData.slots.shrink();
@ -162,6 +163,7 @@ public class SkeletonJson {
}
private Attachment readAttachment (Skin skin, String name, JsonValue map) {
float scale = this.scale;
name = map.getString("name", name);
switch (AttachmentType.valueOf(map.getString("type", AttachmentType.region.name()))) {
@ -182,17 +184,26 @@ public class SkeletonJson {
return region;
case boundingbox: {
BoundingBoxAttachment box = attachmentLoader.newBoundingBoxAttachment(skin, name);
box.setVertices(readFloatArray(map.require("vertices"), scale));
float[] vertices = map.require("vertices").asFloatArray();
if (scale != 1) {
for (int i = 0, n = vertices.length; i < n; i++)
vertices[i] *= scale;
}
box.setVertices(vertices);
return box;
}
case mesh: {
MeshAttachment mesh = attachmentLoader.newMeshAttachment(skin, name, map.getString("path", name));
float[] vertices = readFloatArray(map.require("vertices"), scale);
short[] triangles = readShortArray(map.require("triangles"));
float[] uvs = readFloatArray(map.require("uvs"), 1);
float[] vertices = map.require("vertices").asFloatArray();
if (scale != 1) {
for (int i = 0, n = vertices.length; i < n; i++)
vertices[i] *= scale;
}
short[] triangles = map.require("triangles").asShortArray();
float[] uvs = map.require("uvs").asFloatArray();
mesh.setMesh(vertices, triangles, uvs);
if (map.has("hull")) mesh.setHullLength(map.require("hull").asInt());
if (map.has("edges")) mesh.setEdges(readIntArray(map.require("edges")));
if (map.has("edges")) mesh.setEdges(map.require("edges").asIntArray());
mesh.setWidth(map.getFloat("width", 0) * scale);
mesh.setHeight(map.getFloat("height", 0) * scale);
return mesh;
@ -210,58 +221,34 @@ public class SkeletonJson {
return null;
}
private float[] readFloatArray (JsonValue jsonArray, float scale) {
float[] array = new float[jsonArray.size];
int i = 0;
for (JsonValue point = jsonArray.child; point != null; point = point.next())
array[i++] = point.asFloat() * scale;
return array;
}
private short[] readShortArray (JsonValue jsonArray) {
short[] array = new short[jsonArray.size];
int i = 0;
for (JsonValue point = jsonArray.child; point != null; point = point.next())
array[i++] = (short)point.asInt();
return array;
}
private int[] readIntArray (JsonValue jsonArray) {
int[] array = new int[jsonArray.size];
int i = 0;
for (JsonValue point = jsonArray.child; point != null; point = point.next())
array[i++] = point.asInt();
return array;
}
private void readAnimation (String name, JsonValue map, SkeletonData skeletonData) {
Array<Timeline> timelines = new Array();
float duration = 0;
for (JsonValue boneMap = map.getChild("bones"); boneMap != null; boneMap = boneMap.next()) {
int boneIndex = skeletonData.findBoneIndex(boneMap.name());
if (boneIndex == -1) throw new SerializationException("Bone not found: " + boneMap.name());
// SRT.
for (JsonValue boneMap = map.getChild("bones"); boneMap != null; boneMap = boneMap.next) {
int boneIndex = skeletonData.findBoneIndex(boneMap.name);
if (boneIndex == -1) throw new SerializationException("Bone not found: " + boneMap.name);
for (JsonValue timelineMap = boneMap.child(); timelineMap != null; timelineMap = timelineMap.next()) {
String timelineName = timelineMap.name();
if (timelineName.equals(TIMELINE_ROTATE)) {
for (JsonValue timelineMap = boneMap.child; timelineMap != null; timelineMap = timelineMap.next) {
String timelineName = timelineMap.name;
if (timelineName.equals("rotate")) {
RotateTimeline timeline = new RotateTimeline(timelineMap.size);
timeline.boneIndex = boneIndex;
int frameIndex = 0;
for (JsonValue valueMap = timelineMap.child(); valueMap != null; valueMap = valueMap.next()) {
float time = valueMap.getFloat("time");
timeline.setFrame(frameIndex, time, valueMap.getFloat("angle"));
for (JsonValue valueMap = timelineMap.child; valueMap != null; valueMap = valueMap.next) {
timeline.setFrame(frameIndex, valueMap.getFloat("time"), valueMap.getFloat("angle"));
readCurve(timeline, frameIndex, valueMap);
frameIndex++;
}
timelines.add(timeline);
duration = Math.max(duration, timeline.getFrames()[timeline.getFrameCount() * 2 - 2]);
} else if (timelineName.equals(TIMELINE_TRANSLATE) || timelineName.equals(TIMELINE_SCALE)) {
} else if (timelineName.equals("translate") || timelineName.equals("scale")) {
TranslateTimeline timeline;
float timelineScale = 1;
if (timelineName.equals(TIMELINE_SCALE))
if (timelineName.equals("scale"))
timeline = new ScaleTimeline(timelineMap.size);
else {
timeline = new TranslateTimeline(timelineMap.size);
@ -270,10 +257,9 @@ public class SkeletonJson {
timeline.boneIndex = boneIndex;
int frameIndex = 0;
for (JsonValue valueMap = timelineMap.child(); valueMap != null; valueMap = valueMap.next()) {
float time = valueMap.getFloat("time");
for (JsonValue valueMap = timelineMap.child; valueMap != null; valueMap = valueMap.next) {
float x = valueMap.getFloat("x", 0), y = valueMap.getFloat("y", 0);
timeline.setFrame(frameIndex, time, x * timelineScale, y * timelineScale);
timeline.setFrame(frameIndex, valueMap.getFloat("time"), x * timelineScale, y * timelineScale);
readCurve(timeline, frameIndex, valueMap);
frameIndex++;
}
@ -281,53 +267,88 @@ public class SkeletonJson {
duration = Math.max(duration, timeline.getFrames()[timeline.getFrameCount() * 3 - 3]);
} else
throw new RuntimeException("Invalid timeline type for a bone: " + timelineName + " (" + boneMap.name() + ")");
throw new RuntimeException("Invalid timeline type for a bone: " + timelineName + " (" + boneMap.name + ")");
}
}
for (JsonValue slotMap = map.getChild("slots"); slotMap != null; slotMap = slotMap.next()) {
int slotIndex = skeletonData.findSlotIndex(slotMap.name());
if (slotIndex == -1) throw new SerializationException("Slot not found: " + slotMap.name());
// FFD.
for (JsonValue ffdMap = map.getChild("ffd"); ffdMap != null; ffdMap = ffdMap.next) {
Skin skin = skeletonData.findSkin(ffdMap.name);
if (skin == null) throw new SerializationException("Skin not found: " + ffdMap.name);
for (JsonValue slotMap = ffdMap.child; slotMap != null; slotMap = slotMap.next) {
int slotIndex = skeletonData.findSlotIndex(slotMap.name);
if (slotIndex == -1) throw new SerializationException("Slot not found: " + slotMap.name);
for (JsonValue meshMap = slotMap.child; meshMap != null; meshMap = meshMap.next) {
FfdTimeline timeline = new FfdTimeline(meshMap.size);
MeshAttachment mesh = (MeshAttachment)skin.getAttachment(slotIndex, meshMap.name);
if (mesh == null) throw new SerializationException("Mesh attachment not found: " + meshMap.name);
timeline.slotIndex = slotIndex;
timeline.meshAttachment = mesh;
for (JsonValue timelineMap = slotMap.child(); timelineMap != null; timelineMap = timelineMap.next()) {
String timelineName = timelineMap.name();
if (timelineName.equals(TIMELINE_COLOR)) {
int frameIndex = 0;
for (JsonValue valueMap = meshMap.child; valueMap != null; valueMap = valueMap.next) {
float[] vertices;
JsonValue verticesValue = valueMap.get("vertices");
if (verticesValue == null)
vertices = mesh.getVertices();
else {
vertices = verticesValue.asFloatArray();
if (scale != 1) {
for (int i = 0, n = vertices.length; i < n; i++)
vertices[i] *= scale;
}
}
timeline.setFrame(frameIndex, valueMap.getFloat("time"), vertices);
readCurve(timeline, frameIndex, valueMap);
frameIndex++;
}
timelines.add(timeline);
duration = Math.max(duration, timeline.getFrames()[timeline.getFrameCount() - 1]);
}
}
}
// Color, attachment.
for (JsonValue slotMap = map.getChild("slots"); slotMap != null; slotMap = slotMap.next) {
int slotIndex = skeletonData.findSlotIndex(slotMap.name);
if (slotIndex == -1) throw new SerializationException("Slot not found: " + slotMap.name);
for (JsonValue timelineMap = slotMap.child; timelineMap != null; timelineMap = timelineMap.next) {
String timelineName = timelineMap.name;
if (timelineName.equals("color")) {
ColorTimeline timeline = new ColorTimeline(timelineMap.size);
timeline.slotIndex = slotIndex;
int frameIndex = 0;
for (JsonValue valueMap = timelineMap.child(); valueMap != null; valueMap = valueMap.next()) {
float time = valueMap.getFloat("time");
for (JsonValue valueMap = timelineMap.child; valueMap != null; valueMap = valueMap.next) {
Color color = Color.valueOf(valueMap.getString("color"));
timeline.setFrame(frameIndex, time, color.r, color.g, color.b, color.a);
timeline.setFrame(frameIndex, valueMap.getFloat("time"), color.r, color.g, color.b, color.a);
readCurve(timeline, frameIndex, valueMap);
frameIndex++;
}
timelines.add(timeline);
duration = Math.max(duration, timeline.getFrames()[timeline.getFrameCount() * 5 - 5]);
} else if (timelineName.equals(TIMELINE_ATTACHMENT)) {
} else if (timelineName.equals("attachment")) {
AttachmentTimeline timeline = new AttachmentTimeline(timelineMap.size);
timeline.slotIndex = slotIndex;
int frameIndex = 0;
for (JsonValue valueMap = timelineMap.child(); valueMap != null; valueMap = valueMap.next()) {
float time = valueMap.getFloat("time");
timeline.setFrame(frameIndex++, time, valueMap.getString("name"));
}
for (JsonValue valueMap = timelineMap.child; valueMap != null; valueMap = valueMap.next)
timeline.setFrame(frameIndex++, valueMap.getFloat("time"), valueMap.getString("name"));
timelines.add(timeline);
duration = Math.max(duration, timeline.getFrames()[timeline.getFrameCount() - 1]);
} else
throw new RuntimeException("Invalid timeline type for a slot: " + timelineName + " (" + slotMap.name() + ")");
throw new RuntimeException("Invalid timeline type for a slot: " + timelineName + " (" + slotMap.name + ")");
}
}
// Events.
JsonValue eventsMap = map.get("events");
if (eventsMap != null) {
EventTimeline timeline = new EventTimeline(eventsMap.size);
int frameIndex = 0;
for (JsonValue eventMap = eventsMap.child; eventMap != null; eventMap = eventMap.next()) {
for (JsonValue eventMap = eventsMap.child; eventMap != null; eventMap = eventMap.next) {
EventData eventData = skeletonData.findEvent(eventMap.getString("name"));
if (eventData == null) throw new SerializationException("Event not found: " + eventMap.getString("name"));
Event event = new Event(eventData);
@ -340,12 +361,13 @@ public class SkeletonJson {
duration = Math.max(duration, timeline.getFrames()[timeline.getFrameCount() - 1]);
}
// Draw order.
JsonValue drawOrdersMap = map.get("draworder");
if (drawOrdersMap != null) {
DrawOrderTimeline timeline = new DrawOrderTimeline(drawOrdersMap.size);
int slotCount = skeletonData.slots.size;
int frameIndex = 0;
for (JsonValue drawOrderMap = drawOrdersMap.child; drawOrderMap != null; drawOrderMap = drawOrderMap.next()) {
for (JsonValue drawOrderMap = drawOrdersMap.child; drawOrderMap != null; drawOrderMap = drawOrderMap.next) {
int[] drawOrder = null;
JsonValue offsets = drawOrderMap.get("offsets");
if (offsets != null) {
@ -354,7 +376,7 @@ public class SkeletonJson {
drawOrder[i] = -1;
int[] unchanged = new int[slotCount - offsets.size];
int originalIndex = 0, unchangedIndex = 0;
for (JsonValue offsetMap = offsets.child; offsetMap != null; offsetMap = offsetMap.next()) {
for (JsonValue offsetMap = offsets.child; offsetMap != null; offsetMap = offsetMap.next) {
int slotIndex = skeletonData.findSlotIndex(offsetMap.getString("slot"));
if (slotIndex == -1) throw new SerializationException("Slot not found: " + offsetMap.getString("slot"));
// Collect unchanged items.

View File

@ -31,6 +31,7 @@ package com.esotericsoftware.spine;
import com.esotericsoftware.spine.attachments.Attachment;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.utils.FloatArray;
public class Slot {
final SlotData data;
@ -39,6 +40,7 @@ public class Slot {
final Color color;
Attachment attachment;
private float attachmentTime;
private final FloatArray attachmentVertices = new FloatArray();
Slot () {
data = null;
@ -92,11 +94,13 @@ public class Slot {
return attachment;
}
/** Sets the attachment and resets {@link #getAttachmentTime()}.
/** Sets the attachment, resets {@link #getAttachmentTime()}, and clears {@link #getAttachmentVertices()}.
* @param attachment May be null. */
public void setAttachment (Attachment attachment) {
if (this.attachment == attachment) return;
this.attachment = attachment;
attachmentTime = skeleton.time;
attachmentVertices.clear();
}
public void setAttachmentTime (float time) {
@ -108,6 +112,10 @@ public class Slot {
return skeleton.time - attachmentTime;
}
public FloatArray getAttachmentVertices () {
return attachmentVertices;
}
void setToSetupPose (int slotIndex) {
color.set(data.color);
setAttachment(data.attachmentName == null ? null : skeleton.getAttachment(slotIndex, data.attachmentName));

View File

@ -34,19 +34,22 @@ import com.esotericsoftware.spine.Slot;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.utils.FloatArray;
import com.badlogic.gdx.utils.NumberUtils;
/** Attachment that displays a texture region. */
public class MeshAttachment extends Attachment {
private TextureRegion region;
private String path;
private int hullLength;
private float[] vertices;
private short[] triangles;
private int[] edges;
private float[] worldVertices;
private final Color color = new Color(1, 1, 1, 1);
// Nonessential.
private int[] edges;
private float width, height;
private int hullLength;
public MeshAttachment (String name) {
super(name);
@ -71,41 +74,24 @@ public class MeshAttachment extends Attachment {
float g = skeletonColor.g * slotColor.g * regionColor.g;
float b = skeletonColor.b * slotColor.b * regionColor.b;
float a = skeletonColor.a * slotColor.a * regionColor.a * 255;
float color;
if (premultipliedAlpha) {
r *= a;
g *= a;
b *= a;
} else {
r *= 255;
g *= 255;
b *= 255;
}
color = NumberUtils.intToFloatColor( //
float multiplier = premultipliedAlpha ? a : 255;
float color = NumberUtils.intToFloatColor( //
((int)(a) << 24) //
| ((int)(b) << 16) //
| ((int)(g) << 8) //
| ((int)(r)));
| ((int)(b * multiplier) << 16) //
| ((int)(g * multiplier) << 8) //
| ((int)(r * multiplier)));
float[] worldVertices = this.worldVertices;
float[] vertices = this.vertices;
Bone bone1 = slot.getBone();
float x = skeleton.getX();
float y = skeleton.getY();
float m00 = bone1.getM00();
float m01 = bone1.getM01();
float m10 = bone1.getM10();
float m11 = bone1.getM11();
float vx, vy;
for (int v = 0, w = 0, n = vertices.length; v < n; v += 2, w += 5) {
vx = vertices[v];
vy = vertices[v + 1];
float wx1 = vx * m00 + vy * m01 + x + bone1.getWorldX();
float wy1 = vx * m10 + vy * m11 + y + bone1.getWorldY();
worldVertices[w] = wx1;
worldVertices[w + 1] = wy1;
worldVertices[w + 2] = Color.WHITE.toFloatBits();
FloatArray verticesArray = slot.getAttachmentVertices();
float[] vertices = verticesArray.size > 0 ? verticesArray.items : this.vertices;
Bone bone = slot.getBone();
float x = skeleton.getX() + bone.getWorldX(), y = skeleton.getY() + bone.getWorldY();
float m00 = bone.getM00(), m01 = bone.getM01(), m10 = bone.getM10(), m11 = bone.getM11();
for (int v = 0, w = 0, n = worldVertices.length; w < n; v += 2, w += 5) {
float vx = vertices[v];
float vy = vertices[v + 1];
worldVertices[w] = vx * m00 + vy * m01 + x;
worldVertices[w + 1] = vx * m10 + vy * m11 + y;
worldVertices[w + 2] = color;
}
}
@ -173,9 +159,19 @@ public class MeshAttachment extends Attachment {
int worldVerticesLength = vertices.length / 2 * 5;
if (worldVertices == null || worldVertices.length != worldVerticesLength) worldVertices = new float[worldVerticesLength];
for (int i = 0, w = 3, n = vertices.length; i < n; i += 2, w += 5) {
worldVertices[w] = uvs[i];
worldVertices[w + 1] = uvs[i + 1];
float u, v, w, h;
if (region == null) {
u = v = 0;
w = h = 1;
} else {
u = region.getU();
v = region.getV();
w = region.getU2() - u;
h = region.getV2() - v;
}
for (int i = 0, ii = 3, n = vertices.length; i < n; i += 2, ii += 5) {
worldVertices[ii] = u + uvs[i] * w;
worldVertices[ii + 1] = v + uvs[i + 1] * h;
}
}
}

View File

@ -153,32 +153,20 @@ public class RegionAttachment extends Attachment {
float g = skeletonColor.g * slotColor.g * regionColor.g;
float b = skeletonColor.b * slotColor.b * regionColor.b;
float a = skeletonColor.a * slotColor.a * regionColor.a * 255;
float color;
if (premultipliedAlpha) {
r *= a;
g *= a;
b *= a;
} else {
r *= 255;
g *= 255;
b *= 255;
}
color = NumberUtils.intToFloatColor( //
float multiplier = premultipliedAlpha ? a : 255;
float color = NumberUtils.intToFloatColor( //
((int)(a) << 24) //
| ((int)(b) << 16) //
| ((int)(g) << 8) //
| ((int)(r)));
| ((int)(b * multiplier) << 16) //
| ((int)(g * multiplier) << 8) //
| ((int)(r * multiplier)));
float[] vertices = this.vertices;
float[] offset = this.offset;
Bone bone = slot.getBone();
float x = bone.getWorldX() + skeleton.getX();
float y = bone.getWorldY() + skeleton.getY();
float m00 = bone.getM00();
float m01 = bone.getM01();
float m10 = bone.getM10();
float m11 = bone.getM11();
float x = skeleton.getX() + bone.getWorldX(), y = skeleton.getY() + bone.getWorldY();
float m00 = bone.getM00(), m01 = bone.getM01(), m10 = bone.getM10(), m11 = bone.getM11();
float offsetX, offsetY;
offsetX = offset[BRX];
offsetY = offset[BRY];
vertices[X1] = offsetX * m00 + offsetY * m01 + x; // br

View File

@ -38,14 +38,14 @@ 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.PolygonSpriteBatch;
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.utils.Array;
public class SkeletonTest extends ApplicationAdapter {
SpriteBatch batch;
PolygonSpriteBatch batch;
float time;
SkeletonRenderer renderer;
SkeletonRendererDebug debugRenderer;
@ -56,7 +56,7 @@ public class SkeletonTest extends ApplicationAdapter {
Array<Event> events = new Array();
public void create () {
batch = new SpriteBatch();
batch = new PolygonSpriteBatch();
renderer = new SkeletonRenderer();
debugRenderer = new SkeletonRendererDebug();
@ -120,6 +120,7 @@ public class SkeletonTest extends ApplicationAdapter {
if (x > Gdx.graphics.getWidth()) skeleton.setFlipX(true);
if (x < 0) skeleton.setFlipX(false);
skeleton.setX(x);
skeleton.setX(300);
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
@ -134,7 +135,7 @@ public class SkeletonTest extends ApplicationAdapter {
renderer.draw(batch, skeleton);
batch.end();
debugRenderer.draw(skeleton);
//debugRenderer.draw(skeleton);
}
public void resize (int width, int height) {

View File

@ -8,10 +8,10 @@
{ "name": "torso", "parent": "hip", "length": 85.82, "x": -6.42, "y": 1.97, "rotation": 93.92 },
{ "name": "left lower leg", "parent": "left upper leg", "length": 49.89, "x": 56.34, "y": 0.98, "rotation": -16.65 },
{ "name": "left shoulder", "parent": "torso", "length": 35.43, "x": 74.04, "y": -20.38, "rotation": -156.96 },
{ "name": "neck", "parent": "torso", "length": 18.38, "x": 81.67, "y": -6.34, "rotation": -13.92 },
{ "name": "neck", "parent": "torso", "length": 18.38, "x": 81.67, "y": -6.34, "rotation": -1.51 },
{ "name": "right lower leg", "parent": "right upper leg", "length": 58.52, "x": 42.99, "y": -0.61, "rotation": -14.34 },
{ "name": "right shoulder", "parent": "torso", "length": 37.24, "x": 76.02, "y": 18.14, "rotation": 133.88 },
{ "name": "head", "parent": "neck", "length": 68.28, "x": 20.93, "y": 11.59 },
{ "name": "head", "parent": "neck", "length": 68.28, "x": 20.93, "y": 11.59, "rotation": -13.92, "color": "d48387ff" },
{ "name": "left arm", "parent": "left shoulder", "length": 35.62, "x": 37.85, "y": -2.34, "rotation": 28.16 },
{ "name": "left foot", "parent": "left lower leg", "length": 46.5, "x": 58.94, "y": -7.61, "rotation": 102.43 },
{ "name": "right arm", "parent": "right shoulder", "length": 36.74, "x": 37.6, "y": 0.31, "rotation": 36.32 },
@ -26,9 +26,9 @@
{ "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": "boundingbox" },
{ "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": "torso", "bone": "torso", "attachment": "goblin/head" },
{ "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" },
@ -40,81 +40,34 @@
{ "name": "right shoulder", "bone": "right shoulder", "attachment": "right shoulder" },
{ "name": "right arm", "bone": "right arm", "attachment": "right arm" },
{ "name": "right hand item", "bone": "right hand", "attachment": "dagger" },
{ "name": "right hand", "bone": "right hand", "attachment": "right hand" },
{ "name": "bounding box", "bone": "head", "attachment": "bbox" }
{ "name": "right hand", "bone": "right hand", "attachment": "right hand" }
],
"skins": {
"default": {
"bounding box": {
"bbox": {
"type": "boundingbox",
"vertices": [
-7.2252045,
-34.808647,
-1.9847412,
-40.70198,
12.63089,
-37.503178,
13.559738,
-44.273937,
21.15651,
-45.197586,
25.916733,
-36.59557,
30.31987,
-37.271088,
31.82283,
-47.966175,
36.74109,
-47.863335,
37.828888,
-37.763573,
52.86676,
-18.987732,
54.687653,
4.058235,
42.064606,
24.16484,
37.09868,
25.39936,
36.084442,
44.725235,
28.417389,
53.716904,
23.382614,
49.773712,
28.766006,
39.868294,
25.873352,
27.605164,
8.960373,
14.733994,
-0.746933,
1.7576027
]
}
},
"left hand item": {
"dagger": { "x": 7.88, "y": -23.45, "rotation": 10.47, "width": 26, "height": 108 },
"spear": { "x": -4.55, "y": 39.2, "rotation": 13.04, "width": 22, "height": 368 }
},
"left upper leg": {
"boundingbox": {
"type": "boundingbox",
"vertices": [ -73.94766, 8.514406, -49.917465, 25.294191, -79.28125, 46.664314, -95.755325, 34.604897, -74.9664, 27.453112 ]
}
},
"right hand item": {
"dagger": { "x": 6.51, "y": -24.15, "rotation": -8.06, "width": 26, "height": 108 }
},
"torso": {
"goblin/head": {
"type": "mesh",
"vertices": [ 87.34, -39.83, 109.98, 60.57, 174.32, 46.07, 151.68, -54.33 ],
"triangles": [ 1, 2, 3, 1, 3, 0 ],
"uvs": [ 1, 1, 0, 1, 0, 0, 1, 0 ],
"edges": [ 0, 2, 2, 4, 4, 6, 0, 6 ],
"hull": 8,
"width": 103,
"height": 66
}
}
},
"goblin": {
"eyes": {
"eyes closed": { "name": "goblin/eyes-closed", "x": 32.21, "y": -21.27, "rotation": -88.92, "width": 34, "height": 12 }
},
"head": {
"head": { "name": "goblin/head", "x": 25.73, "y": 2.33, "rotation": -92.29, "width": 103, "height": 66 }
},
"left arm": {
"left arm": {
"name": "goblin/left-arm",
@ -546,11 +499,25 @@
]
}
},
"ffd": {
"default": {
"torso": {
"goblin/head": [
{ "time": 0 },
{
"time": 0.5,
"vertices": [ 78.53, -53.55, 96.74, 63.46, 197.1, 24.55, 144.41, -37.48 ]
},
{ "time": 1 }
]
}
}
},
"slots": {
"eyes": {
"attachment": [
{ "time": 0.7, "name": "eyes closed" },
{ "time": 0.8, "name": null }
"head": {
"color": [
{ "time": 0.1, "color": "ffffffff" },
{ "time": 0.8, "color": "fcff00ff" }
]
}
},

Binary file not shown.