Committed spine-c into spine-unity.

Screw it, it's easier for Unity people.
This commit is contained in:
NathanSweet 2013-04-23 21:54:52 +02:00
parent 803a8ab045
commit 4a4098ed45
19 changed files with 2645 additions and 0 deletions

View File

@ -0,0 +1,415 @@
/*******************************************************************************
* Copyright (c) 2013, Esoteric Software
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
using System;
using System.Collections.Generic;
namespace Spine {
public class Animation {
public String Name { get; private set; }
public List<Timeline> Timelines { get; set; }
public float Duration { get; set; }
public Animation (String name, List<Timeline> timelines, float duration) {
if (name == null) throw new ArgumentNullException("name cannot be null.");
if (timelines == null) throw new ArgumentNullException("timelines cannot be null.");
Name = name;
Timelines = timelines;
Duration = duration;
}
/** Poses the skeleton at the specified time for this animation. */
public void Apply (Skeleton skeleton, float time, bool loop) {
if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null.");
if (loop && Duration != 0) time %= Duration;
List<Timeline> timelines = Timelines;
for (int i = 0, n = timelines.Count; i < n; i++)
timelines[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, bool loop, float alpha) {
if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null.");
if (loop && Duration != 0) time %= Duration;
List<Timeline> timelines = Timelines;
for (int i = 0, n = timelines.Count; i < n; i++)
timelines[i].Apply(skeleton, time, alpha);
}
/** @param target After the first and before the last entry. */
internal 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 = (int)((uint)high >> 1);
while (true) {
if (values[(current + 1) * step] <= target)
low = current + 1;
else
high = current;
if (low == high) return (low + 1) * step;
current = (int)((uint)(low + high) >> 1);
}
}
internal 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) return i;
return -1;
}
}
public interface Timeline {
/** Sets the value(s) for the specified time. */
void Apply (Skeleton skeleton, float time, float alpha);
}
/** Base class for frames that use an interpolation bezier curve. */
abstract public class CurveTimeline : Timeline {
static protected float LINEAR = 0;
static protected float STEPPED = -1;
static protected int BEZIER_SEGMENTS = 10;
private float[] curves; // dfx, dfy, ddfx, ddfy, dddfx, dddfy, ...
public int FrameCount {
get {
return curves.Length / 6 + 1;
}
}
public CurveTimeline (int frameCount) {
curves = new float[(frameCount - 1) * 6];
}
abstract public void Apply (Skeleton skeleton, float time, float alpha);
public void SetLinear (int frameIndex) {
curves[frameIndex * 6] = LINEAR;
}
public void SetStepped (int frameIndex) {
curves[frameIndex * 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 frameIndex, 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 = frameIndex * 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 frameIndex, float percent) {
int curveIndex = frameIndex * 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.
}
}
public class RotateTimeline : CurveTimeline {
static protected int LAST_FRAME_TIME = -2;
static protected int FRAME_VALUE = 1;
public int BoneIndex { get; set; }
public float[] Frames { get; private set; } // time, value, ...
public RotateTimeline (int frameCount)
: base(frameCount) {
Frames = new float[frameCount * 2];
}
/** Sets the time and value of the specified keyframe. */
public void SetFrame (int frameIndex, float time, float angle) {
frameIndex *= 2;
Frames[frameIndex] = time;
Frames[frameIndex + 1] = angle;
}
override public void Apply (Skeleton skeleton, float time, float alpha) {
float[] frames = Frames;
if (time < frames[0]) return; // Time is before first frame.
Bone bone = skeleton.Bones[BoneIndex];
float amount;
if (time >= frames[frames.Length - 2]) { // Time is after last frame.
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 = Animation.binarySearch(frames, time, 2);
float lastFrameValue = frames[frameIndex - 1];
float frameTime = frames[frameIndex];
float percent = 1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime);
percent = GetCurvePercent(frameIndex / 2 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
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;
}
}
public class TranslateTimeline : CurveTimeline {
static protected int LAST_FRAME_TIME = -3;
static protected int FRAME_X = 1;
static protected int FRAME_Y = 2;
public int BoneIndex { get; set; }
public float[] Frames { get; private set; } // time, value, value, ...
public TranslateTimeline (int frameCount)
: base(frameCount) {
Frames = new float[frameCount * 3];
}
/** Sets the time and value of the specified keyframe. */
public void SetFrame (int frameIndex, float time, float x, float y) {
frameIndex *= 3;
Frames[frameIndex] = time;
Frames[frameIndex + 1] = x;
Frames[frameIndex + 2] = y;
}
override public void Apply (Skeleton skeleton, float time, float alpha) {
float[] frames = Frames;
if (time < frames[0]) return; // Time is before first frame.
Bone bone = skeleton.Bones[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 = Animation.binarySearch(frames, time, 3);
float lastFrameX = frames[frameIndex - 2];
float lastFrameY = frames[frameIndex - 1];
float frameTime = frames[frameIndex];
float percent = 1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime);
percent = GetCurvePercent(frameIndex / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 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;
}
}
public class ScaleTimeline : TranslateTimeline {
public ScaleTimeline (int frameCount)
: base(frameCount) {
}
override public void Apply (Skeleton skeleton, float time, float alpha) {
float[] frames = Frames;
if (time < frames[0]) return; // Time is before first frame.
Bone bone = skeleton.Bones[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 = Animation.binarySearch(frames, time, 3);
float lastFrameX = frames[frameIndex - 2];
float lastFrameY = frames[frameIndex - 1];
float frameTime = frames[frameIndex];
float percent = 1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime);
percent = GetCurvePercent(frameIndex / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 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;
}
}
public class ColorTimeline : CurveTimeline {
static protected int LAST_FRAME_TIME = -5;
static protected int FRAME_R = 1;
static protected int FRAME_G = 2;
static protected int FRAME_B = 3;
static protected int FRAME_A = 4;
public int SlotIndex { get; set; }
public float[] Frames { get; private set; } // time, r, g, b, a, ...
public ColorTimeline (int frameCount)
: base(frameCount) {
Frames = new float[frameCount * 5];
}
/** Sets the time and value of the specified keyframe. */
public void setFrame (int frameIndex, float time, float r, float g, float b, float a) {
frameIndex *= 5;
Frames[frameIndex] = time;
Frames[frameIndex + 1] = r;
Frames[frameIndex + 2] = g;
Frames[frameIndex + 3] = b;
Frames[frameIndex + 4] = a;
}
override public void Apply (Skeleton skeleton, float time, float alpha) {
float[] frames = Frames;
if (time < frames[0]) return; // Time is before first frame.
Slot slot = skeleton.Slots[SlotIndex];
if (time >= frames[frames.Length - 5]) { // Time is after last frame.
int i = frames.Length - 1;
slot.R = frames[i - 3];
slot.G = frames[i - 2];
slot.B = frames[i - 1];
slot.A = frames[i];
return;
}
// Interpolate between the last frame and the current frame.
int frameIndex = Animation.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 = 1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime);
percent = GetCurvePercent(frameIndex / 5 - 1, percent < 0 ? 0 : (percent > 1 ? 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) {
slot.R += (r - slot.R) * alpha;
slot.G += (g - slot.G) * alpha;
slot.B += (b - slot.B) * alpha;
slot.A += (a - slot.A) * alpha;
} else {
slot.R = r;
slot.G = g;
slot.B = b;
slot.A = a;
}
}
}
public class AttachmentTimeline : Timeline {
public int SlotIndex { get; set; }
public float[] Frames { get; private set; } // time, ...
public String[] AttachmentNames { get; private set; }
public int FrameCount {
get {
return Frames.Length;
}
}
public AttachmentTimeline (int frameCount) {
Frames = new float[frameCount];
AttachmentNames = new String[frameCount];
}
/** Sets the time and value of the specified keyframe. */
public void setFrame (int frameIndex, float time, String attachmentName) {
Frames[frameIndex] = time;
AttachmentNames[frameIndex] = attachmentName;
}
public void Apply (Skeleton skeleton, float time, float alpha) {
float[] frames = 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 = Animation.binarySearch(frames, time, 1) - 1;
String attachmentName = AttachmentNames[frameIndex];
skeleton.Slots[SlotIndex].Attachment =
attachmentName == null ? null : skeleton.GetAttachment(SlotIndex, attachmentName);
}
}
}

View File

@ -0,0 +1,101 @@
/*******************************************************************************
* Copyright (c) 2013, Esoteric Software
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
using System;
using System.Collections.Generic;
namespace Spine {
public class AnimationState {
public AnimationStateData Data { get; private set; }
public Animation Animation { get; private set; }
public float Time { get; set; }
public bool Loop { get; set; }
private Animation previous;
float previousTime;
bool previousLoop;
float mixTime, mixDuration;
public AnimationState (AnimationStateData data) {
if (data == null) throw new ArgumentNullException("data cannot be null.");
Data = data;
}
public void Update (float delta) {
Time += delta;
previousTime += delta;
mixTime += delta;
}
public void Apply (Skeleton skeleton) {
if (Animation == null) return;
if (previous != null) {
previous.Apply(skeleton, previousTime, previousLoop);
float alpha = mixTime / mixDuration;
if (alpha >= 1) {
alpha = 1;
previous = null;
}
Animation.Mix(skeleton, Time, Loop, alpha);
} else
Animation.Apply(skeleton, Time, Loop);
}
public void SetAnimation (String animationName, bool loop) {
Animation animation = Data.SkeletonData.FindAnimation(animationName);
if (animation == null) throw new ArgumentException("Animation not found: " + animationName);
SetAnimation(animation, loop);
}
public void SetAnimation (Animation animation, bool loop) {
previous = null;
if (animation != null && Animation != null) {
mixDuration = Data.GetMix(Animation, animation);
if (mixDuration > 0) {
mixTime = 0;
previous = Animation;
previousTime = Time;
previousLoop = Loop;
}
}
Animation = animation;
Loop = loop;
Time = 0;
}
public void ClearAnimation () {
previous = null;
Animation = null;
}
/** Returns true if no animation is set or if the current time is greater than the animation duration, regardless of looping. */
public bool isComplete () {
return Animation == null || Time >= Animation.Duration;
}
override public String ToString () {
return (Animation != null && Animation.Name != null) ? Animation.Name : base.ToString();
}
}
}

View File

@ -0,0 +1,61 @@
/*******************************************************************************
* Copyright (c) 2013, Esoteric Software
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
using System;
using System.Collections.Generic;
namespace Spine {
public class AnimationStateData {
public SkeletonData SkeletonData { get; private set; }
private Dictionary<KeyValuePair<Animation, Animation>, float> animationToMixTime = new Dictionary<KeyValuePair<Animation, Animation>, float>();
public AnimationStateData (SkeletonData skeletonData) {
SkeletonData = skeletonData;
}
public void SetMix (String fromName, String toName, float duration) {
Animation from = SkeletonData.FindAnimation(fromName);
if (from == null) throw new ArgumentException("Animation not found: " + fromName);
Animation to = SkeletonData.FindAnimation(toName);
if (to == null) throw new ArgumentException("Animation not found: " + toName);
SetMix(from, to, duration);
}
public void SetMix (Animation from, Animation to, float duration) {
if (from == null) throw new ArgumentNullException("from cannot be null.");
if (to == null) throw new ArgumentNullException("to cannot be null.");
KeyValuePair<Animation, Animation> key = new KeyValuePair<Animation, Animation>(from, to);
animationToMixTime.Remove(key);
animationToMixTime.Add(key, duration);
}
public float GetMix (Animation from, Animation to) {
KeyValuePair<Animation, Animation> key = new KeyValuePair<Animation, Animation>(from, to);
float duration;
animationToMixTime.TryGetValue(key, out duration);
return duration;
}
}
}

View File

@ -0,0 +1,229 @@
/*******************************************************************************
* Copyright (c) 2013, Esoteric Software
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
using System;
using System.Collections.Generic;
using System.IO;
namespace Spine {
public class Atlas {
List<AtlasPage> pages = new List<AtlasPage>();
List<AtlasRegion> regions = new List<AtlasRegion>();
TextureLoader textureLoader;
public Atlas (String path, TextureLoader textureLoader) {
using (StreamReader reader = new StreamReader(path)) {
try {
Load(reader, Path.GetDirectoryName(path), textureLoader);
} catch (Exception ex) {
throw new Exception("Error reading atlas file: " + path, ex);
}
}
}
public Atlas (TextReader reader, String dir, TextureLoader textureLoader) {
Load(reader, dir, textureLoader);
}
private void Load (TextReader reader, String imagesDir, TextureLoader textureLoader) {
if (textureLoader == null) throw new ArgumentNullException("textureLoader cannot be null.");
this.textureLoader = textureLoader;
String[] tuple = new String[4];
AtlasPage page = null;
while (true) {
String line = reader.ReadLine();
if (line == null) break;
if (line.Trim().Length == 0)
page = null;
else if (page == null) {
page = new AtlasPage();
page.name = line;
page.format = (Format)Enum.Parse(typeof(Format), readValue(reader), false);
readTuple(reader, tuple);
page.minFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[0]);
page.magFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[1]);
String direction = readValue(reader);
page.uWrap = TextureWrap.ClampToEdge;
page.vWrap = TextureWrap.ClampToEdge;
if (direction == "x")
page.uWrap = TextureWrap.Repeat;
else if (direction == "y")
page.vWrap = TextureWrap.Repeat;
else if (direction == "xy")
page.uWrap = page.vWrap = TextureWrap.Repeat;
textureLoader.Load(page, Path.Combine(imagesDir, line));
pages.Add(page);
} else {
AtlasRegion region = new AtlasRegion();
region.name = line;
region.page = page;
region.rotate = Boolean.Parse(readValue(reader));
readTuple(reader, tuple);
int x = int.Parse(tuple[0]);
int y = int.Parse(tuple[1]);
readTuple(reader, tuple);
int width = int.Parse(tuple[0]);
int height = int.Parse(tuple[1]);
region.u = x / (float)page.width;
region.v = y / (float)page.height;
region.u2 = (x + width) / (float)page.width;
region.v2 = (y + height) / (float)page.height;
region.x = x;
region.y = y;
region.width = Math.Abs(width);
region.height = Math.Abs(height);
if (readTuple(reader, tuple) == 4) { // split is optional
region.splits = new int[] {int.Parse(tuple[0]), int.Parse(tuple[1]),
int.Parse(tuple[2]), int.Parse(tuple[3])};
if (readTuple(reader, tuple) == 4) { // pad is optional, but only present with splits
region.pads = new int[] {int.Parse(tuple[0]), int.Parse(tuple[1]),
int.Parse(tuple[2]), int.Parse(tuple[3])};
readTuple(reader, tuple);
}
}
region.originalWidth = int.Parse(tuple[0]);
region.originalHeight = int.Parse(tuple[1]);
readTuple(reader, tuple);
region.offsetX = int.Parse(tuple[0]);
region.offsetY = int.Parse(tuple[1]);
region.index = int.Parse(readValue(reader));
regions.Add(region);
}
}
}
static String readValue (TextReader reader) {
String line = reader.ReadLine();
int colon = line.IndexOf(':');
if (colon == -1) throw new Exception("Invalid line: " + line);
return line.Substring(colon + 1).Trim();
}
/** Returns the number of tuple values read (2 or 4). */
static int readTuple (TextReader reader, String[] tuple) {
String line = reader.ReadLine();
int colon = line.IndexOf(':');
if (colon == -1) throw new Exception("Invalid line: " + line);
int i = 0, lastMatch = colon + 1;
for (; i < 3; i++) {
int comma = line.IndexOf(',', lastMatch);
if (comma == -1) {
if (i == 0) throw new Exception("Invalid line: " + line);
break;
}
tuple[i] = line.Substring(lastMatch, comma - lastMatch).Trim();
lastMatch = comma + 1;
}
tuple[i] = line.Substring(lastMatch).Trim();
return i + 1;
}
/** Returns the first region found with the specified name. This method uses string comparison to find the region, so the result
* should be cached rather than calling this method multiple times.
* @return The region, or null. */
public AtlasRegion FindRegion (String name) {
for (int i = 0, n = regions.Count; i < n; i++)
if (regions[i].name == name) return regions[i];
return null;
}
public void Dispose () {
for (int i = 0, n = pages.Count; i < n; i++)
textureLoader.Unload(pages[i].texture);
}
}
public enum Format {
Alpha,
Intensity,
LuminanceAlpha,
RGB565,
RGBA4444,
RGB888,
RGBA8888
}
public enum TextureFilter {
Nearest,
Linear,
MipMap,
MipMapNearestNearest,
MipMapLinearNearest,
MipMapNearestLinear,
MipMapLinearLinear
}
public enum TextureWrap {
MirroredRepeat,
ClampToEdge,
Repeat
}
public class AtlasPage {
public String name;
public Format format;
public TextureFilter minFilter;
public TextureFilter magFilter;
public TextureWrap uWrap;
public TextureWrap vWrap;
public Object texture;
public int width, height;
}
public class AtlasRegion {
public AtlasPage page;
public String name;
public int x, y, width, height;
public float u, v, u2, v2;
public float offsetX, offsetY;
public int originalWidth, originalHeight;
public int index;
public bool rotate;
public int[] splits;
public int[] pads;
}
public interface TextureLoader {
void Load (AtlasPage page, String path);
void Unload (Object texture);
}
}

View File

@ -0,0 +1,49 @@
/*******************************************************************************
* Copyright (c) 2013, Esoteric Software
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
using System;
namespace Spine {
public class AtlasAttachmentLoader : AttachmentLoader {
private Atlas atlas;
public AtlasAttachmentLoader (Atlas atlas) {
if (atlas == null) throw new ArgumentNullException("atlas cannot be null.");
this.atlas = atlas;
}
public Attachment NewAttachment (Skin skin, AttachmentType type, String name) {
switch (type) {
case AttachmentType.region:
AtlasRegion region = atlas.FindRegion(name);
if (region == null) throw new Exception("Region not found in atlas: " + name + " (" + type + ")");
RegionAttachment attachment = new RegionAttachment(name);
attachment.Region = region;
return attachment;
}
throw new Exception("Unknown attachment type: " + type);
}
}
}

View File

@ -0,0 +1,41 @@
/*******************************************************************************
* Copyright (c) 2013, Esoteric Software
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
using System;
namespace Spine {
abstract public class Attachment {
public String Name { get; private set; }
public Attachment (String name) {
if (name == null) throw new ArgumentNullException("name cannot be null.");
Name = name;
}
override public String ToString () {
return Name;
}
}
}

View File

@ -0,0 +1,33 @@
/*******************************************************************************
* Copyright (c) 2013, Esoteric Software
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
using System;
namespace Spine {
public interface AttachmentLoader {
/** @return May be null to not load any attachment. */
Attachment NewAttachment (Skin skin, AttachmentType type, String name);
}
}

View File

@ -0,0 +1,30 @@
/*******************************************************************************
* Copyright (c) 2013, Esoteric Software
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
namespace Spine {
public enum AttachmentType {
region, regionSequence
}
}

View File

@ -0,0 +1,130 @@
/*******************************************************************************
* Copyright (c) 2013, Esoteric Software
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
using System;
namespace Spine {
/** Attachment that displays a texture region. */
public class RegionAttachment : Attachment {
public const int X1 = 0;
public const int Y1 = 1;
public const int X2 = 2;
public const int Y2 = 3;
public const int X3 = 4;
public const int Y3 = 5;
public const int X4 = 6;
public const int Y4 = 7;
public float X { get; set; }
public float Y { get; set; }
public float ScaleX { get; set; }
public float ScaleY { get; set; }
public float Rotation { get; set; }
public float Width { get; set; }
public float Height { get; set; }
public float[] Offset { get; private set; }
public float[] Vertices { get; private set; }
public AtlasRegion Region { get; set; }
public RegionAttachment (string name)
: base(name) {
Offset = new float[8];
Vertices = new float[8];
ScaleX = 1;
ScaleY = 1;
}
public void UpdateOffset () {
float width = Width;
float height = Height;
float localX2 = width / 2;
float localY2 = height / 2;
float localX = -localX2;
float localY = -localY2;
AtlasRegion region = Region;
if (region != null) {
if (region.rotate) {
localX += region.offsetX / region.originalWidth * height;
localY += region.offsetY / region.originalHeight * width;
localX2 -= (region.originalWidth - region.offsetX - region.height) / region.originalWidth * width;
localY2 -= (region.originalHeight - region.offsetY - region.width) / region.originalHeight * height;
} else {
localX += region.offsetX / region.originalWidth * width;
localY += region.offsetY / region.originalHeight * height;
localX2 -= (region.originalWidth - region.offsetX - region.width) / region.originalWidth * width;
localY2 -= (region.originalHeight - region.offsetY - region.height) / region.originalHeight * height;
}
}
float scaleX = ScaleX;
float scaleY = ScaleY;
localX *= scaleX;
localY *= scaleY;
localX2 *= scaleX;
localY2 *= scaleY;
float radians = Rotation * (float)Math.PI / 180;
float cos = (float)Math.Cos(radians);
float sin = (float)Math.Sin(radians);
float x = X;
float y = Y;
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 = Offset;
offset[X1] = localXCos - localYSin;
offset[Y1] = localYCos + localXSin;
offset[X2] = localXCos - localY2Sin;
offset[Y2] = localY2Cos + localXSin;
offset[X3] = localX2Cos - localY2Sin;
offset[Y3] = localY2Cos + localX2Sin;
offset[X4] = localX2Cos - localYSin;
offset[Y4] = localYCos + localX2Sin;
}
public void UpdateVertices (Bone bone) {
float x = bone.WorldX;
float y = bone.WorldY;
float m00 = bone.M00;
float m01 = bone.M01;
float m10 = bone.M10;
float m11 = bone.M11;
float[] vertices = Vertices;
float[] offset = Offset;
vertices[X1] = offset[X1] * m00 + offset[Y1] * m01 + x;
vertices[Y1] = offset[X1] * m10 + offset[Y1] * m11 + y;
vertices[X2] = offset[X2] * m00 + offset[Y2] * m01 + x;
vertices[Y2] = offset[X2] * m10 + offset[Y2] * m11 + y;
vertices[X3] = offset[X3] * m00 + offset[Y3] * m01 + x;
vertices[Y3] = offset[X3] * m10 + offset[Y3] * m11 + y;
vertices[X4] = offset[X4] * m00 + offset[Y4] * m01 + x;
vertices[Y4] = offset[X4] * m10 + offset[Y4] * m11 + y;
}
}
}

View File

@ -0,0 +1,108 @@
/*******************************************************************************
* Copyright (c) 2013, Esoteric Software
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
using System;
namespace Spine {
public class Bone {
static public bool yDown;
public BoneData Data { get; private set; }
public Bone Parent { get; private set; }
public float X { get; set; }
public float Y { get; set; }
public float Rotation { get; set; }
public float ScaleX { get; set; }
public float ScaleY { get; set; }
public float M00 { get; private set; }
public float M01 { get; private set; }
public float M10 { get; private set; }
public float M11 { get; private set; }
public float WorldX { get; private set; }
public float WorldY { get; private set; }
public float WorldRotation { get; private set; }
public float WorldScaleX { get; private set; }
public float WorldScaleY { get; private set; }
/** @param parent May be null. */
public Bone (BoneData data, Bone parent) {
if (data == null) throw new ArgumentNullException("data cannot be null.");
Data = data;
Parent = parent;
SetToBindPose();
}
/** Computes the world SRT using the parent bone and the local SRT. */
public void UpdateWorldTransform (bool flipX, bool flipY) {
Bone parent = 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 radians = WorldRotation * (float)Math.PI / 180;
float cos = (float)Math.Cos(radians);
float sin = (float)Math.Sin(radians);
M00 = cos * WorldScaleX;
M10 = sin * WorldScaleX;
M01 = -sin * WorldScaleY;
M11 = cos * WorldScaleY;
if (flipX) {
M00 = -M00;
M01 = -M01;
}
if (flipY) {
M10 = -M10;
M11 = -M11;
}
if (yDown) {
M10 = -M10;
M11 = -M11;
}
}
public void SetToBindPose () {
BoneData data = Data;
X = data.X;
Y = data.Y;
Rotation = data.Rotation;
ScaleX = data.ScaleX;
ScaleY = data.ScaleY;
}
override public String ToString () {
return Data.Name;
}
}
}

View File

@ -0,0 +1,53 @@
/*******************************************************************************
* Copyright (c) 2013, Esoteric Software
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
using System;
namespace Spine {
public class BoneData {
/** May be null. */
public BoneData Parent { get; private set; }
public String Name { get; private set; }
public float Length { get; set; }
public float X { get; set; }
public float Y { get; set; }
public float Rotation { get; set; }
public float ScaleX { get; set; }
public float ScaleY { get; set; }
/** @param parent May be null. */
public BoneData (String name, BoneData parent) {
if (name == null) throw new ArgumentNullException("name cannot be null.");
Name = name;
Parent = parent;
ScaleX = 1;
ScaleY = 1;
}
override public String ToString () {
return Name;
}
}
}

View File

@ -0,0 +1,542 @@
/*
* Copyright (c) 2012 Calvin Rien
*
* Based on the JSON parser by Patrick van Bergen
* http://techblog.procurios.nl/k/618/news/view/14605/14863/How-do-I-write-my-own-parser-for-JSON.html
*
* Simplified it so that it doesn't throw exceptions
* and can be used in Unity iPhone with maximum code stripping.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Globalization;
namespace Spine
{
// Example usage:
//
// using UnityEngine;
// using System.Collections;
// using System.Collections.Generic;
// using MiniJSON;
//
// public class MiniJSONTest : MonoBehaviour {
// void Start () {
// var jsonString = "{ \"array\": [1.44,2,3], " +
// "\"object\": {\"key1\":\"value1\", \"key2\":256}, " +
// "\"string\": \"The quick brown fox \\\"jumps\\\" over the lazy dog \", " +
// "\"unicode\": \"\\u3041 Men\u00fa sesi\u00f3n\", " +
// "\"int\": 65536, " +
// "\"float\": 3.1415926, " +
// "\"bool\": true, " +
// "\"null\": null }";
//
// var dict = Json.Deserialize(jsonString) as Dictionary<string,object>;
//
// Debug.Log("deserialized: " + dict.GetType());
// Debug.Log("dict['array'][0]: " + ((List<object>) dict["array"])[0]);
// Debug.Log("dict['string']: " + (string) dict["string"]);
// Debug.Log("dict['float']: " + (float) dict["float"]);
// Debug.Log("dict['int']: " + (long) dict["int"]); // ints come out as longs
// Debug.Log("dict['unicode']: " + (string) dict["unicode"]);
//
// var str = Json.Serialize(dict);
//
// Debug.Log("serialized: " + str);
// }
// }
/// <summary>
/// This class encodes and decodes JSON strings.
/// Spec. details, see http://www.json.org/
///
/// JSON uses Arrays and Objects. These correspond here to the datatypes IList and IDictionary.
/// All numbers are parsed to floats.
/// </summary>
public static class Json {
/// <summary>
/// Parses the string json into a value
/// </summary>
/// <param name="json">A JSON string.</param>
/// <returns>An List&lt;object&gt;, a Dictionary&lt;string, object&gt;, a float, an integer,a string, null, true, or false</returns>
public static object Deserialize (TextReader json) {
if (json == null) {
return null;
}
return Parser.Parse(json);
}
sealed class Parser : IDisposable {
const string WHITE_SPACE = " \t\n\r";
const string WORD_BREAK = " \t\n\r{}[],:\"";
enum TOKEN {
NONE,
CURLY_OPEN,
CURLY_CLOSE,
SQUARED_OPEN,
SQUARED_CLOSE,
COLON,
COMMA,
STRING,
NUMBER,
TRUE,
FALSE,
NULL
};
TextReader json;
Parser(TextReader reader) {
json = reader;
}
public static object Parse (TextReader reader) {
using (var instance = new Parser(reader)) {
return instance.ParseValue();
}
}
public void Dispose() {
json.Dispose();
json = null;
}
Dictionary<string, object> ParseObject() {
Dictionary<string, object> table = new Dictionary<string, object>();
// ditch opening brace
json.Read();
// {
while (true) {
switch (NextToken) {
case TOKEN.NONE:
return null;
case TOKEN.COMMA:
continue;
case TOKEN.CURLY_CLOSE:
return table;
default:
// name
string name = ParseString();
if (name == null) {
return null;
}
// :
if (NextToken != TOKEN.COLON) {
return null;
}
// ditch the colon
json.Read();
// value
table[name] = ParseValue();
break;
}
}
}
List<object> ParseArray() {
List<object> array = new List<object>();
// ditch opening bracket
json.Read();
// [
var parsing = true;
while (parsing) {
TOKEN nextToken = NextToken;
switch (nextToken) {
case TOKEN.NONE:
return null;
case TOKEN.COMMA:
continue;
case TOKEN.SQUARED_CLOSE:
parsing = false;
break;
default:
object value = ParseByToken(nextToken);
array.Add(value);
break;
}
}
return array;
}
object ParseValue() {
TOKEN nextToken = NextToken;
return ParseByToken(nextToken);
}
object ParseByToken(TOKEN token) {
switch (token) {
case TOKEN.STRING:
return ParseString();
case TOKEN.NUMBER:
return ParseNumber();
case TOKEN.CURLY_OPEN:
return ParseObject();
case TOKEN.SQUARED_OPEN:
return ParseArray();
case TOKEN.TRUE:
return true;
case TOKEN.FALSE:
return false;
case TOKEN.NULL:
return null;
default:
return null;
}
}
string ParseString() {
StringBuilder s = new StringBuilder();
char c;
// ditch opening quote
json.Read();
bool parsing = true;
while (parsing) {
if (json.Peek() == -1) {
parsing = false;
break;
}
c = NextChar;
switch (c) {
case '"':
parsing = false;
break;
case '\\':
if (json.Peek() == -1) {
parsing = false;
break;
}
c = NextChar;
switch (c) {
case '"':
case '\\':
case '/':
s.Append(c);
break;
case 'b':
s.Append('\b');
break;
case 'f':
s.Append('\f');
break;
case 'n':
s.Append('\n');
break;
case 'r':
s.Append('\r');
break;
case 't':
s.Append('\t');
break;
case 'u':
var hex = new StringBuilder();
for (int i=0; i< 4; i++) {
hex.Append(NextChar);
}
s.Append((char) Convert.ToInt32(hex.ToString(), 16));
break;
}
break;
default:
s.Append(c);
break;
}
}
return s.ToString();
}
object ParseNumber() {
string number = NextWord;
float parsedFloat;
float.TryParse(number, NumberStyles.Float, CultureInfo.InvariantCulture, out parsedFloat);
return parsedFloat;
}
void EatWhitespace() {
while (WHITE_SPACE.IndexOf(PeekChar) != -1) {
json.Read();
if (json.Peek() == -1) {
break;
}
}
}
char PeekChar {
get {
return Convert.ToChar(json.Peek());
}
}
char NextChar {
get {
return Convert.ToChar(json.Read());
}
}
string NextWord {
get {
StringBuilder word = new StringBuilder();
while (WORD_BREAK.IndexOf(PeekChar) == -1) {
word.Append(NextChar);
if (json.Peek() == -1) {
break;
}
}
return word.ToString();
}
}
TOKEN NextToken {
get {
EatWhitespace();
if (json.Peek() == -1) {
return TOKEN.NONE;
}
char c = PeekChar;
switch (c) {
case '{':
return TOKEN.CURLY_OPEN;
case '}':
json.Read();
return TOKEN.CURLY_CLOSE;
case '[':
return TOKEN.SQUARED_OPEN;
case ']':
json.Read();
return TOKEN.SQUARED_CLOSE;
case ',':
json.Read();
return TOKEN.COMMA;
case '"':
return TOKEN.STRING;
case ':':
return TOKEN.COLON;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '-':
return TOKEN.NUMBER;
}
string word = NextWord;
switch (word) {
case "false":
return TOKEN.FALSE;
case "true":
return TOKEN.TRUE;
case "null":
return TOKEN.NULL;
}
return TOKEN.NONE;
}
}
}
/// <summary>
/// Converts a IDictionary / IList object or a simple type (string, int, etc.) into a JSON string
/// </summary>
/// <param name="json">A Dictionary&lt;string, object&gt; / List&lt;object&gt;</param>
/// <returns>A JSON encoded string, or null if object 'json' is not serializable</returns>
public static string Serialize(object obj) {
return Serializer.Serialize(obj);
}
sealed class Serializer {
StringBuilder builder;
Serializer() {
builder = new StringBuilder();
}
public static string Serialize(object obj) {
var instance = new Serializer();
instance.SerializeValue(obj);
return instance.builder.ToString();
}
void SerializeValue(object value) {
IList asList;
IDictionary asDict;
string asStr;
if (value == null) {
builder.Append("null");
}
else if ((asStr = value as string) != null) {
SerializeString(asStr);
}
else if (value is bool) {
builder.Append(value.ToString().ToLower());
}
else if ((asList = value as IList) != null) {
SerializeArray(asList);
}
else if ((asDict = value as IDictionary) != null) {
SerializeObject(asDict);
}
else if (value is char) {
SerializeString(value.ToString());
}
else {
SerializeOther(value);
}
}
void SerializeObject(IDictionary obj) {
bool first = true;
builder.Append('{');
foreach (object e in obj.Keys) {
if (!first) {
builder.Append(',');
}
SerializeString(e.ToString());
builder.Append(':');
SerializeValue(obj[e]);
first = false;
}
builder.Append('}');
}
void SerializeArray(IList anArray) {
builder.Append('[');
bool first = true;
foreach (object obj in anArray) {
if (!first) {
builder.Append(',');
}
SerializeValue(obj);
first = false;
}
builder.Append(']');
}
void SerializeString(string str) {
builder.Append('\"');
char[] charArray = str.ToCharArray();
foreach (var c in charArray) {
switch (c) {
case '"':
builder.Append("\\\"");
break;
case '\\':
builder.Append("\\\\");
break;
case '\b':
builder.Append("\\b");
break;
case '\f':
builder.Append("\\f");
break;
case '\n':
builder.Append("\\n");
break;
case '\r':
builder.Append("\\r");
break;
case '\t':
builder.Append("\\t");
break;
default:
int codepoint = Convert.ToInt32(c);
if ((codepoint >= 32) && (codepoint <= 126)) {
builder.Append(c);
}
else {
builder.Append("\\u" + Convert.ToString(codepoint, 16).PadLeft(4, '0'));
}
break;
}
}
builder.Append('\"');
}
void SerializeOther(object value) {
if (value is float
|| value is int
|| value is uint
|| value is long
|| value is float
|| value is sbyte
|| value is byte
|| value is short
|| value is ushort
|| value is ulong
|| value is decimal) {
builder.Append(value.ToString());
}
else {
SerializeString(value.ToString());
}
}
}
}
}

View File

@ -0,0 +1,196 @@
/*******************************************************************************
* Copyright (c) 2013, Esoteric Software
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
using System;
using System.Collections.Generic;
namespace Spine {
public class Skeleton {
public SkeletonData Data { get; private set; }
public List<Bone> Bones { get; private set; }
public List<Slot> Slots { get; private set; }
public List<Slot> DrawOrder { get; private set; }
public Skin Skin { get; set; }
public float R { get; set; }
public float G { get; set; }
public float B { get; set; }
public float A { get; set; }
public float Time { get; set; }
public bool FlipX { get; set; }
public bool FlipY { get; set; }
public Bone RootBone {
get {
return Bones.Count == 0 ? null : Bones[0];
}
}
public Skeleton (SkeletonData data) {
if (data == null) throw new ArgumentNullException("data cannot be null.");
Data = data;
Bones = new List<Bone>(Data.Bones.Count);
foreach (BoneData boneData in Data.Bones) {
Bone parent = boneData.Parent == null ? null : Bones[Data.Bones.IndexOf(boneData.Parent)];
Bones.Add(new Bone(boneData, parent));
}
Slots = new List<Slot>(Data.Slots.Count);
DrawOrder = new List<Slot>(Data.Slots.Count);
foreach (SlotData slotData in Data.Slots) {
Bone bone = Bones[Data.Bones.IndexOf(slotData.BoneData)];
Slot slot = new Slot(slotData, this, bone);
Slots.Add(slot);
DrawOrder.Add(slot);
}
R = 1;
G = 1;
B = 1;
A = 1;
}
/** Updates the world transform for each bone. */
public void UpdateWorldTransform () {
bool flipX = FlipX;
bool flipY = FlipY;
List<Bone> bones = Bones;
for (int i = 0, n = bones.Count; i < n; i++)
bones[i].UpdateWorldTransform(flipX, flipY);
}
/** Sets the bones and slots to their bind pose values. */
public void SetToBindPose () {
SetBonesToBindPose();
SetSlotsToBindPose();
}
public void SetBonesToBindPose () {
List<Bone> bones = this.Bones;
for (int i = 0, n = bones.Count; i < n; i++)
bones[i].SetToBindPose();
}
public void SetSlotsToBindPose () {
List<Slot> slots = this.Slots;
for (int i = 0, n = slots.Count; i < n; i++)
slots[i].SetToBindPose(i);
}
/** @return May be null. */
public Bone FindBone (String boneName) {
if (boneName == null) throw new ArgumentNullException("boneName cannot be null.");
List<Bone> bones = this.Bones;
for (int i = 0, n = bones.Count; i < n; i++) {
Bone bone = bones[i];
if (bone.Data.Name == boneName) return bone;
}
return null;
}
/** @return -1 if the bone was not found. */
public int FindBoneIndex (String boneName) {
if (boneName == null) throw new ArgumentNullException("boneName cannot be null.");
List<Bone> bones = this.Bones;
for (int i = 0, n = bones.Count; i < n; i++)
if (bones[i].Data.Name == boneName) return i;
return -1;
}
/** @return May be null. */
public Slot FindSlot (String slotName) {
if (slotName == null) throw new ArgumentNullException("slotName cannot be null.");
List<Slot> slots = this.Slots;
for (int i = 0, n = slots.Count; i < n; i++) {
Slot slot = slots[i];
if (slot.Data.Name == slotName) return slot;
}
return null;
}
/** @return -1 if the bone was not found. */
public int FindSlotIndex (String slotName) {
if (slotName == null) throw new ArgumentNullException("slotName cannot be null.");
List<Slot> slots = this.Slots;
for (int i = 0, n = slots.Count; i < n; i++)
if (slots[i].Data.Name.Equals(slotName)) return i;
return -1;
}
/** Sets a skin by name.
* @see #setSkin(Skin) */
public void SetSkin (String skinName) {
Skin skin = Data.FindSkin(skinName);
if (skin == null) throw new ArgumentException("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 was 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 ArgumentNullException("attachmentName cannot be null.");
if (Skin != null) {
Attachment attachment = Skin.GetAttachment(slotIndex, attachmentName);
if (attachment != null) return attachment;
}
if (Data.DefaultSkin != null) return Data.DefaultSkin.GetAttachment(slotIndex, attachmentName);
return null;
}
/** @param attachmentName May be null. */
public void SetAttachment (String slotName, String attachmentName) {
if (slotName == null) throw new ArgumentNullException("slotName cannot be null.");
List<Slot> slots = Slots;
for (int i = 0, n = slots.Count; i < n; i++) {
Slot slot = slots[i];
if (slot.Data.Name == slotName) {
Attachment attachment = null;
if (attachmentName != null) {
attachment = GetAttachment(i, attachmentName);
if (attachment == null) throw new ArgumentNullException("Attachment not found: " + attachmentName + ", for slot: " + slotName);
}
slot.Attachment = attachment;
return;
}
}
throw new Exception("Slot not found: " + slotName);
}
public void Update (float delta) {
Time += delta;
}
}
}

View File

@ -0,0 +1,135 @@
/*******************************************************************************
* Copyright (c) 2013, Esoteric Software
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
using System;
using System.Collections.Generic;
namespace Spine {
public class SkeletonData {
public String Name { get; set; }
public List<BoneData> Bones { get; private set; } // Ordered parents first.
public List<SlotData> Slots { get; private set; } // Bind pose draw order.
public List<Skin> Skins { get; private set; }
/** May be null. */
public Skin DefaultSkin;
public List<Animation> Animations { get; private set; }
public SkeletonData () {
Bones = new List<BoneData>();
Slots = new List<SlotData>();
Skins = new List<Skin>();
Animations = new List<Animation>();
}
// --- Bones.
public void AddBone (BoneData bone) {
if (bone == null) throw new ArgumentNullException("bone cannot be null.");
Bones.Add(bone);
}
/** @return May be null. */
public BoneData FindBone (String boneName) {
if (boneName == null) throw new ArgumentNullException("boneName cannot be null.");
for (int i = 0, n = Bones.Count; i < n; i++) {
BoneData bone = Bones[i];
if (bone.Name == boneName) return bone;
}
return null;
}
/** @return -1 if the bone was not found. */
public int FindBoneIndex (String boneName) {
if (boneName == null) throw new ArgumentNullException("boneName cannot be null.");
for (int i = 0, n = Bones.Count; i < n; i++)
if (Bones[i].Name == boneName) return i;
return -1;
}
// --- Slots.
public void AddSlot (SlotData slot) {
if (slot == null) throw new ArgumentNullException("slot cannot be null.");
Slots.Add(slot);
}
/** @return May be null. */
public SlotData FindSlot (String slotName) {
if (slotName == null) throw new ArgumentNullException("slotName cannot be null.");
for (int i = 0, n = Slots.Count; i < n; i++) {
SlotData slot = Slots[i];
if (slot.Name == slotName) return slot;
}
return null;
}
/** @return -1 if the bone was not found. */
public int FindSlotIndex (String slotName) {
if (slotName == null) throw new ArgumentNullException("slotName cannot be null.");
for (int i = 0, n = Slots.Count; i < n; i++)
if (Slots[i].Name == slotName) return i;
return -1;
}
// --- Skins.
public void AddSkin (Skin skin) {
if (skin == null) throw new ArgumentNullException("skin cannot be null.");
Skins.Add(skin);
}
/** @return May be null. */
public Skin FindSkin (String skinName) {
if (skinName == null) throw new ArgumentNullException("skinName cannot be null.");
foreach (Skin skin in Skins)
if (skin.Name == skinName) return skin;
return null;
}
// --- Animations.
public void AddAnimation (Animation animation) {
if (animation == null) throw new ArgumentNullException("animation cannot be null.");
Animations.Add(animation);
}
/** @return May be null. */
public Animation FindAnimation (String animationName) {
if (animationName == null) throw new ArgumentNullException("animationName cannot be null.");
for (int i = 0, n = Animations.Count; i < n; i++) {
Animation animation = Animations[i];
if (animation.Name == animationName) return animation;
}
return null;
}
// ---
override public String ToString () {
return Name != null ? Name : base.ToString();
}
}
}

View File

@ -0,0 +1,302 @@
/*******************************************************************************
* Copyright (c) 2013, Esoteric Software
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
using System;
using System.IO;
using System.Collections.Generic;
namespace Spine {
public class SkeletonJson {
static public String TIMELINE_SCALE = "scale";
static public String TIMELINE_ROTATE = "rotate";
static public String TIMELINE_TRANSLATE = "translate";
static public String TIMELINE_ATTACHMENT = "attachment";
static public String TIMELINE_COLOR = "color";
static public String ATTACHMENT_REGION = "region";
static public String ATTACHMENT_REGION_SEQUENCE = "regionSequence";
private AttachmentLoader attachmentLoader;
public float Scale { get; set; }
public SkeletonJson (Atlas atlas) {
this.attachmentLoader = new AtlasAttachmentLoader(atlas);
Scale = 1;
}
public SkeletonJson (AttachmentLoader attachmentLoader) {
if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader cannot be null.");
this.attachmentLoader = attachmentLoader;
Scale = 1;
}
public SkeletonData ReadSkeletonData (String path) {
using (StreamReader reader = new StreamReader(path)) {
SkeletonData skeletonData = ReadSkeletonData(reader);
skeletonData.Name = Path.GetFileNameWithoutExtension(path);
return skeletonData;
}
}
public SkeletonData ReadSkeletonData (TextReader reader) {
if (reader == null) throw new ArgumentNullException("reader cannot be null.");
SkeletonData skeletonData = new SkeletonData();
var root = Json.Deserialize(reader) as Dictionary<String, Object>;
if (root == null) throw new Exception("Invalid JSON.");
// Bones.
foreach (Dictionary<String, Object> boneMap in (List<Object>)root["bones"]) {
BoneData parent = null;
if (boneMap.ContainsKey("parent")) {
parent = skeletonData.FindBone((String)boneMap["parent"]);
if (parent == null)
throw new Exception("Parent bone not found: " + boneMap["parent"]);
}
BoneData boneData = new BoneData((String)boneMap["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.
if (root.ContainsKey("slots")) {
var slots = (List<Object>)root["slots"];
foreach (Dictionary<String, Object> slotMap in (List<Object>)slots) {
String slotName = (String)slotMap["name"];
String boneName = (String)slotMap["bone"];
BoneData boneData = skeletonData.FindBone(boneName);
if (boneData == null)
throw new Exception("Slot bone not found: " + boneName);
SlotData slotData = new SlotData(slotName, boneData);
if (slotMap.ContainsKey("color")) {
String color = (String)slotMap["color"];
slotData.R = ToColor(color, 0);
slotData.G = ToColor(color, 1);
slotData.B = ToColor(color, 2);
slotData.A = ToColor(color, 3);
}
if (slotMap.ContainsKey("attachment"))
slotData.AttachmentName = (String)slotMap["attachment"];
skeletonData.AddSlot(slotData);
}
}
// Skins.
if (root.ContainsKey("skins")) {
var skinMap = (Dictionary<String, Object>)root["skins"];
foreach (KeyValuePair<String, Object> entry in skinMap) {
Skin skin = new Skin(entry.Key);
foreach (KeyValuePair<String, Object> slotEntry in (Dictionary<String, Object>)entry.Value) {
int slotIndex = skeletonData.FindSlotIndex(slotEntry.Key);
foreach (KeyValuePair<String, Object> attachmentEntry in ((Dictionary<String, Object>)slotEntry.Value)) {
Attachment attachment = ReadAttachment(skin, attachmentEntry.Key, (Dictionary<String, Object>)attachmentEntry.Value);
skin.AddAttachment(slotIndex, attachmentEntry.Key, attachment);
}
}
skeletonData.AddSkin(skin);
if (skin.Name == "default")
skeletonData.DefaultSkin = skin;
}
}
// Animations.
if (root.ContainsKey("animations")) {
var animationMap = (Dictionary<String, Object>)root["animations"];
foreach (KeyValuePair<String, Object> entry in animationMap)
ReadAnimation(entry.Key, (Dictionary<String, Object>)entry.Value, skeletonData);
}
skeletonData.Bones.TrimExcess();
skeletonData.Slots.TrimExcess();
skeletonData.Skins.TrimExcess();
skeletonData.Animations.TrimExcess();
return skeletonData;
}
private Attachment ReadAttachment (Skin skin, String name, Dictionary<String, Object> map) {
if (map.ContainsKey("name"))
name = (String)map["name"];
AttachmentType type = AttachmentType.region;
if (map.ContainsKey("type"))
type = (AttachmentType)Enum.Parse(typeof(AttachmentType), (String)map["type"], false);
Attachment attachment = attachmentLoader.NewAttachment(skin, type, name);
if (attachment is RegionAttachment) {
RegionAttachment regionAttachment = (RegionAttachment)attachment;
regionAttachment.X = GetFloat(map, "x", 0) * Scale;
regionAttachment.Y = GetFloat(map, "y", 0) * Scale;
regionAttachment.ScaleX = GetFloat(map, "scaleX", 1);
regionAttachment.ScaleY = GetFloat(map, "scaleY", 1);
regionAttachment.Rotation = GetFloat(map, "rotation", 0);
regionAttachment.Width = GetFloat(map, "width", 32) * Scale;
regionAttachment.Height = GetFloat(map, "height", 32) * Scale;
regionAttachment.UpdateOffset();
}
return attachment;
}
private float GetFloat (Dictionary<String, Object> map, String name, float defaultValue) {
if (!map.ContainsKey(name))
return (float)defaultValue;
return (float)map[name];
}
public static float ToColor (String hexString, int colorIndex) {
if (hexString.Length != 8)
throw new ArgumentException("Color hexidecimal length must be 8, recieved: " + hexString);
return Convert.ToInt32(hexString.Substring(colorIndex * 2, 2), 16) / (float)255;
}
private void ReadAnimation (String name, Dictionary<String, Object> map, SkeletonData skeletonData) {
var timelines = new List<Timeline>();
float duration = 0;
if (map.ContainsKey("bones")) {
var bonesMap = (Dictionary<String, Object>)map["bones"];
foreach (KeyValuePair<String, Object> entry in bonesMap) {
String boneName = entry.Key;
int boneIndex = skeletonData.FindBoneIndex(boneName);
if (boneIndex == -1)
throw new Exception("Bone not found: " + boneName);
var timelineMap = (Dictionary<String, Object>)entry.Value;
foreach (KeyValuePair<String, Object> timelineEntry in timelineMap) {
var values = (List<Object>)timelineEntry.Value;
String timelineName = (String)timelineEntry.Key;
if (timelineName.Equals(TIMELINE_ROTATE)) {
RotateTimeline timeline = new RotateTimeline(values.Count);
timeline.BoneIndex = boneIndex;
int frameIndex = 0;
foreach (Dictionary<String, Object> valueMap in values) {
float time = (float)valueMap["time"];
timeline.SetFrame(frameIndex, time, (float)valueMap["angle"]);
ReadCurve(timeline, frameIndex, valueMap);
frameIndex++;
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.Frames[timeline.FrameCount * 2 - 2]);
} else if (timelineName.Equals(TIMELINE_TRANSLATE) || timelineName.Equals(TIMELINE_SCALE)) {
TranslateTimeline timeline;
float timelineScale = 1;
if (timelineName.Equals(TIMELINE_SCALE))
timeline = new ScaleTimeline(values.Count);
else {
timeline = new TranslateTimeline(values.Count);
timelineScale = Scale;
}
timeline.BoneIndex = boneIndex;
int frameIndex = 0;
foreach (Dictionary<String, Object> valueMap in values) {
float time = (float)valueMap["time"];
float x = valueMap.ContainsKey("x") ? (float)valueMap["x"] : 0;
float y = valueMap.ContainsKey("y") ? (float)valueMap["y"] : 0;
timeline.SetFrame(frameIndex, time, (float)x * timelineScale, (float)y * timelineScale);
ReadCurve(timeline, frameIndex, valueMap);
frameIndex++;
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.Frames[timeline.FrameCount * 3 - 3]);
} else
throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")");
}
}
}
if (map.ContainsKey("slots")) {
var slotsMap = (Dictionary<String, Object>)map["slots"];
foreach (KeyValuePair<String, Object> entry in slotsMap) {
String slotName = entry.Key;
int slotIndex = skeletonData.FindSlotIndex(slotName);
var timelineMap = (Dictionary<String, Object>)entry.Value;
foreach (KeyValuePair<String, Object> timelineEntry in timelineMap) {
var values = (List<Object>)timelineEntry.Value;
String timelineName = (String)timelineEntry.Key;
if (timelineName.Equals(TIMELINE_COLOR)) {
ColorTimeline timeline = new ColorTimeline(values.Count);
timeline.SlotIndex = slotIndex;
int frameIndex = 0;
foreach (Dictionary<String, Object> valueMap in values) {
float time = (float)valueMap["time"];
String c = (String)valueMap["color"];
timeline.setFrame(frameIndex, time, ToColor(c, 0), ToColor(c, 1), ToColor(c, 2), ToColor(c, 3));
ReadCurve(timeline, frameIndex, valueMap);
frameIndex++;
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.Frames[timeline.FrameCount * 5 - 5]);
} else if (timelineName.Equals(TIMELINE_ATTACHMENT)) {
AttachmentTimeline timeline = new AttachmentTimeline(values.Count);
timeline.SlotIndex = slotIndex;
int frameIndex = 0;
foreach (Dictionary<String, Object> valueMap in values) {
float time = (float)valueMap["time"];
timeline.setFrame(frameIndex++, time, (String)valueMap["name"]);
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.Frames[timeline.FrameCount - 1]);
} else
throw new Exception("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")");
}
}
}
timelines.TrimExcess();
skeletonData.AddAnimation(new Animation(name, timelines, duration));
}
private void ReadCurve (CurveTimeline timeline, int frameIndex, Dictionary<String, Object> valueMap) {
if (!valueMap.ContainsKey("curve"))
return;
Object curveObject = valueMap["curve"];
if (curveObject.Equals("stepped"))
timeline.SetStepped(frameIndex);
else if (curveObject is List<Object>) {
List<Object> curve = (List<Object>)curveObject;
timeline.SetCurve(frameIndex, (float)curve[0], (float)curve[1], (float)curve[2], (float)curve[3]);
}
}
}
}

View File

@ -0,0 +1,80 @@
/*******************************************************************************
* Copyright (c) 2013, Esoteric Software
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
using System;
using System.Collections.Generic;
namespace Spine {
/** Stores attachments by slot index and attachment name. */
public class Skin {
public String Name { get; private set; }
private Dictionary<KeyValuePair<int, String>, Attachment> attachments = new Dictionary<KeyValuePair<int, String>, Attachment>();
public Skin (String name) {
if (name == null) throw new ArgumentNullException("name cannot be null.");
Name = name;
}
public void AddAttachment (int slotIndex, String name, Attachment attachment) {
if (attachment == null) throw new ArgumentNullException("attachment cannot be null.");
attachments.Add(new KeyValuePair<int, String>(slotIndex, name), attachment);
}
/** @return May be null. */
public Attachment GetAttachment (int slotIndex, String name) {
Attachment attachment;
attachments.TryGetValue(new KeyValuePair<int, String>(slotIndex, name), out attachment);
return attachment;
}
public void FindNamesForSlot (int slotIndex, List<String> names) {
if (names == null) throw new ArgumentNullException("names cannot be null.");
foreach (KeyValuePair<int, String> key in attachments.Keys)
if (key.Key == slotIndex) names.Add(key.Value);
}
public void FindAttachmentsForSlot (int slotIndex, List<Attachment> attachments) {
if (attachments == null) throw new ArgumentNullException("attachments cannot be null.");
foreach (KeyValuePair<KeyValuePair<int, String>, Attachment> entry in this.attachments)
if (entry.Key.Key == slotIndex) attachments.Add(entry.Value);
}
override public String ToString () {
return Name;
}
/** Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached. */
internal void AttachAll (Skeleton skeleton, Skin oldSkin) {
foreach (KeyValuePair<KeyValuePair<int, String>, Attachment> entry in oldSkin.attachments) {
int slotIndex = entry.Key.Key;
Slot slot = skeleton.Slots[slotIndex];
if (slot.Attachment == entry.Value) {
Attachment attachment = GetAttachment(slotIndex, entry.Key.Value);
if (attachment != null) slot.Attachment = attachment;
}
}
}
}
}

View File

@ -0,0 +1,86 @@
/*******************************************************************************
* Copyright (c) 2013, Esoteric Software
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
using System;
namespace Spine {
public class Slot {
public SlotData Data { get; private set; }
public Bone Bone { get; private set; }
public Skeleton Skeleton { get; private set; }
public float R { get; set; }
public float G { get; set; }
public float B { get; set; }
public float A { get; set; }
/** May be null. */
private Attachment attachment;
public Attachment Attachment {
get {
return attachment;
}
set {
attachment = value;
attachmentTime = Skeleton.Time;
}
}
private float attachmentTime;
public float AttachmentTime {
get {
return Skeleton.Time - attachmentTime;
}
set {
attachmentTime = Skeleton.Time - value;
}
}
public Slot (SlotData data, Skeleton skeleton, Bone bone) {
if (data == null) throw new ArgumentNullException("data cannot be null.");
if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null.");
if (bone == null) throw new ArgumentNullException("bone cannot be null.");
Data = data;
Skeleton = skeleton;
Bone = bone;
SetToBindPose();
}
internal void SetToBindPose (int slotIndex) {
R = Data.R;
G = Data.G;
B = Data.B;
A = Data.A;
Attachment = Data.AttachmentName == null ? null : Skeleton.GetAttachment(slotIndex, Data.AttachmentName);
}
public void SetToBindPose () {
SetToBindPose(Skeleton.Data.Slots.IndexOf(Data));
}
override public String ToString () {
return Data.Name;
}
}
}

View File

@ -0,0 +1,54 @@
/*******************************************************************************
* Copyright (c) 2013, Esoteric Software
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
using System;
namespace Spine {
public class SlotData {
public String Name { get; private set; }
public BoneData BoneData { get; private set; }
public float R { get; set; }
public float G { get; set; }
public float B { get; set; }
public float A { get; set; }
/** @param attachmentName May be null. */
public String AttachmentName { get; set; }
public SlotData (String name, BoneData boneData) {
if (name == null) throw new ArgumentNullException("name cannot be null.");
if (boneData == null) throw new ArgumentNullException("boneData cannot be null.");
Name = name;
BoneData = boneData;
R = 1;
G = 1;
B = 1;
A = 1;
}
override public String ToString () {
return Name;
}
}
}