[csharp][monogame][unity][xna] Updated to 3.3.x (#623)

* [spine-csharp] Ported 3.3 changes of Animation and inner classes. Added MathUtils.Clamp

* [spine-csharp] Ported 3.3 changes of AnimationStateData. Updated .gitignore to exclude .meta files from spine-csharp

* [spine-csharp] Ported 3.3 changes of Bone

* [spine-csharp] Ported 3.3 changes of BoneData

* [spine-csharp] Ported 3.3 changes of Event

* [spine-csharp] Ported 3.3 changes to IKConstraint. Also got rid of the hideous labeled break. Replaced with while and sprinkled continues and break :D

* [spine-csharp] Mario is not good with labeled breaks. Fixed with goto

* [spine-csharp] Ported more 3.3 changes, only SkeletonJson and SkeletonBinary left. Unity runtime also needs updating with new changes

* [spine-csharp] fixed compilation errors initially not reported by Mono CS

* Minor clean up.

Every space has its place.™

* Match csharp 3.3 refactorings.

* [Unity] Minor cleanup in SkeletonBaker.

* Better exception messages.

* Removed extra usings. Matched whitespace style.

* Fixed Bone.cs method PascalCase and xml documentation.

* Fixed Exception ctor arguments.

* Fixed single bone IK mixing.

Matched libgdx commit:
e0ee18a088

* [spine-csharp] Port of 3.3 changes to SkeletonJson, incomplete

* [Unity] Ragdoll: account for shear and new constraints.

* [spine-csharp] Ported 3.3 changes to SkeletonJson

* [spine-csharp] Ported 3.3 changes to SkeletonBinary. Time for testing and debugging

* [spine-csharp] Fixed up XNA runtime

* Added simple example data in new exports/ folder

* [spine-csharp] Fixed bug in Slot construtor, wasn't assigning index. Fixed bug in CurveTimeline, argument guard was wrong. Fixed bugs in SkeletonJson, indexing to get duration of a timeline was wrong. Added simple example to spine-xna for easier debugging

* [spine-csharp] Fixed porting bug in ColorTimeline#apply, indexing was wrong

* [spine-csharp] Fixed SkeletonJson#ReadVertices, calling resize on temp lists was wrong. Updated goblin sample in spine-xna, fixed mesh attachment rendering in spine-xna.

* [spine-xna] Added binary exports, modified XNA game

* [spine-xna] Added tank example

* Some cleanup. Relabeled generic todos.

* Prevent SpineEditorUtilities from orphaning failed instantiations.

* Ignore PathAttachment when checking for required atlas regions.

* [spine-csharp] the great spaces to tabs battle

* [spine-csharp] Fixed inherit deform.

* [spine-csharp] Match Skeleton.java properties and stuff.

* [spine-csharp] Fixed enums, all upper cased now, using Enum.ParseType in case-insensitive mode

* [csharp] Ported 206e7f983c4df4d27fee6cac05d152eb2295c8b0 to csharp runtime. Fixes attachment keys on different tracks

* [csharp] Updated README.md

* [xna] [monogame] [unity] Updated README.md

* Revert "[csharp] Ported 206e7f983c4df4d27fee6cac05d152eb2295c8b0 to csharp runtime. Fixes attachment keys on different tracks"

This reverts commit 175216868dd00b4ae31cc717022242308c150f6a.

* [csharp] Fix to AttachmentTimeline#apply, fix for the fix for the fix for the revert for the fix

* [csharp] Matched and fixed more comments, summaries, exception messages and formatting.

* [csharp] Fix deformed weighted vertices condition + match libgdx closer.

* [csharp] Use internal ExposedList array for critical methods.

* [csharp] SkeletonJson and SkeletonBinary minor formatting and fixes.

* [unity] Match changes and fixes in spine-csharp 3.3 + better editor messages.

* [unity] Updated sample scenes and files.

* [csharp] Some formatting got left behind.

* [unity] New readme links + Removed redundant info.

* [exports] Remove dummy project.
This commit is contained in:
John 2016-06-30 04:29:33 +08:00 committed by GitHub
parent 20460fbdaa
commit 1c19365325
169 changed files with 9365 additions and 3131 deletions

3
.gitignore vendored
View File

@ -12,6 +12,7 @@ target
*.user *.user
.DS_Store .DS_Store
.idea/ .idea/
build/ build/
@ -39,7 +40,9 @@ spine-cocos2d-iphone/spine-cocos2d-iphone-ios.xcodeproj/project.xcworkspace/xcsh
spine-csharp/bin spine-csharp/bin
spine-csharp/obj spine-csharp/obj
spine-csharp/src/*.meta
spine-csharp/src/*.cs.meta spine-csharp/src/*.cs.meta
spine-csharp/src/Attachments/*.cs.meta
spine-monogame/xamarinstudio-ios/src/bin spine-monogame/xamarinstudio-ios/src/bin
spine-monogame/xamarinstudio-ios/src/obj spine-monogame/xamarinstudio-ios/src/obj

View File

@ -10,14 +10,14 @@ The Spine Runtimes are developed with the intent to be used with data exported f
## Spine version ## Spine version
spine-csharp works with data exported from Spine 3.2.01. Updating spine-csharp to [v3.3](https://github.com/EsotericSoftware/spine-runtimes/issues/613) is in progress. spine-csharp works with data exported from Spine 3.3.07.
spine-csharp supports all Spine features. spine-csharp supports all Spine features.
## Setup ## Setup
1. Download the Spine Runtimes source using [git](https://help.github.com/articles/set-up-git) or by downloading it [as a zip](https://github.com/EsotericSoftware/spine-runtimes/archive/master.zip). 1. Download the Spine Runtimes source using [git](https://help.github.com/articles/set-up-git) or by downloading it [as a zip](https://github.com/EsotericSoftware/spine-runtimes/archive/master.zip).
1. Open the `spine-csharp.sln` Visual C# 2010 Express project file. 1. Open the `spine-csharp.sln` Visual Studio 2015 Community project file.
Alternatively, the contents of the `spine-csharp/src` directory can be copied into your project. Alternatively, the contents of the `spine-csharp/src` directory can be copied into your project.

View File

@ -70,10 +70,10 @@
<Compile Include="src\Attachments\AttachmentLoader.cs" /> <Compile Include="src\Attachments\AttachmentLoader.cs" />
<Compile Include="src\Attachments\AttachmentType.cs" /> <Compile Include="src\Attachments\AttachmentType.cs" />
<Compile Include="src\Attachments\BoundingBoxAttachment.cs" /> <Compile Include="src\Attachments\BoundingBoxAttachment.cs" />
<Compile Include="src\Attachments\IFfdAttachment.cs" />
<Compile Include="src\Attachments\MeshAttachment.cs" /> <Compile Include="src\Attachments\MeshAttachment.cs" />
<Compile Include="src\Attachments\PathAttachment.cs" />
<Compile Include="src\Attachments\RegionAttachment.cs" /> <Compile Include="src\Attachments\RegionAttachment.cs" />
<Compile Include="src\Attachments\WeightedMeshAttachment.cs" /> <Compile Include="src\Attachments\VertexAttachment.cs" />
<Compile Include="src\BlendMode.cs"> <Compile Include="src\BlendMode.cs">
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>
@ -105,6 +105,8 @@
<Compile Include="src\MathUtils.cs"> <Compile Include="src\MathUtils.cs">
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>
<Compile Include="src\PathConstraint.cs" />
<Compile Include="src\PathConstraintData.cs" />
<Compile Include="src\Skeleton.cs"> <Compile Include="src\Skeleton.cs">
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>

View File

@ -43,8 +43,8 @@ namespace Spine {
public float Duration { get { return duration; } set { duration = value; } } public float Duration { get { return duration; } set { duration = value; } }
public Animation (String name, ExposedList<Timeline> timelines, float duration) { public Animation (String name, ExposedList<Timeline> timelines, float duration) {
if (name == null) throw new ArgumentNullException("name cannot be null."); if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
if (timelines == null) throw new ArgumentNullException("timelines cannot be null."); if (timelines == null) throw new ArgumentNullException("timelines", "timelines cannot be null.");
this.name = name; this.name = name;
this.timelines = timelines; this.timelines = timelines;
this.duration = duration; this.duration = duration;
@ -52,9 +52,9 @@ namespace Spine {
/// <summary>Poses the skeleton at the specified time for this animation.</summary> /// <summary>Poses the skeleton at the specified time for this animation.</summary>
/// <param name="lastTime">The last time the animation was applied.</param> /// <param name="lastTime">The last time the animation was applied.</param>
/// <param name="events">Any triggered events are added.</param> /// <param name="events">Any triggered events are added. May be null.</param>
public void Apply (Skeleton skeleton, float lastTime, float time, bool loop, ExposedList<Event> events) { public void Apply (Skeleton skeleton, float lastTime, float time, bool loop, ExposedList<Event> events) {
if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null.");
if (loop && duration != 0) { if (loop && duration != 0) {
time %= duration; time %= duration;
@ -68,10 +68,10 @@ namespace Spine {
/// <summary>Poses the skeleton at the specified time for this animation mixed with the current pose.</summary> /// <summary>Poses the skeleton at the specified time for this animation mixed with the current pose.</summary>
/// <param name="lastTime">The last time the animation was applied.</param> /// <param name="lastTime">The last time the animation was applied.</param>
/// <param name="events">Any triggered events are added.</param> /// <param name="events">Any triggered events are added. May be null.</param>
/// <param name="alpha">The amount of this animation that affects the current pose.</param> /// <param name="alpha">The amount of this animation that affects the current pose.</param>
public void Mix (Skeleton skeleton, float lastTime, float time, bool loop, ExposedList<Event> events, float alpha) { public void Mix (Skeleton skeleton, float lastTime, float time, bool loop, ExposedList<Event> events, float alpha) {
if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null.");
if (loop && duration != 0) { if (loop && duration != 0) {
time %= duration; time %= duration;
@ -131,12 +131,13 @@ namespace Spine {
/// <summary>Base class for frames that use an interpolation bezier curve.</summary> /// <summary>Base class for frames that use an interpolation bezier curve.</summary>
abstract public class CurveTimeline : Timeline { abstract public class CurveTimeline : Timeline {
protected const float LINEAR = 0, STEPPED = 1, BEZIER = 2; protected const float LINEAR = 0, STEPPED = 1, BEZIER = 2;
protected const int BEZIER_SEGMENTS = 10, BEZIER_SIZE = BEZIER_SEGMENTS * 2 - 1; protected const int BEZIER_SIZE = 10 * 2 - 1;
private float[] curves; // type, x, y, ... private float[] curves; // type, x, y, ...
public int FrameCount { get { return curves.Length / BEZIER_SIZE + 1; } } public int FrameCount { get { return curves.Length / BEZIER_SIZE + 1; } }
public CurveTimeline (int frameCount) { public CurveTimeline (int frameCount) {
if (frameCount <= 0) throw new ArgumentException("frameCount must be > 0: " + frameCount, "frameCount");
curves = new float[(frameCount - 1) * BEZIER_SIZE]; curves = new float[(frameCount - 1) * BEZIER_SIZE];
} }
@ -154,12 +155,10 @@ namespace Spine {
/// cx1 and cx2 are from 0 to 1, representing the percent of time between the two keyframes. cy1 and cy2 are the percent of /// 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.</summary> /// the difference between the keyframe's values.</summary>
public void SetCurve (int frameIndex, float cx1, float cy1, float cx2, float cy2) { public void SetCurve (int frameIndex, float cx1, float cy1, float cx2, float cy2) {
float subdiv1 = 1f / BEZIER_SEGMENTS, subdiv2 = subdiv1 * subdiv1, subdiv3 = subdiv2 * subdiv1; float tmpx = (-cx1 * 2 + cx2) * 0.03f, tmpy = (-cy1 * 2 + cy2) * 0.03f;
float pre1 = 3 * subdiv1, pre2 = 3 * subdiv2, pre4 = 6 * subdiv2, pre5 = 6 * subdiv3; float dddfx = ((cx1 - cx2) * 3 + 1) * 0.006f, dddfy = ((cy1 - cy2) * 3 + 1) * 0.006f;
float tmp1x = -cx1 * 2 + cx2, tmp1y = -cy1 * 2 + cy2, tmp2x = (cx1 - cx2) * 3 + 1, tmp2y = (cy1 - cy2) * 3 + 1; float ddfx = tmpx * 2 + dddfx, ddfy = tmpy * 2 + dddfy;
float dfx = cx1 * pre1 + tmp1x * pre2 + tmp2x * subdiv3, dfy = cy1 * pre1 + tmp1y * pre2 + tmp2y * subdiv3; float dfx = cx1 * 0.3f + tmpx + dddfx * 0.16666667f, dfy = cy1 * 0.3f + tmpy + dddfy * 0.16666667f;
float ddfx = tmp1x * pre4 + tmp2x * pre5, ddfy = tmp1y * pre4 + tmp2y * pre5;
float dddfx = tmp2x * pre5, dddfy = tmp2y * pre5;
int i = frameIndex * BEZIER_SIZE; int i = frameIndex * BEZIER_SIZE;
float[] curves = this.curves; float[] curves = this.curves;
@ -179,6 +178,7 @@ namespace Spine {
} }
public float GetCurvePercent (int frameIndex, float percent) { public float GetCurvePercent (int frameIndex, float percent) {
percent = MathUtils.Clamp (percent, 0, 1);
float[] curves = this.curves; float[] curves = this.curves;
int i = frameIndex * BEZIER_SIZE; int i = frameIndex * BEZIER_SIZE;
float type = curves[i]; float type = curves[i];
@ -209,8 +209,9 @@ namespace Spine {
} }
public class RotateTimeline : CurveTimeline { public class RotateTimeline : CurveTimeline {
internal const int PREV_TIME = -2; public const int ENTRIES = 2;
internal const int VALUE = 1; internal const int PREV_TIME = -2, PREV_ROTATION = -1;
internal const int ROTATION = 1;
internal int boneIndex; internal int boneIndex;
internal float[] frames; internal float[] frames;
@ -224,10 +225,10 @@ namespace Spine {
} }
/// <summary>Sets the time and value of the specified keyframe.</summary> /// <summary>Sets the time and value of the specified keyframe.</summary>
public void SetFrame (int frameIndex, float time, float angle) { public void SetFrame (int frameIndex, float time, float degrees) {
frameIndex *= 2; frameIndex <<= 1;
frames[frameIndex] = time; frames[frameIndex] = time;
frames[frameIndex + 1] = angle; frames[frameIndex + ROTATION] = degrees;
} }
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) { override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
@ -238,8 +239,8 @@ namespace Spine {
float amount; float amount;
if (time >= frames[frames.Length - 2]) { // Time is after last frame. if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
amount = bone.data.rotation + frames[frames.Length - 1] - bone.rotation; amount = bone.data.rotation + frames[frames.Length + PREV_ROTATION] - bone.rotation;
while (amount > 180) while (amount > 180)
amount -= 360; amount -= 360;
while (amount < -180) while (amount < -180)
@ -249,18 +250,17 @@ namespace Spine {
} }
// Interpolate between the previous frame and the current frame. // Interpolate between the previous frame and the current frame.
int frame = Animation.binarySearch(frames, time, 2); int frame = Animation.binarySearch(frames, time, ENTRIES);
float prevFrameValue = frames[frame - 1]; float prevRotation = frames[frame + PREV_ROTATION];
float frameTime = frames[frame]; float frameTime = frames[frame];
float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime); float percent = GetCurvePercent((frame >> 1) - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
percent = GetCurvePercent((frame >> 1) - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
amount = frames[frame + VALUE] - prevFrameValue; amount = frames[frame + ROTATION] - prevRotation;
while (amount > 180) while (amount > 180)
amount -= 360; amount -= 360;
while (amount < -180) while (amount < -180)
amount += 360; amount += 360;
amount = bone.data.rotation + (prevFrameValue + amount * percent) - bone.rotation; amount = bone.data.rotation + (prevRotation + amount * percent) - bone.rotation;
while (amount > 180) while (amount > 180)
amount -= 360; amount -= 360;
while (amount < -180) while (amount < -180)
@ -270,9 +270,9 @@ namespace Spine {
} }
public class TranslateTimeline : CurveTimeline { public class TranslateTimeline : CurveTimeline {
protected const int PREV_TIME = -3; public const int ENTRIES = 3;
protected const int X = 1; protected const int PREV_TIME = -3, PREV_X = -2, PREV_Y = -1;
protected const int Y = 2; protected const int X = 1, Y = 2;
internal int boneIndex; internal int boneIndex;
internal float[] frames; internal float[] frames;
@ -282,15 +282,15 @@ namespace Spine {
public TranslateTimeline (int frameCount) public TranslateTimeline (int frameCount)
: base(frameCount) { : base(frameCount) {
frames = new float[frameCount * 3]; frames = new float[frameCount * ENTRIES];
} }
/// <summary>Sets the time and value of the specified keyframe.</summary> /// <summary>Sets the time and value of the specified keyframe.</summary>
public void SetFrame (int frameIndex, float time, float x, float y) { public void SetFrame (int frameIndex, float time, float x, float y) {
frameIndex *= 3; frameIndex *= ENTRIES;
frames[frameIndex] = time; frames[frameIndex] = time;
frames[frameIndex + 1] = x; frames[frameIndex + X] = x;
frames[frameIndex + 2] = y; frames[frameIndex + Y] = y;
} }
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) { override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
@ -299,22 +299,21 @@ namespace Spine {
Bone bone = skeleton.bones.Items[boneIndex]; Bone bone = skeleton.bones.Items[boneIndex];
if (time >= frames[frames.Length - 3]) { // Time is after last frame. if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
bone.x += (bone.data.x + frames[frames.Length - 2] - bone.x) * alpha; bone.x += (bone.data.x + frames[frames.Length + PREV_X] - bone.x) * alpha;
bone.y += (bone.data.y + frames[frames.Length - 1] - bone.y) * alpha; bone.y += (bone.data.y + frames[frames.Length + PREV_Y] - bone.y) * alpha;
return; return;
} }
// Interpolate between the previous frame and the current frame. // Interpolate between the previous frame and the current frame.
int frame = Animation.binarySearch(frames, time, 3); int frame = Animation.binarySearch(frames, time, ENTRIES);
float prevFrameX = frames[frame - 2]; float prevX = frames[frame + PREV_X];
float prevFrameY = frames[frame - 1]; float prevY = frames[frame + PREV_Y];
float frameTime = frames[frame]; float frameTime = frames[frame];
float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime); float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
percent = GetCurvePercent(frame / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
bone.x += (bone.data.x + prevFrameX + (frames[frame + X] - prevFrameX) * percent - bone.x) * alpha; bone.x += (bone.data.x + prevX + (frames[frame + X] - prevX) * percent - bone.x) * alpha;
bone.y += (bone.data.y + prevFrameY + (frames[frame + Y] - prevFrameY) * percent - bone.y) * alpha; bone.y += (bone.data.y + prevY + (frames[frame + Y] - prevY) * percent - bone.y) * alpha;
} }
} }
@ -328,28 +327,27 @@ namespace Spine {
if (time < frames[0]) return; // Time is before first frame. if (time < frames[0]) return; // Time is before first frame.
Bone bone = skeleton.bones.Items[boneIndex]; Bone bone = skeleton.bones.Items[boneIndex];
if (time >= frames[frames.Length - 3]) { // Time is after last frame. if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
bone.scaleX += (bone.data.scaleX * frames[frames.Length - 2] - bone.scaleX) * alpha; bone.scaleX += (bone.data.scaleX * frames[frames.Length + PREV_X] - bone.scaleX) * alpha;
bone.scaleY += (bone.data.scaleY * frames[frames.Length - 1] - bone.scaleY) * alpha; bone.scaleY += (bone.data.scaleY * frames[frames.Length + PREV_Y] - bone.scaleY) * alpha;
return; return;
} }
// Interpolate between the previous frame and the current frame. // Interpolate between the previous frame and the current frame.
int frame = Animation.binarySearch(frames, time, 3); int frame = Animation.binarySearch(frames, time, ENTRIES);
float prevFrameX = frames[frame - 2]; float prevX = frames[frame + PREV_X];
float prevFrameY = frames[frame - 1]; float prevY = frames[frame + PREV_Y];
float frameTime = frames[frame]; float frameTime = frames[frame];
float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime); float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
percent = GetCurvePercent(frame / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
bone.scaleX += (bone.data.scaleX * (prevFrameX + (frames[frame + X] - prevFrameX) * percent) - bone.scaleX) * alpha; bone.scaleX += (bone.data.scaleX * (prevX + (frames[frame + X] - prevX) * percent) - bone.scaleX) * alpha;
bone.scaleY += (bone.data.scaleY * (prevFrameY + (frames[frame + Y] - prevFrameY) * percent) - bone.scaleY) * alpha; bone.scaleY += (bone.data.scaleY * (prevY + (frames[frame + Y] - prevY) * percent) - bone.scaleY) * alpha;
} }
} }
public class ShearTimeline : TranslateTimeline { public class ShearTimeline : TranslateTimeline {
public ShearTimeline (int frameCount) public ShearTimeline (int frameCount)
: base (frameCount) { : base(frameCount) {
} }
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) { override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
@ -357,31 +355,28 @@ namespace Spine {
if (time < frames[0]) return; // Time is before first frame. if (time < frames[0]) return; // Time is before first frame.
Bone bone = skeleton.bones.Items[boneIndex]; Bone bone = skeleton.bones.Items[boneIndex];
if (time >= frames[frames.Length - 3]) { // Time is after last frame. if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
bone.shearX += (bone.data.shearX + frames[frames.Length - 2] - bone.shearX) * alpha; bone.shearX += (bone.data.shearX + frames[frames.Length + PREV_X] - bone.shearX) * alpha;
bone.shearY += (bone.data.shearY + frames[frames.Length - 1] - bone.shearY) * alpha; bone.shearY += (bone.data.shearY + frames[frames.Length + PREV_Y] - bone.shearY) * alpha;
return; return;
} }
// Interpolate between the previous frame and the current frame. // Interpolate between the previous frame and the current frame.
int frame = Animation.binarySearch(frames, time, 3); int frame = Animation.binarySearch(frames, time, ENTRIES);
float prevFrameX = frames[frame - 2]; float prevX = frames[frame + PREV_X];
float prevFrameY = frames[frame - 1]; float prevY = frames[frame + PREV_Y];
float frameTime = frames[frame]; float frameTime = frames[frame];
float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime); float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
percent = GetCurvePercent(frame / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
bone.shearX += (bone.data.shearX + (prevFrameX + (frames[frame + X] - prevFrameX) * percent) - bone.shearX) * alpha; bone.shearX += (bone.data.shearX + (prevX + (frames[frame + X] - prevX) * percent) - bone.shearX) * alpha;
bone.shearY += (bone.data.shearY + (prevFrameY + (frames[frame + Y] - prevFrameY) * percent) - bone.shearY) * alpha; bone.shearY += (bone.data.shearY + (prevY + (frames[frame + Y] - prevY) * percent) - bone.shearY) * alpha;
} }
} }
public class ColorTimeline : CurveTimeline { public class ColorTimeline : CurveTimeline {
protected const int PREV_TIME = -5; public const int ENTRIES = 5;
protected const int R = 1; protected const int PREV_TIME = -5, PREV_R = -4, PREV_G = -3, PREV_B = -2, PREV_A = -1;
protected const int G = 2; protected const int R = 1, G = 2, B = 3, A = 4;
protected const int B = 3;
protected const int A = 4;
internal int slotIndex; internal int slotIndex;
internal float[] frames; internal float[] frames;
@ -391,17 +386,17 @@ namespace Spine {
public ColorTimeline (int frameCount) public ColorTimeline (int frameCount)
: base(frameCount) { : base(frameCount) {
frames = new float[frameCount * 5]; frames = new float[frameCount * ENTRIES];
} }
/// <summary>Sets the time and value of the specified keyframe.</summary> /// <summary>Sets the time and value of the specified keyframe.</summary>
public void SetFrame (int frameIndex, float time, float r, float g, float b, float a) { public void SetFrame (int frameIndex, float time, float r, float g, float b, float a) {
frameIndex *= 5; frameIndex *= ENTRIES;
frames[frameIndex] = time; frames[frameIndex] = time;
frames[frameIndex + 1] = r; frames[frameIndex + R] = r;
frames[frameIndex + 2] = g; frames[frameIndex + G] = g;
frames[frameIndex + 3] = b; frames[frameIndex + B] = b;
frames[frameIndex + 4] = a; frames[frameIndex + A] = a;
} }
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) { override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
@ -409,23 +404,23 @@ namespace Spine {
if (time < frames[0]) return; // Time is before first frame. if (time < frames[0]) return; // Time is before first frame.
float r, g, b, a; float r, g, b, a;
if (time >= frames[frames.Length - 5]) { // Time is after last frame. if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
int i = frames.Length - 1; int i = frames.Length;
r = frames[i - 3]; r = frames[i + PREV_R];
g = frames[i - 2]; g = frames[i + PREV_G];
b = frames[i - 1]; b = frames[i + PREV_B];
a = frames[i]; a = frames[i + PREV_A];
} else { } else {
// Interpolate between the previous frame and the current frame. // Interpolate between the previous frame and the current frame.
int frame = Animation.binarySearch(frames, time, 5); int frame = Animation.binarySearch(frames, time, ENTRIES);
r = frames[frame + PREV_R];
g = frames[frame + PREV_G];
b = frames[frame + PREV_B];
a = frames[frame + PREV_A];
float frameTime = frames[frame]; float frameTime = frames[frame];
float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime); float percent = GetCurvePercent(frame / ENTRIES - 1,
percent = GetCurvePercent(frame / 5 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
r = frames[frame - 4];
g = frames[frame - 3];
b = frames[frame - 2];
a = frames[frame - 1];
r += (frames[frame + R] - r) * percent; r += (frames[frame + R] - r) * percent;
g += (frames[frame + G] - g) * percent; g += (frames[frame + G] - g) * percent;
b += (frames[frame + B] - b) * percent; b += (frames[frame + B] - b) * percent;
@ -469,18 +464,17 @@ namespace Spine {
public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) { public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
float[] frames = this.frames; float[] frames = this.frames;
if (time < frames[0]) { if (time < frames[0]) return; // Time is before first frame.
if (lastTime > time) Apply(skeleton, lastTime, int.MaxValue, null, 0);
return;
} else if (lastTime > time) //
lastTime = -1;
int frameIndex = (time >= frames[frames.Length - 1] ? frames.Length : Animation.binarySearch(frames, time)) - 1; int frameIndex;
if (frames[frameIndex] < lastTime) return; 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]; String attachmentName = attachmentNames[frameIndex];
skeleton.slots.Items[slotIndex].Attachment = skeleton.slots.Items[slotIndex]
attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); .Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName);
} }
} }
@ -503,7 +497,7 @@ namespace Spine {
events[frameIndex] = e; events[frameIndex] = e;
} }
/// <summary>Fires events for frames > lastTime and <= time.</summary> /// <summary>Fires events for frames &gt; lastTime and &lt;= time.</summary>
public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) { public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
if (firedEvents == null) return; if (firedEvents == null) return;
float[] frames = this.frames; float[] frames = this.frames;
@ -570,24 +564,26 @@ namespace Spine {
for (int i = 0, n = slots.Count; i < n; i++) for (int i = 0, n = slots.Count; i < n; i++)
drawOrder.Add(slots.Items[i]); drawOrder.Add(slots.Items[i]);
} else { } else {
var drawOrderItems = drawOrder.Items;
var slotsItems = slots.Items;
for (int i = 0, n = drawOrderToSetupIndex.Length; i < n; i++) for (int i = 0, n = drawOrderToSetupIndex.Length; i < n; i++)
drawOrder.Items[i] = slots.Items[drawOrderToSetupIndex[i]]; drawOrderItems[i] = slotsItems[drawOrderToSetupIndex[i]];
} }
} }
} }
public class FfdTimeline : CurveTimeline { public class DeformTimeline : CurveTimeline {
internal int slotIndex; internal int slotIndex;
internal float[] frames; internal float[] frames;
private float[][] frameVertices; private float[][] frameVertices;
internal Attachment attachment; internal VertexAttachment attachment;
public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } }
public float[] Frames { get { return frames; } set { frames = value; } } // time, ... public float[] Frames { get { return frames; } set { frames = value; } } // time, ...
public float[][] Vertices { get { return frameVertices; } set { frameVertices = value; } } public float[][] Vertices { get { return frameVertices; } set { frameVertices = value; } }
public Attachment Attachment { get { return attachment; } set { attachment = value; } } public VertexAttachment Attachment { get { return attachment; } set { attachment = value; } }
public FfdTimeline (int frameCount) public DeformTimeline (int frameCount)
: base(frameCount) { : base(frameCount) {
frames = new float[frameCount]; frames = new float[frameCount];
frameVertices = new float[frameCount][]; frameVertices = new float[frameCount][];
@ -601,8 +597,8 @@ namespace Spine {
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) { override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
Slot slot = skeleton.slots.Items[slotIndex]; Slot slot = skeleton.slots.Items[slotIndex];
IFfdAttachment ffdAttachment = slot.attachment as IFfdAttachment; VertexAttachment slotAttachment = slot.attachment as VertexAttachment;
if (ffdAttachment == null || !ffdAttachment.ApplyFFD(attachment)) return; if (slotAttachment == null || !slotAttachment.ApplyDeform(attachment)) return;
float[] frames = this.frames; float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame. if (time < frames[0]) return; // Time is before first frame.
@ -610,15 +606,12 @@ namespace Spine {
float[][] frameVertices = this.frameVertices; float[][] frameVertices = this.frameVertices;
int vertexCount = frameVertices[0].Length; int vertexCount = frameVertices[0].Length;
float[] vertices = slot.attachmentVertices; var verticesArray = slot.attachmentVertices;
if (slot.attachmentVerticesCount != vertexCount) alpha = 1; // Don't mix from uninitialized slot vertices. if (verticesArray.Count != vertexCount) alpha = 1; // Don't mix from uninitialized slot vertices.
// verticesArray.SetSize(vertexCount) // Ensure size and preemptively set count.
// Ensure capacity if (verticesArray.Capacity < vertexCount) verticesArray.Capacity = vertexCount;
if (vertices.Length < vertexCount) { verticesArray.Count = vertexCount;
vertices = new float[vertexCount]; float[] vertices = verticesArray.Items;
slot.attachmentVertices = vertices;
}
slot.attachmentVerticesCount = vertexCount;
if (time >= frames[frames.Length - 1]) { // Time is after last frame. if (time >= frames[frames.Length - 1]) { // Time is after last frame.
float[] lastVertices = frameVertices[frames.Length - 1]; float[] lastVertices = frameVertices[frames.Length - 1];
@ -634,12 +627,10 @@ namespace Spine {
// Interpolate between the previous frame and the current frame. // Interpolate between the previous frame and the current frame.
int frame = Animation.binarySearch(frames, time); int frame = Animation.binarySearch(frames, time);
float frameTime = frames[frame];
float percent = 1 - (time - frameTime) / (frames[frame - 1] - frameTime);
percent = GetCurvePercent(frame - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
float[] prevVertices = frameVertices[frame - 1]; float[] prevVertices = frameVertices[frame - 1];
float[] nextVertices = frameVertices[frame]; float[] nextVertices = frameVertices[frame];
float frameTime = frames[frame];
float percent = GetCurvePercent(frame - 1, 1 - (time - frameTime) / (frames[frame - 1] - frameTime));
if (alpha < 1) { if (alpha < 1) {
for (int i = 0; i < vertexCount; i++) { for (int i = 0; i < vertexCount; i++) {
@ -657,10 +648,9 @@ namespace Spine {
} }
public class IkConstraintTimeline : CurveTimeline { public class IkConstraintTimeline : CurveTimeline {
private const int PREV_TIME = -3; public const int ENTRIES = 3;
private const int PREV_MIX = -2; private const int PREV_TIME = -3, PREV_MIX = -2, PREV_BEND_DIRECTION = -1;
private const int PREV_BEND_DIRECTION = -1; private const int MIX = 1, BEND_DIRECTION = 2;
private const int MIX = 1;
internal int ikConstraintIndex; internal int ikConstraintIndex;
internal float[] frames; internal float[] frames;
@ -670,15 +660,15 @@ namespace Spine {
public IkConstraintTimeline (int frameCount) public IkConstraintTimeline (int frameCount)
: base(frameCount) { : base(frameCount) {
frames = new float[frameCount * 3]; frames = new float[frameCount * ENTRIES];
} }
/// <summary>Sets the time, mix and bend direction of the specified keyframe.</summary> /// <summary>Sets the time, mix and bend direction of the specified keyframe.</summary>
public void SetFrame (int frameIndex, float time, float mix, int bendDirection) { public void SetFrame (int frameIndex, float time, float mix, int bendDirection) {
frameIndex *= 3; frameIndex *= ENTRIES;
frames[frameIndex] = time; frames[frameIndex] = time;
frames[frameIndex + 1] = mix; frames[frameIndex + MIX] = mix;
frames[frameIndex + 2] = bendDirection; frames[frameIndex + BEND_DIRECTION] = bendDirection;
} }
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) { override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
@ -687,34 +677,27 @@ namespace Spine {
IkConstraint constraint = skeleton.ikConstraints.Items[ikConstraintIndex]; IkConstraint constraint = skeleton.ikConstraints.Items[ikConstraintIndex];
if (time >= frames[frames.Length - 3]) { // Time is after last frame. if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
constraint.mix += (frames[frames.Length + PREV_MIX] - constraint.mix) * alpha; constraint.mix += (frames[frames.Length + PREV_MIX] - constraint.mix) * alpha;
constraint.bendDirection = (int)frames[frames.Length + PREV_BEND_DIRECTION]; constraint.bendDirection = (int)frames[frames.Length + PREV_BEND_DIRECTION];
return; return;
} }
// Interpolate between the previous frame and the current frame. // Interpolate between the previous frame and the current frame.
int frame = Animation.binarySearch(frames, time, 3); int frame = Animation.binarySearch(frames, time, ENTRIES);
float frameTime = frames[frame];
float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime);
percent = GetCurvePercent(frame / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
float mix = frames[frame + PREV_MIX]; float mix = frames[frame + PREV_MIX];
float frameTime = frames[frame];
float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
constraint.mix += (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha; constraint.mix += (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha;
constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION]; constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION];
} }
} }
public class TransformConstraintTimeline : CurveTimeline { public class TransformConstraintTimeline : CurveTimeline {
private const int PREV_TIME = -5; public const int ENTRIES = 5;
private const int PREV_ROTATE_MIX = -4; private const int PREV_TIME = -5, PREV_ROTATE = -4, PREV_TRANSLATE = -3, PREV_SCALE = -2, PREV_SHEAR = -1;
private const int PREV_TRANSLATE_MIX = -3; private const int ROTATE = 1, TRANSLATE = 2, SCALE = 3, SHEAR = 4;
private const int PREV_SCALE_MIX = -2;
private const int PREV_SHEAR_MIX = -1;
private const int ROTATE_MIX = 1;
private const int TRANSLATE_MIX = 2;
private const int SCALE_MIX = 3;
private const int SHEAR_MIX = 4;
internal int transformConstraintIndex; internal int transformConstraintIndex;
internal float[] frames; internal float[] frames;
@ -724,16 +707,16 @@ namespace Spine {
public TransformConstraintTimeline (int frameCount) public TransformConstraintTimeline (int frameCount)
: base(frameCount) { : base(frameCount) {
frames = new float[frameCount * 5]; frames = new float[frameCount * ENTRIES];
} }
public void SetFrame (int frameIndex, float time, float rotateMix, float translateMix, float scaleMix, float shearMix) { public void SetFrame (int frameIndex, float time, float rotateMix, float translateMix, float scaleMix, float shearMix) {
frameIndex *= 5; frameIndex *= ENTRIES;
frames[frameIndex] = time; frames[frameIndex] = time;
frames[frameIndex + 1] = rotateMix; frames[frameIndex + ROTATE] = rotateMix;
frames[frameIndex + 2] = translateMix; frames[frameIndex + TRANSLATE] = translateMix;
frames[frameIndex + 3] = scaleMix; frames[frameIndex + SCALE] = scaleMix;
frames[frameIndex + 4] = shearMix; frames[frameIndex + SHEAR] = shearMix;
} }
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) { override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
@ -742,29 +725,151 @@ namespace Spine {
TransformConstraint constraint = skeleton.transformConstraints.Items[transformConstraintIndex]; TransformConstraint constraint = skeleton.transformConstraints.Items[transformConstraintIndex];
if (time >= frames[frames.Length - 5]) { // Time is after last frame. if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
int i = frames.Length - 1; int i = frames.Length;
constraint.rotateMix += (frames[i - 3] - constraint.rotateMix) * alpha; constraint.rotateMix += (frames[i + PREV_ROTATE] - constraint.rotateMix) * alpha;
constraint.translateMix += (frames[i - 2] - constraint.translateMix) * alpha; constraint.translateMix += (frames[i + PREV_TRANSLATE] - constraint.translateMix) * alpha;
constraint.scaleMix += (frames[i - 1] - constraint.scaleMix) * alpha; constraint.scaleMix += (frames[i + PREV_SCALE] - constraint.scaleMix) * alpha;
constraint.shearMix += (frames[i] - constraint.shearMix) * alpha; constraint.shearMix += (frames[i + PREV_SHEAR] - constraint.shearMix) * alpha;
return; return;
} }
// Interpolate between the previous frame and the current frame. // Interpolate between the previous frame and the current frame.
int frame = Animation.binarySearch(frames, time, 5); int frame = Animation.binarySearch(frames, time, ENTRIES);
float frameTime = frames[frame]; float frameTime = frames[frame];
float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime); float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
percent = GetCurvePercent(frame / 5 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
float rotate = frames[frame + PREV_ROTATE_MIX]; float rotate = frames[frame + PREV_ROTATE];
float translate = frames[frame + PREV_TRANSLATE_MIX]; float translate = frames[frame + PREV_TRANSLATE];
float scale = frames[frame + PREV_SCALE_MIX]; float scale = frames[frame + PREV_SCALE];
float shear = frames[frame + PREV_SHEAR_MIX]; float shear = frames[frame + PREV_SHEAR];
constraint.rotateMix += (rotate + (frames[frame + ROTATE_MIX] - rotate) * percent - constraint.rotateMix) * alpha; constraint.rotateMix += (rotate + (frames[frame + ROTATE] - rotate) * percent - constraint.rotateMix) * alpha;
constraint.translateMix += (translate + (frames[frame + TRANSLATE_MIX] - translate) * percent - constraint.translateMix) * alpha; constraint.translateMix += (translate + (frames[frame + TRANSLATE] - translate) * percent - constraint.translateMix)
constraint.scaleMix += (scale + (frames[frame + SCALE_MIX] - scale) * percent - constraint.scaleMix) * alpha; * alpha;
constraint.shearMix += (shear + (frames[frame + SHEAR_MIX] - shear) * percent - constraint.shearMix) * alpha; constraint.scaleMix += (scale + (frames[frame + SCALE] - scale) * percent - constraint.scaleMix) * alpha;
constraint.shearMix += (shear + (frames[frame + SHEAR] - shear) * percent - constraint.shearMix) * alpha;
}
}
public class PathConstraintPositionTimeline : CurveTimeline {
public const int ENTRIES = 2;
protected const int PREV_TIME = -2, PREV_VALUE = -1;
protected const int VALUE = 1;
internal int pathConstraintIndex;
internal float[] frames;
public PathConstraintPositionTimeline (int frameCount)
: base(frameCount) {
frames = new float[frameCount * ENTRIES];
}
public int PathConstraintIndex { get { return pathConstraintIndex; } set { pathConstraintIndex = value; } }
public float[] Frames { get { return frames; } set { frames = value; } } // time, position, ...
/// <summary>Sets the time and value of the specified keyframe.</summary>
public void SetFrame (int frameIndex, float time, float value) {
frameIndex *= ENTRIES;
frames[frameIndex] = time;
frames[frameIndex + VALUE] = value;
}
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> events, float alpha) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex];
if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
int i = frames.Length;
constraint.position += (frames[i + PREV_VALUE] - constraint.position) * alpha;
return;
}
// Interpolate between the previous frame and the current frame.
int frame = Animation.binarySearch(frames, time, ENTRIES);
float position = frames[frame + PREV_VALUE];
float frameTime = frames[frame];
float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
constraint.position += (position + (frames[frame + VALUE] - position) * percent - constraint.position) * alpha;
}
}
public class PathConstraintSpacingTimeline : PathConstraintPositionTimeline {
public PathConstraintSpacingTimeline (int frameCount)
: base(frameCount) {
}
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> events, float alpha) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex];
if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
int i = frames.Length;
constraint.spacing += (frames[i + PREV_VALUE] - constraint.spacing) * alpha;
return;
}
// Interpolate between the previous frame and the current frame.
int frame = Animation.binarySearch(frames, time, ENTRIES);
float spacing = frames[frame + PREV_VALUE];
float frameTime = frames[frame];
float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
constraint.spacing += (spacing + (frames[frame + VALUE] - spacing) * percent - constraint.spacing) * alpha;
}
}
public class PathConstraintMixTimeline : CurveTimeline {
public const int ENTRIES = 3;
private const int PREV_TIME = -3, PREV_ROTATE = -2, PREV_TRANSLATE = -1;
private const int ROTATE = 1, TRANSLATE = 2;
internal int pathConstraintIndex;
internal float[] frames;
public int PathConstraintIndex { get { return pathConstraintIndex; } set { pathConstraintIndex = value; } }
public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, ...
public PathConstraintMixTimeline (int frameCount)
: base(frameCount) {
frames = new float[frameCount * ENTRIES];
}
/** Sets the time and mixes of the specified keyframe. */
public void SetFrame (int frameIndex, float time, float rotateMix, float translateMix) {
frameIndex *= ENTRIES;
frames[frameIndex] = time;
frames[frameIndex + ROTATE] = rotateMix;
frames[frameIndex + TRANSLATE] = translateMix;
}
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> events, float alpha) {
float[] frames = this.frames;
if (time < frames[0]) return; // Time is before first frame.
PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex];
if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
int i = frames.Length;
constraint.rotateMix += (frames[i + PREV_ROTATE] - constraint.rotateMix) * alpha;
constraint.translateMix += (frames[i + PREV_TRANSLATE] - constraint.translateMix) * alpha;
return;
}
// Interpolate between the previous frame and the current frame.
int frame = Animation.binarySearch(frames, time, ENTRIES);
float rotate = frames[frame + PREV_ROTATE];
float translate = frames[frame + PREV_TRANSLATE];
float frameTime = frames[frame];
float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
constraint.rotateMix += (rotate + (frames[frame + ROTATE] - rotate) * percent - constraint.rotateMix) * alpha;
constraint.translateMix += (translate + (frames[frame + TRANSLATE] - translate) * percent - constraint.translateMix)
* alpha;
} }
} }
} }

View File

@ -41,6 +41,8 @@ namespace Spine {
private float timeScale = 1; private float timeScale = 1;
public AnimationStateData Data { get { return data; } } public AnimationStateData Data { get { return data; } }
/// <summary>A list of tracks that have animations, which may contain nulls.</summary>
public ExposedList<TrackEntry> Tracks { get { return tracks; } }
public float TimeScale { get { return timeScale; } set { timeScale = value; } } public float TimeScale { get { return timeScale; } set { timeScale = value; } }
public delegate void StartEndDelegate (AnimationState state, int trackIndex); public delegate void StartEndDelegate (AnimationState state, int trackIndex);
@ -54,7 +56,7 @@ namespace Spine {
public event CompleteDelegate Complete; public event CompleteDelegate Complete;
public AnimationState (AnimationStateData data) { public AnimationState (AnimationStateData data) {
if (data == null) throw new ArgumentNullException("data cannot be null."); if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
this.data = data; this.data = data;
} }
@ -187,15 +189,16 @@ namespace Spine {
if (Start != null) Start(this, index); if (Start != null) Start(this, index);
} }
/// <seealso cref="SetAnimation(int, Animation, bool)" />
public TrackEntry SetAnimation (int trackIndex, String animationName, bool loop) { public TrackEntry SetAnimation (int trackIndex, String animationName, bool loop) {
Animation animation = data.skeletonData.FindAnimation(animationName); Animation animation = data.skeletonData.FindAnimation(animationName);
if (animation == null) throw new ArgumentException("Animation not found: " + animationName); if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName");
return SetAnimation(trackIndex, animation, loop); return SetAnimation(trackIndex, animation, loop);
} }
/// <summary>Set the current animation. Any queued animations are cleared.</summary> /// <summary>Set the current animation. Any queued animations are cleared.</summary>
public TrackEntry SetAnimation (int trackIndex, Animation animation, bool loop) { public TrackEntry SetAnimation (int trackIndex, Animation animation, bool loop) {
if (animation == null) throw new ArgumentException("animation cannot be null."); if (animation == null) throw new ArgumentNullException("animation", "animation cannot be null.");
TrackEntry entry = new TrackEntry(); TrackEntry entry = new TrackEntry();
entry.animation = animation; entry.animation = animation;
entry.loop = loop; entry.loop = loop;
@ -205,16 +208,17 @@ namespace Spine {
return entry; return entry;
} }
/// <seealso cref="AddAnimation(int, Animation, bool, float)" />
public TrackEntry AddAnimation (int trackIndex, String animationName, bool loop, float delay) { public TrackEntry AddAnimation (int trackIndex, String animationName, bool loop, float delay) {
Animation animation = data.skeletonData.FindAnimation(animationName); Animation animation = data.skeletonData.FindAnimation(animationName);
if (animation == null) throw new ArgumentException("Animation not found: " + animationName); if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName");
return AddAnimation(trackIndex, animation, loop, delay); return AddAnimation(trackIndex, animation, loop, delay);
} }
/// <summary>Adds an animation to be played delay seconds after the current or last queued animation.</summary> /// <summary>Adds an animation to be played delay seconds after the current or last queued animation.</summary>
/// <param name="delay">May be <= 0 to use duration of previous animation minus any mix duration plus the negative delay.</param> /// <param name="delay">May be &lt;= 0 to use duration of previous animation minus any mix duration plus the negative delay.</param>
public TrackEntry AddAnimation (int trackIndex, Animation animation, bool loop, float delay) { public TrackEntry AddAnimation (int trackIndex, Animation animation, bool loop, float delay) {
if (animation == null) throw new ArgumentException("animation cannot be null."); if (animation == null) throw new ArgumentNullException("animation", "animation cannot be null.");
TrackEntry entry = new TrackEntry(); TrackEntry entry = new TrackEntry();
entry.animation = animation; entry.animation = animation;
entry.loop = loop; entry.loop = loop;

View File

@ -42,6 +42,7 @@ namespace Spine {
public float DefaultMix { get { return defaultMix; } set { defaultMix = value; } } public float DefaultMix { get { return defaultMix; } set { defaultMix = value; } }
public AnimationStateData (SkeletonData skeletonData) { public AnimationStateData (SkeletonData skeletonData) {
if (skeletonData == null) throw new ArgumentException ("skeletonData cannot be null.");
this.skeletonData = skeletonData; this.skeletonData = skeletonData;
} }
@ -54,8 +55,8 @@ namespace Spine {
} }
public void SetMix (Animation from, Animation to, float duration) { public void SetMix (Animation from, Animation to, float duration) {
if (from == null) throw new ArgumentNullException("from cannot be null."); if (from == null) throw new ArgumentNullException("from", "from cannot be null.");
if (to == null) throw new ArgumentNullException("to cannot be null."); if (to == null) throw new ArgumentNullException("to", "to cannot be null.");
AnimationPair key = new AnimationPair(from, to); AnimationPair key = new AnimationPair(from, to);
animationToMixTime.Remove(key); animationToMixTime.Remove(key);
animationToMixTime.Add(key, duration); animationToMixTime.Add(key, duration);

View File

@ -72,31 +72,16 @@ namespace Spine {
attachment.regionOriginalWidth = region.originalWidth; attachment.regionOriginalWidth = region.originalWidth;
attachment.regionOriginalHeight = region.originalHeight; attachment.regionOriginalHeight = region.originalHeight;
return attachment; return attachment;
} }
public WeightedMeshAttachment NewWeightedMeshAttachment (Skin skin, String name, String path) {
AtlasRegion region = FindRegion(path);
if (region == null) throw new Exception("Region not found in atlas: " + path + " (weighted mesh attachment: " + name + ")");
WeightedMeshAttachment attachment = new WeightedMeshAttachment(name);
attachment.RendererObject = region;
attachment.RegionU = region.u;
attachment.RegionV = region.v;
attachment.RegionU2 = region.u2;
attachment.RegionV2 = region.v2;
attachment.RegionRotate = region.rotate;
attachment.regionOffsetX = region.offsetX;
attachment.regionOffsetY = region.offsetY;
attachment.regionWidth = region.width;
attachment.regionHeight = region.height;
attachment.regionOriginalWidth = region.originalWidth;
attachment.regionOriginalHeight = region.originalHeight;
return attachment;
}
public BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, String name) { public BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, String name) {
return new BoundingBoxAttachment(name); return new BoundingBoxAttachment(name);
} }
public PathAttachment NewPathAttachment (Skin skin, String name) {
return new PathAttachment (name);
}
public AtlasRegion FindRegion (string name) { public AtlasRegion FindRegion (string name) {
AtlasRegion region; AtlasRegion region;

View File

@ -36,7 +36,7 @@ namespace Spine {
public String Name { get; private set; } public String Name { get; private set; }
public Attachment (String name) { public Attachment (String name) {
if (name == null) throw new ArgumentNullException("name cannot be null."); if (name == null) throw new ArgumentNullException("name", "name cannot be null");
Name = name; Name = name;
} }

View File

@ -39,10 +39,10 @@ namespace Spine {
/// <return>May be null to not load any attachment.</return> /// <return>May be null to not load any attachment.</return>
MeshAttachment NewMeshAttachment (Skin skin, String name, String path); MeshAttachment NewMeshAttachment (Skin skin, String name, String path);
/// <return>May be null to not load any attachment.</return>
WeightedMeshAttachment NewWeightedMeshAttachment (Skin skin, String name, String path);
/// <return>May be null to not load any attachment.</return> /// <return>May be null to not load any attachment.</return>
BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, String name); BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, String name);
/// <returns>May be null to not load any attachment</returns>
PathAttachment NewPathAttachment (Skin skin, String name);
} }
} }

View File

@ -31,6 +31,6 @@
namespace Spine { namespace Spine {
public enum AttachmentType { public enum AttachmentType {
region, boundingbox, mesh, weightedmesh, linkedmesh, weightedlinkedmesh Region, Boundingbox, Mesh, Linkedmesh, Path
} }
} }

View File

@ -33,29 +33,9 @@ using System;
namespace Spine { namespace Spine {
/// <summary>Attachment that has a polygon for bounds checking.</summary> /// <summary>Attachment that has a polygon for bounds checking.</summary>
public class BoundingBoxAttachment : Attachment { public class BoundingBoxAttachment : VertexAttachment {
internal float[] vertices;
public float[] Vertices { get { return vertices; } set { vertices = value; } }
public BoundingBoxAttachment (string name) public BoundingBoxAttachment (string name)
: base(name) { : base(name) {
} }
/// <param name="worldVertices">Must have at least the same length as this attachment's vertices.</param>
public void ComputeWorldVertices (Bone bone, float[] worldVertices) {
float x = bone.skeleton.x + bone.worldX, y = bone.skeleton.y + bone.worldY;
float m00 = bone.a;
float m01 = bone.b;
float m10 = bone.c;
float m11 = bone.d;
float[] vertices = this.vertices;
for (int i = 0, n = vertices.Length; i < n; i += 2) {
float px = vertices[i];
float py = vertices[i + 1];
worldVertices[i] = px * m00 + py * m01 + x;
worldVertices[i + 1] = px * m10 + py * m11 + y;
}
}
} }
} }

View File

@ -33,16 +33,16 @@ using System;
namespace Spine { namespace Spine {
/// <summary>Attachment that displays a texture region using a mesh.</summary> /// <summary>Attachment that displays a texture region using a mesh.</summary>
public class MeshAttachment : Attachment, IFfdAttachment { public class MeshAttachment : VertexAttachment {
internal float[] vertices, uvs, regionUVs;
internal int[] triangles;
internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight;
internal float[] uvs, regionUVs;
internal int[] triangles;
internal float r = 1, g = 1, b = 1, a = 1; internal float r = 1, g = 1, b = 1, a = 1;
internal int hulllength;
internal MeshAttachment parentMesh; internal MeshAttachment parentMesh;
internal bool inheritFFD; internal bool inheritDeform;
public int HullLength { get; set; } public int HullLength { get { return hulllength; } set { hulllength = value; } }
public float[] Vertices { get { return vertices; } set { vertices = value; } }
public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } } public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } }
public float[] UVs { get { return uvs; } set { uvs = value; } } public float[] UVs { get { return uvs; } set { uvs = value; } }
public int[] Triangles { get { return triangles; } set { triangles = value; } } public int[] Triangles { get { return triangles; } set { triangles = value; } }
@ -66,7 +66,7 @@ namespace Spine {
public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } }
public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size.
public bool InheritFFD { get { return inheritFFD; } set { inheritFFD = value; } } public bool InheritDeform { get { return inheritDeform; } set { inheritDeform = value; } }
public MeshAttachment ParentMesh { public MeshAttachment ParentMesh {
get { return parentMesh; } get { return parentMesh; }
@ -74,6 +74,7 @@ namespace Spine {
parentMesh = value; parentMesh = value;
if (value != null) { if (value != null) {
vertices = value.vertices; vertices = value.vertices;
worldVerticesLength = value.worldVerticesLength;
regionUVs = value.regionUVs; regionUVs = value.regionUVs;
triangles = value.triangles; triangles = value.triangles;
HullLength = value.HullLength; HullLength = value.HullLength;
@ -111,23 +112,8 @@ namespace Spine {
} }
} }
public void ComputeWorldVertices (Slot slot, float[] worldVertices) { override public bool ApplyDeform (VertexAttachment sourceAttachment) {
Bone bone = slot.bone; return this == sourceAttachment || (inheritDeform && parentMesh == sourceAttachment);
float x = bone.skeleton.x + bone.worldX, y = bone.skeleton.y + bone.worldY;
float m00 = bone.a, m01 = bone.b, m10 = bone.c, m11 = bone.d;
float[] vertices = this.vertices;
int verticesCount = vertices.Length;
if (slot.attachmentVerticesCount == verticesCount) vertices = slot.AttachmentVertices;
for (int i = 0; i < verticesCount; i += 2) {
float vx = vertices[i];
float vy = vertices[i + 1];
worldVertices[i] = vx * m00 + vy * m01 + x;
worldVertices[i + 1] = vx * m10 + vy * m11 + y;
}
}
public bool ApplyFFD (Attachment sourceAttachment) {
return this == sourceAttachment || (inheritFFD && parentMesh == sourceAttachment);
} }
} }
} }

View File

@ -29,8 +29,21 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
using System;
using System.Collections.Generic;
namespace Spine { namespace Spine {
public interface IFfdAttachment { public class PathAttachment : VertexAttachment {
bool ApplyFFD (Attachment sourceAttachment); internal float[] lengths;
internal bool closed, constantSpeed;
/// <summary>The length in the setup pose from the start of the path to the end of each curve.</summary>
public float[] Lengths { get { return lengths; } set { lengths = value; } }
public bool Closed { get { return closed; } set { closed = value; } }
public bool ConstantSpeed { get { return constantSpeed; } set { constantSpeed = value; } }
public PathAttachment (String name)
: base(name) {
}
} }
} }

View File

@ -111,9 +111,9 @@ namespace Spine {
float localY = -height / 2 * scaleY + regionOffsetY * regionScaleY; float localY = -height / 2 * scaleY + regionOffsetY * regionScaleY;
float localX2 = localX + regionWidth * regionScaleX; float localX2 = localX + regionWidth * regionScaleX;
float localY2 = localY + regionHeight * regionScaleY; float localY2 = localY + regionHeight * regionScaleY;
float radians = rotation * (float)Math.PI / 180; float rotation = this.rotation;
float cos = (float)Math.Cos(radians); float cos = MathUtils.CosDeg(rotation);
float sin = (float)Math.Sin(radians); float sin = MathUtils.SinDeg(rotation);
float x = this.x; float x = this.x;
float y = this.y; float y = this.y;
float localXCos = localX * cos + x; float localXCos = localX * cos + x;
@ -136,17 +136,18 @@ namespace Spine {
} }
public void ComputeWorldVertices (Bone bone, float[] worldVertices) { public void ComputeWorldVertices (Bone bone, float[] worldVertices) {
float x = bone.skeleton.x + bone.worldX, y = bone.skeleton.y + bone.worldY; Skeleton skeleton = bone.skeleton;
float m00 = bone.a, m01 = bone.b, m10 = bone.c, m11 = bone.d; float x = skeleton.x + bone.worldX, y = skeleton.y + bone.worldY;
float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
float[] offset = this.offset; float[] offset = this.offset;
worldVertices[X1] = offset[X1] * m00 + offset[Y1] * m01 + x; worldVertices[X1] = offset[X1] * a + offset[Y1] * b + x;
worldVertices[Y1] = offset[X1] * m10 + offset[Y1] * m11 + y; worldVertices[Y1] = offset[X1] * c + offset[Y1] * d + y;
worldVertices[X2] = offset[X2] * m00 + offset[Y2] * m01 + x; worldVertices[X2] = offset[X2] * a + offset[Y2] * b + x;
worldVertices[Y2] = offset[X2] * m10 + offset[Y2] * m11 + y; worldVertices[Y2] = offset[X2] * c + offset[Y2] * d + y;
worldVertices[X3] = offset[X3] * m00 + offset[Y3] * m01 + x; worldVertices[X3] = offset[X3] * a + offset[Y3] * b + x;
worldVertices[Y3] = offset[X3] * m10 + offset[Y3] * m11 + y; worldVertices[Y3] = offset[X3] * c + offset[Y3] * d + y;
worldVertices[X4] = offset[X4] * m00 + offset[Y4] * m01 + x; worldVertices[X4] = offset[X4] * a + offset[Y4] * b + x;
worldVertices[Y4] = offset[X4] * m10 + offset[Y4] * m11 + y; worldVertices[Y4] = offset[X4] * c + offset[Y4] * d + y;
} }
} }
} }

View File

@ -0,0 +1,114 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.3
*
* Copyright (c) 2013-2015, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable and
* non-transferable license to use, install, execute and perform the Spine
* Runtimes Software (the "Software") and derivative works solely for personal
* or internal use. Without the written permission of Esoteric Software (see
* Section 2 of the Spine Software License Agreement), you may not (a) modify,
* translate, adapt or otherwise create derivative works, improvements of the
* Software or develop new applications using the Software or (b) remove,
* delete, alter or obscure any trademarks or any copyright, trademark, patent
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "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 ESOTERIC SOFTWARE 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 {
/// <summary>>An attachment with vertices that are transformed by one or more bones and can be deformed by a slot's vertices.</summary>
public class VertexAttachment : Attachment {
internal int[] bones;
internal float[] vertices;
internal int worldVerticesLength;
public int[] Bones { get { return bones; } set { bones = value; } }
public float[] Vertices { get { return vertices; } set { vertices = value; } }
public int WorldVerticesLength { get { return worldVerticesLength; } set { worldVerticesLength = value; } }
public VertexAttachment (String name)
: base(name) {
}
public void ComputeWorldVertices (Slot slot, float[] worldVertices) {
ComputeWorldVertices(slot, 0, worldVerticesLength, worldVertices, 0);
}
public void ComputeWorldVertices (Slot slot, int start, int count, float[] worldVertices, int offset) {
count += offset;
Skeleton skeleton = slot.Skeleton;
float x = skeleton.x, y = skeleton.y;
var deformArray = slot.attachmentVertices;
float[] vertices = this.vertices;
int[] bones = this.bones;
if (bones == null) {
if (deformArray.Count > 0) vertices = deformArray.Items;
Bone bone = slot.bone;
x += bone.worldX;
y += bone.worldY;
float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
for (int vv = start, w = offset; w < count; vv += 2, w += 2) {
float vx = vertices[vv], vy = vertices[vv + 1];
worldVertices[w] = vx * a + vy * b + x;
worldVertices[w + 1] = vx * c + vy * d + y;
}
return;
}
int v = 0, skip = 0;
for (int i = 0; i < start; i += 2) {
int n = bones[v];
v += n + 1;
skip += n;
}
Bone[] skeletonBones = skeleton.Bones.Items;
if (deformArray.Count == 0) {
for (int w = offset, b = skip * 3; w < count; w += 2) {
float wx = x, wy = y;
for (int n = bones[v++] + v; v < n; v++, b += 3) {
Bone bone = skeletonBones[bones[v]];
float vx = vertices[b], vy = vertices[b + 1], weight = vertices[b + 2];
wx += (vx * bone.a + vy * bone.b + bone.worldX) * weight;
wy += (vx * bone.c + vy * bone.d + bone.worldY) * weight;
}
worldVertices[w] = wx;
worldVertices[w + 1] = wy;
}
} else {
float[] deform = deformArray.Items;
for (int w = offset, b = skip * 3, f = skip << 1; w < count; w += 2) {
float wx = x, wy = y;
for (int n = bones[v++] + v; v < n; v++, b += 3, f += 2) {
Bone bone = skeletonBones[bones[v]];
float vx = vertices[b] + deform[f], vy = vertices[b + 1] + deform[f + 1], weight = vertices[b + 2];
wx += (vx * bone.a + vy * bone.b + bone.worldX) * weight;
wy += (vx * bone.c + vy * bone.d + bone.worldY) * weight;
}
worldVertices[w] = wx;
worldVertices[w + 1] = wy;
}
}
}
/// <summary>Returns true if a deform originally applied to the specified attachment should be applied to this attachment.</summary>
virtual public bool ApplyDeform (VertexAttachment sourceAttachment) {
return this == sourceAttachment;
}
}
}

View File

@ -1,158 +0,0 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.3
*
* Copyright (c) 2013-2015, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable and
* non-transferable license to use, install, execute and perform the Spine
* Runtimes Software (the "Software") and derivative works solely for personal
* or internal use. Without the written permission of Esoteric Software (see
* Section 2 of the Spine Software License Agreement), you may not (a) modify,
* translate, adapt or otherwise create derivative works, improvements of the
* Software or develop new applications using the Software or (b) remove,
* delete, alter or obscure any trademarks or any copyright, trademark, patent
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "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 ESOTERIC SOFTWARE 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 {
/// <summary>Attachment that displays a texture region using a mesh which can be deformed by bones.</summary>
public class WeightedMeshAttachment : Attachment, IFfdAttachment {
internal int[] bones;
internal float[] weights, uvs, regionUVs;
internal int[] triangles;
internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight;
internal float r = 1, g = 1, b = 1, a = 1;
internal WeightedMeshAttachment parentMesh;
internal bool inheritFFD;
public int HullLength { get; set; }
public int[] Bones { get { return bones; } set { bones = value; } }
public float[] Weights { get { return weights; } set { weights = value; } }
public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } }
public float[] UVs { get { return uvs; } set { uvs = value; } }
public int[] Triangles { get { return triangles; } set { triangles = value; } }
public float R { get { return r; } set { r = value; } }
public float G { get { return g; } set { g = value; } }
public float B { get { return b; } set { b = value; } }
public float A { get { return a; } set { a = value; } }
public String Path { get; set; }
public Object RendererObject { get; set; }
public float RegionU { get; set; }
public float RegionV { get; set; }
public float RegionU2 { get; set; }
public float RegionV2 { get; set; }
public bool RegionRotate { get; set; }
public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } }
public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated.
public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } }
public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size.
public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } }
public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size.
public bool InheritFFD { get { return inheritFFD; } set { inheritFFD = value; } }
public WeightedMeshAttachment ParentMesh {
get { return parentMesh; }
set {
parentMesh = value;
if (value != null) {
bones = value.bones;
weights = value.weights;
regionUVs = value.regionUVs;
triangles = value.triangles;
HullLength = value.HullLength;
Edges = value.Edges;
Width = value.Width;
Height = value.Height;
}
}
}
// Nonessential.
public int[] Edges { get; set; }
public float Width { get; set; }
public float Height { get; set; }
public WeightedMeshAttachment (string name)
: base(name) {
}
public void UpdateUVs () {
float u = RegionU, v = RegionV, width = RegionU2 - RegionU, height = RegionV2 - RegionV;
float[] regionUVs = this.regionUVs;
if (this.uvs == null || this.uvs.Length != regionUVs.Length) this.uvs = new float[regionUVs.Length];
float[] uvs = this.uvs;
if (RegionRotate) {
for (int i = 0, n = uvs.Length; i < n; i += 2) {
uvs[i] = u + regionUVs[i + 1] * width;
uvs[i + 1] = v + height - regionUVs[i] * height;
}
} else {
for (int i = 0, n = uvs.Length; i < n; i += 2) {
uvs[i] = u + regionUVs[i] * width;
uvs[i + 1] = v + regionUVs[i + 1] * height;
}
}
}
public void ComputeWorldVertices (Slot slot, float[] worldVertices) {
Skeleton skeleton = slot.bone.skeleton;
ExposedList<Bone> skeletonBones = skeleton.bones;
float x = skeleton.x, y = skeleton.y;
float[] weights = this.weights;
int[] bones = this.bones;
if (slot.attachmentVerticesCount == 0) {
for (int w = 0, v = 0, b = 0, n = bones.Length; v < n; w += 2) {
float wx = 0, wy = 0;
int nn = bones[v++] + v;
for (; v < nn; v++, b += 3) {
Bone bone = skeletonBones.Items[bones[v]];
float vx = weights[b], vy = weights[b + 1], weight = weights[b + 2];
wx += (vx * bone.a + vy * bone.b + bone.worldX) * weight;
wy += (vx * bone.c + vy * bone.d + bone.worldY) * weight;
}
worldVertices[w] = wx + x;
worldVertices[w + 1] = wy + y;
}
} else {
float[] ffd = slot.attachmentVertices;
for (int w = 0, v = 0, b = 0, f = 0, n = bones.Length; v < n; w += 2) {
float wx = 0, wy = 0;
int nn = bones[v++] + v;
for (; v < nn; v++, b += 3, f += 2) {
Bone bone = skeletonBones.Items[bones[v]];
float vx = weights[b] + ffd[f], vy = weights[b + 1] + ffd[f + 1], weight = weights[b + 2];
wx += (vx * bone.a + vy * bone.b + bone.worldX) * weight;
wy += (vx * bone.c + vy * bone.d + bone.worldY) * weight;
}
worldVertices[w] = wx + x;
worldVertices[w + 1] = wy + y;
}
}
}
public bool ApplyFFD (Attachment sourceAttachment) {
return this == sourceAttachment || (inheritFFD && parentMesh == sourceAttachment);
}
}
}

View File

@ -30,7 +30,6 @@
*****************************************************************************/ *****************************************************************************/
using System; using System;
using System.Collections.Generic;
namespace Spine { namespace Spine {
public class Bone : IUpdatable { public class Bone : IUpdatable {
@ -41,12 +40,14 @@ namespace Spine {
internal Bone parent; internal Bone parent;
internal ExposedList<Bone> children = new ExposedList<Bone>(); internal ExposedList<Bone> children = new ExposedList<Bone>();
internal float x, y, rotation, scaleX, scaleY, shearX, shearY; internal float x, y, rotation, scaleX, scaleY, shearX, shearY;
internal float appliedRotation, appliedScaleX, appliedScaleY; internal float appliedRotation;
internal float a, b, worldX; internal float a, b, worldX;
internal float c, d, worldY; internal float c, d, worldY;
internal float worldSignX, worldSignY; internal float worldSignX, worldSignY;
internal bool sorted;
public BoneData Data { get { return data; } } public BoneData Data { get { return data; } }
public Skeleton Skeleton { get { return skeleton; } } public Skeleton Skeleton { get { return skeleton; } }
public Bone Parent { get { return parent; } } public Bone Parent { get { return parent; } }
@ -56,10 +57,6 @@ namespace Spine {
public float Rotation { get { return rotation; } set { rotation = value; } } public float Rotation { get { return rotation; } set { rotation = value; } }
/// <summary>The rotation, as calculated by any constraints.</summary> /// <summary>The rotation, as calculated by any constraints.</summary>
public float AppliedRotation { get { return appliedRotation; } set { appliedRotation = value; } } public float AppliedRotation { get { return appliedRotation; } set { appliedRotation = value; } }
/// <summary>The scale X, as calculated by any constraints.</summary>
public float AppliedScaleX { get { return appliedScaleX; } set { appliedScaleX = value; } }
/// <summary>The scale Y, as calculated by any constraints.</summary>
public float AppliedScaleY { get { return appliedScaleY; } set { appliedScaleY = value; } }
public float ScaleX { get { return scaleX; } set { scaleX = value; } } public float ScaleX { get { return scaleX; } set { scaleX = value; } }
public float ScaleY { get { return scaleY; } set { scaleY = value; } } public float ScaleY { get { return scaleY; } set { scaleY = value; } }
public float ShearX { get { return shearX; } set { shearX = value; } } public float ShearX { get { return shearX; } set { shearX = value; } }
@ -80,29 +77,27 @@ namespace Spine {
/// <param name="parent">May be null.</param> /// <param name="parent">May be null.</param>
public Bone (BoneData data, Skeleton skeleton, Bone parent) { public Bone (BoneData data, Skeleton skeleton, Bone parent) {
if (data == null) throw new ArgumentNullException("data cannot be null."); if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null.");
this.data = data; this.data = data;
this.skeleton = skeleton; this.skeleton = skeleton;
this.parent = parent; this.parent = parent;
SetToSetupPose(); SetToSetupPose();
} }
/// <summary>Same as {@link #updateWorldTransform()}. This method exists for Bone to implement {@link Updatable}.</summary> /// <summary>Same as <see cref="UpdateWorldTransform"/>. This method exists for Bone to implement <see cref="Spine.IUpdatable"/>.</summary>
public void Update () { public void Update () {
UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY);
} }
/// <summary>Computes the world SRT using the parent bone and this bone's local SRT.</summary> /// <summary>Computes the world transform using the parent bone and this bone's local transform.</summary>
public void UpdateWorldTransform () { public void UpdateWorldTransform () {
UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY);
} }
/// <summary>Computes the world SRT using the parent bone and the specified local SRT.</summary> /// <summary>Computes the world transform using the parent bone and the specified local transform.</summary>
public void UpdateWorldTransform (float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY) { public void UpdateWorldTransform (float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY) {
appliedRotation = rotation; appliedRotation = rotation;
appliedScaleX = scaleX;
appliedScaleY = scaleY;
float rotationY = rotation + 90 + shearY; float rotationY = rotation + 90 + shearY;
float la = MathUtils.CosDeg(rotation + shearX) * scaleX, lb = MathUtils.CosDeg(rotationY) * scaleY; float la = MathUtils.CosDeg(rotation + shearX) * scaleX, lb = MathUtils.CosDeg(rotationY) * scaleY;
@ -152,10 +147,10 @@ namespace Spine {
do { do {
float cos = MathUtils.CosDeg(parent.appliedRotation), sin = MathUtils.SinDeg(parent.appliedRotation); float cos = MathUtils.CosDeg(parent.appliedRotation), sin = MathUtils.SinDeg(parent.appliedRotation);
float temp = pa * cos + pb * sin; float temp = pa * cos + pb * sin;
pb = pa * -sin + pb * cos; pb = pb * cos - pa * sin;
pa = temp; pa = temp;
temp = pc * cos + pd * sin; temp = pc * cos + pd * sin;
pd = pc * -sin + pd * cos; pd = pd * cos - pc * sin;
pc = temp; pc = temp;
if (!parent.data.inheritRotation) break; if (!parent.data.inheritRotation) break;
@ -171,24 +166,22 @@ namespace Spine {
pc = 0; pc = 0;
pd = 1; pd = 1;
do { do {
float r = parent.appliedRotation, cos = MathUtils.CosDeg(r), sin = MathUtils.SinDeg(r); float cos = MathUtils.CosDeg(parent.appliedRotation), sin = MathUtils.SinDeg(parent.appliedRotation);
float psx = parent.appliedScaleX, psy = parent.appliedScaleY; float psx = parent.scaleX, psy = parent.scaleY;
float za = cos * psx, zb = -sin * psy, zc = sin * psx, zd = cos * psy; float za = cos * psx, zb = sin * psy, zc = sin * psx, zd = cos * psy;
float temp = pa * za + pb * zc; float temp = pa * za + pb * zc;
pb = pa * zb + pb * zd; pb = pb * zd - pa * zb;
pa = temp; pa = temp;
temp = pc * za + pd * zc; temp = pc * za + pd * zc;
pd = pc * zb + pd * zd; pd = pd * zd - pc * zb;
pc = temp; pc = temp;
if (psx < 0) r = -r; if (psx >= 0) sin = -sin;
cos = MathUtils.CosDeg(-r);
sin = MathUtils.SinDeg(-r);
temp = pa * cos + pb * sin; temp = pa * cos + pb * sin;
pb = pa * -sin + pb * cos; pb = pb * cos - pa * sin;
pa = temp; pa = temp;
temp = pc * cos + pd * sin; temp = pc * cos + pd * sin;
pd = pc * -sin + pd * cos; pd = pd * cos - pc * sin;
pc = temp; pc = temp;
if (!parent.data.inheritScale) break; if (!parent.data.inheritScale) break;
@ -226,10 +219,86 @@ namespace Spine {
shearY = data.shearY; shearY = data.shearY;
} }
public void WorldToLocal (float worldX, float worldY, out float localX, out float localY) { public float WorldToLocalRotationX {
float x = worldX - this.worldX, y = worldY - this.worldY; get {
Bone parent = this.parent;
if (parent == null) return rotation;
float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, a = this.a, c = this.c;
return MathUtils.Atan2(pa * c - pc * a, pd * a - pb * c) * MathUtils.radDeg;
}
}
public float WorldToLocalRotationY {
get {
Bone parent = this.parent;
if (parent == null) return rotation;
float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, b = this.b, d = this.d;
return MathUtils.Atan2(pa * d - pc * b, pd * b - pb * d) * MathUtils.radDeg;
}
}
public void RotateWorld (float degrees) {
float a = this.a, b = this.b, c = this.c, d = this.d;
float cos = MathUtils.CosDeg(degrees), sin = MathUtils.SinDeg(degrees);
this.a = cos * a - sin * c;
this.b = cos * b - sin * d;
this.c = sin * a + cos * c;
this.d = sin * b + cos * d;
}
/// <summary>
/// Computes the local transform from the world transform. This can be useful to perform processing on the local transform
/// after the world transform has been modified directly (eg, by a constraint).
///
/// Some redundant information is lost by the world transform, such as -1,-1 scale versus 180 rotation. The computed local
/// transform values may differ from the original values but are functionally the same.
/// </summary>
public void UpdateLocalTransform () {
Bone parent = this.parent;
if (parent == null) {
x = worldX;
y = worldY;
rotation = MathUtils.Atan2(c, a) * MathUtils.radDeg;
scaleX = (float)Math.Sqrt(a * a + c * c);
scaleY = (float)Math.Sqrt(b * b + d * d);
float det = a * d - b * c;
shearX = 0;
shearY = MathUtils.Atan2(a * b + c * d, det) * MathUtils.radDeg;
return;
}
float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
float pid = 1 / (pa * pd - pb * pc);
float dx = worldX - parent.worldX, dy = worldY - parent.worldY;
x = (dx * pd * pid - dy * pb * pid);
y = (dy * pa * pid - dx * pc * pid);
float ia = pid * pd;
float id = pid * pa;
float ib = pid * pb;
float ic = pid * pc;
float ra = ia * a - ib * c;
float rb = ia * b - ib * d;
float rc = id * c - ic * a;
float rd = id * d - ic * b;
shearX = 0;
scaleX = (float)Math.Sqrt(ra * ra + rc * rc);
if (scaleX > 0.0001f) {
float det = ra * rd - rb * rc;
scaleY = det / scaleX;
shearY = MathUtils.Atan2(ra * rb + rc * rd, det) * MathUtils.radDeg;
rotation = MathUtils.Atan2(rc, ra) * MathUtils.radDeg;
} else {
scaleX = 0;
scaleY = (float)Math.Sqrt(rb * rb + rd * rd);
shearY = 0;
rotation = 90 - MathUtils.Atan2(rd, rb) * MathUtils.radDeg;
}
appliedRotation = rotation;
}
public void WorldToLocal (float worldX, float worldY, out float localX, out float localY) {
float a = this.a, b = this.b, c = this.c, d = this.d; float a = this.a, b = this.b, c = this.c, d = this.d;
float invDet = 1 / (a * d - b * c); float invDet = 1 / (a * d - b * c);
float x = worldX - this.worldX, y = worldY - this.worldY;
localX = (x * d * invDet - y * b * invDet); localX = (x * d * invDet - y * b * invDet);
localY = (y * a * invDet - x * c * invDet); localY = (y * a * invDet - x * c * invDet);
} }

View File

@ -33,14 +33,17 @@ using System;
namespace Spine { namespace Spine {
public class BoneData { public class BoneData {
internal BoneData parent; internal int index;
internal String name; internal String name;
internal float length, x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY; internal BoneData parent;
internal bool inheritScale = true, inheritRotation = true; internal float length;
internal float x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY;
internal bool inheritRotation = true, inheritScale = true;
/// <summary>May be null.</summary> /// <summary>May be null.</summary>
public BoneData Parent { get { return parent; } } public int Index { get { return index; } }
public String Name { get { return name; } } public String Name { get { return name; } }
public BoneData Parent { get { return parent; } }
public float Length { get { return length; } set { length = value; } } public float Length { get { return length; } set { length = value; } }
public float X { get { return x; } set { x = value; } } public float X { get { return x; } set { x = value; } }
public float Y { get { return y; } set { y = value; } } public float Y { get { return y; } set { y = value; } }
@ -49,12 +52,14 @@ namespace Spine {
public float ScaleY { get { return scaleY; } set { scaleY = value; } } public float ScaleY { get { return scaleY; } set { scaleY = value; } }
public float ShearX { get { return shearX; } set { shearX = value; } } public float ShearX { get { return shearX; } set { shearX = value; } }
public float ShearY { get { return shearY; } set { shearY = value; } } public float ShearY { get { return shearY; } set { shearY = value; } }
public bool InheritScale { get { return inheritScale; } set { inheritScale = value; } }
public bool InheritRotation { get { return inheritRotation; } set { inheritRotation = value; } } public bool InheritRotation { get { return inheritRotation; } set { inheritRotation = value; } }
public bool InheritScale { get { return inheritScale; } set { inheritScale = value; } }
/// <param name="parent">May be null.</param> /// <param name="parent">May be null.</param>
public BoneData (String name, BoneData parent) { public BoneData (int index, String name, BoneData parent) {
if (name == null) throw new ArgumentNullException("name cannot be null."); if (index < 0) throw new ArgumentException("index must be >= 0", "index");
if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
this.index = index;
this.name = name; this.name = name;
this.parent = parent; this.parent = parent;
} }

View File

@ -40,6 +40,7 @@ namespace Spine {
public float Time { get; private set; } public float Time { get; private set; }
public Event (float time, EventData data) { public Event (float time, EventData data) {
if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
Time = time; Time = time;
Data = data; Data = data;
} }

View File

@ -41,7 +41,7 @@ namespace Spine {
public String String { get; set; } public String String { get; set; }
public EventData (String name) { public EventData (String name) {
if (name == null) throw new ArgumentNullException("name cannot be null."); if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
this.name = name; this.name = name;
} }

View File

@ -89,6 +89,12 @@ namespace Spine {
Capacity = Math.Max(Math.Max(Capacity * 2, DefaultCapacity), minimumSize); Capacity = Math.Max(Math.Max(Capacity * 2, DefaultCapacity), minimumSize);
} }
public ExposedList<T> Resize (int newSize) {
if (newSize > Items.Length) Array.Resize(ref Items, newSize);
Count = newSize;
return this;
}
private void CheckRange (int idx, int count) { private void CheckRange (int idx, int count) {
if (idx < 0) if (idx < 0)
throw new ArgumentOutOfRangeException("index"); throw new ArgumentOutOfRangeException("index");
@ -580,4 +586,4 @@ namespace Spine {
} }
} }
} }
} }

View File

@ -30,15 +30,16 @@
*****************************************************************************/ *****************************************************************************/
using System; using System;
using System.Collections.Generic;
namespace Spine { namespace Spine {
public class IkConstraint : IUpdatable { public class IkConstraint : IUpdatable {
internal IkConstraintData data; internal IkConstraintData data;
internal ExposedList<Bone> bones = new ExposedList<Bone>(); internal ExposedList<Bone> bones = new ExposedList<Bone>();
internal Bone target; internal Bone target;
internal int bendDirection;
internal float mix; internal float mix;
internal int bendDirection;
internal int level;
public IkConstraintData Data { get { return data; } } public IkConstraintData Data { get { return data; } }
public ExposedList<Bone> Bones { get { return bones; } } public ExposedList<Bone> Bones { get { return bones; } }
@ -47,8 +48,8 @@ namespace Spine {
public float Mix { get { return mix; } set { mix = value; } } public float Mix { get { return mix; } set { mix = value; } }
public IkConstraint (IkConstraintData data, Skeleton skeleton) { public IkConstraint (IkConstraintData data, Skeleton skeleton) {
if (data == null) throw new ArgumentNullException("data cannot be null."); if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null.");
this.data = data; this.data = data;
mix = data.mix; mix = data.mix;
bendDirection = data.bendDirection; bendDirection = data.bendDirection;
@ -87,21 +88,24 @@ namespace Spine {
float id = 1 / (pp.a * pp.d - pp.b * pp.c); float id = 1 / (pp.a * pp.d - pp.b * pp.c);
float x = targetX - pp.worldX, y = targetY - pp.worldY; float x = targetX - pp.worldX, y = targetY - pp.worldY;
float tx = (x * pp.d - y * pp.b) * id - bone.x, ty = (y * pp.a - x * pp.c) * id - bone.y; float tx = (x * pp.d - y * pp.b) * id - bone.x, ty = (y * pp.a - x * pp.c) * id - bone.y;
float rotationIK = MathUtils.Atan2(ty, tx) * MathUtils.radDeg - bone.shearX; float rotationIK = MathUtils.Atan2(ty, tx) * MathUtils.radDeg - bone.shearX - bone.rotation;
if (bone.scaleX < 0) rotationIK += 180; if (bone.scaleX < 0) rotationIK += 180;
if (rotationIK > 180) if (rotationIK > 180)
rotationIK -= 360; rotationIK -= 360;
else if (rotationIK < -180) rotationIK += 360; else if (rotationIK < -180) rotationIK += 360;
bone.UpdateWorldTransform(bone.x, bone.y, bone.rotation + (rotationIK - bone.rotation) * alpha, bone.appliedScaleX, bone.UpdateWorldTransform(bone.x, bone.y, bone.rotation + rotationIK * alpha, bone.scaleX, bone.scaleY,
bone.appliedScaleY, bone.shearX, bone.shearY); bone.shearX, bone.shearY);
} }
/// <summary>Adjusts the parent and child bone rotations so the tip of the child is as close to the target position as /// <summary>Adjusts the parent and child bone rotations so the tip of the child is as close to the target position as
/// possible. The target is specified in the world coordinate system.</summary> /// possible. The target is specified in the world coordinate system.</summary>
/// <param name="child">A direct descendant of the parent bone.</param> /// <param name="child">A direct descendant of the parent bone.</param>
static public void Apply (Bone parent, Bone child, float targetX, float targetY, int bendDir, float alpha) { static public void Apply (Bone parent, Bone child, float targetX, float targetY, int bendDir, float alpha) {
if (alpha == 0) return; if (alpha == 0) {
float px = parent.x, py = parent.y, psx = parent.appliedScaleX, psy = parent.appliedScaleY; child.UpdateWorldTransform ();
return;
}
float px = parent.x, py = parent.y, psx = parent.scaleX, psy = parent.scaleY, csx = child.scaleX;
int os1, os2, s2; int os1, os2, s2;
if (psx < 0) { if (psx < 0) {
psx = -psx; psx = -psx;
@ -115,44 +119,55 @@ namespace Spine {
psy = -psy; psy = -psy;
s2 = -s2; s2 = -s2;
} }
float cx = child.x, cy = child.y, csx = child.appliedScaleX;
bool u = Math.Abs(psx - psy) <= 0.0001f;
if (!u && cy != 0) {
child.worldX = parent.a * cx + parent.worldX;
child.worldY = parent.c * cx + parent.worldY;
cy = 0;
}
if (csx < 0) { if (csx < 0) {
csx = -csx; csx = -csx;
os2 = 180; os2 = 180;
} else } else
os2 = 0; os2 = 0;
float cx = child.x, cy, cwx, cwy, a = parent.a, b = parent.b, c = parent.c, d = parent.d;
bool u = Math.Abs(psx - psy) <= 0.0001f;
if (!u) {
cy = 0;
cwx = a * cx + parent.worldX;
cwy = c * cx + parent.worldY;
} else {
cy = child.y;
cwx = a * cx + b * cy + parent.worldX;
cwy = c * cx + d * cy + parent.worldY;
}
Bone pp = parent.parent; Bone pp = parent.parent;
float ppa = pp.a, ppb = pp.b, ppc = pp.c, ppd = pp.d, id = 1 / (ppa * ppd - ppb * ppc); a = pp.a;
float x = targetX - pp.worldX, y = targetY - pp.worldY; b = pp.b;
float tx = (x * ppd - y * ppb) * id - px, ty = (y * ppa - x * ppc) * id - py; c = pp.c;
x = child.worldX - pp.worldX; d = pp.d;
y = child.worldY - pp.worldY; float id = 1 / (a * d - b * c), x = targetX - pp.worldX, y = targetY - pp.worldY;
float dx = (x * ppd - y * ppb) * id - px, dy = (y * ppa - x * ppc) * id - py; float tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py;
x = cwx - pp.worldX;
y = cwy - pp.worldY;
float dx = (x * d - y * b) * id - px, dy = (y * a - x * c) * id - py;
float l1 = (float)Math.Sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2; float l1 = (float)Math.Sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2;
if (u) { if (u) {
l2 *= psx; l2 *= psx;
float cos = (tx * tx + ty * ty - l1 * l1 - l2 * l2) / (2 * l1 * l2); float cos = (tx * tx + ty * ty - l1 * l1 - l2 * l2) / (2 * l1 * l2);
if (cos < -1) cos = -1; if (cos < -1)
cos = -1;
else if (cos > 1) cos = 1; else if (cos > 1) cos = 1;
a2 = (float)Math.Acos(cos) * bendDir; a2 = (float)Math.Acos(cos) * bendDir;
float a = l1 + l2 * cos, o = l2 * MathUtils.Sin(a2); a = l1 + l2 * cos;
a1 = MathUtils.Atan2(ty * a - tx * o, tx * a + ty * o); b = l2 * MathUtils.Sin(a2);
a1 = MathUtils.Atan2(ty * a - tx * b, tx * a + ty * b);
} else { } else {
float a = psx * l2, b = psy * l2, ta = MathUtils.Atan2(ty, tx); a = psx * l2;
float aa = a * a, bb = b * b, ll = l1 * l1, dd = tx * tx + ty * ty; b = psy * l2;
float c0 = bb * ll + aa * dd - aa * bb, c1 = -2 * bb * l1, c2 = bb - aa; float aa = a * a, bb = b * b, dd = tx * tx + ty * ty, ta = MathUtils.Atan2(ty, tx);
float d = c1 * c1 - 4 * c2 * c0; c = bb * l1 * l1 + aa * dd - aa * bb;
float c1 = -2 * bb * l1, c2 = bb - aa;
d = c1 * c1 - 4 * c2 * c;
if (d >= 0) { if (d >= 0) {
float q = (float)Math.Sqrt(d); float q = (float)Math.Sqrt(d);
if (c1 < 0) q = -q; if (c1 < 0) q = -q;
q = -(c1 + q) / 2; q = -(c1 + q) / 2;
float r0 = q / c2, r1 = c0 / q; float r0 = q / c2, r1 = c / q;
float r = Math.Abs(r0) < Math.Abs(r1) ? r0 : r1; float r = Math.Abs(r0) < Math.Abs(r1) ? r0 : r1;
if (r * r <= dd) { if (r * r <= dd) {
y = (float)Math.Sqrt(dd - r * r) * bendDir; y = (float)Math.Sqrt(dd - r * r) * bendDir;
@ -201,18 +216,20 @@ namespace Spine {
a2 = maxAngle * bendDir; a2 = maxAngle * bendDir;
} }
} }
outer: outer:
float os = MathUtils.Atan2(cy, cx) * s2; float os = MathUtils.Atan2(cy, cx) * s2;
a1 = (a1 - os) * MathUtils.radDeg + os1;
a2 = ((a2 + os) * MathUtils.radDeg - child.shearX) * s2 + os2;
if (a1 > 180) a1 -= 360;
else if (a1 < -180) a1 += 360;
if (a2 > 180) a2 -= 360;
else if (a2 < -180) a2 += 360;
float rotation = parent.rotation; float rotation = parent.rotation;
parent.UpdateWorldTransform(px, py, rotation + (a1 - rotation) * alpha, parent.appliedScaleX, parent.appliedScaleY, 0, 0); a1 = (a1 - os) * MathUtils.radDeg + os1 - rotation;
if (a1 > 180)
a1 -= 360;
else if (a1 < -180) a1 += 360;
parent.UpdateWorldTransform(px, py, rotation + a1 * alpha, parent.scaleX, parent.scaleY, 0, 0);
rotation = child.rotation; rotation = child.rotation;
child.UpdateWorldTransform(cx, cy, rotation + (a2 - rotation) * alpha, child.appliedScaleX, child.appliedScaleY, child.shearX, child.shearY); a2 = ((a2 + os) * MathUtils.radDeg - child.shearX) * s2 + os2 - rotation;
if (a2 > 180)
a2 -= 360;
else if (a2 < -180) a2 += 360;
child.UpdateWorldTransform(cx, cy, rotation + a2 * alpha, child.scaleX, child.scaleY, child.shearX, child.shearY);
} }
} }
} }

View File

@ -47,7 +47,7 @@ namespace Spine {
public float Mix { get { return mix; } set { mix = value; } } public float Mix { get { return mix; } set { mix = value; } }
public IkConstraintData (String name) { public IkConstraintData (String name) {
if (name == null) throw new ArgumentNullException("name cannot be null."); if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
this.name = name; this.name = name;
} }

View File

@ -54,22 +54,22 @@ namespace Spine {
sin[(int)(i * degToIndex) & SIN_MASK] = (float)Math.Sin(i * degRad); sin[(int)(i * degToIndex) & SIN_MASK] = (float)Math.Sin(i * degRad);
} }
/** Returns the sine in radians from a lookup table. */ /// <summary>Returns the sine in radians from a lookup table.</summary>
static public float Sin (float radians) { static public float Sin (float radians) {
return sin[(int)(radians * radToIndex) & SIN_MASK]; return sin[(int)(radians * radToIndex) & SIN_MASK];
} }
/** Returns the cosine in radians from a lookup table. */ /// <summary>Returns the cosine in radians from a lookup table.</summary>
static public float Cos (float radians) { static public float Cos (float radians) {
return sin[(int)((radians + PI / 2) * radToIndex) & SIN_MASK]; return sin[(int)((radians + PI / 2) * radToIndex) & SIN_MASK];
} }
/** Returns the sine in radians from a lookup table. */ /// <summary>Returns the sine in radians from a lookup table.</summary>
static public float SinDeg (float degrees) { static public float SinDeg (float degrees) {
return sin[(int)(degrees * degToIndex) & SIN_MASK]; return sin[(int)(degrees * degToIndex) & SIN_MASK];
} }
/** Returns the cosine in radians from a lookup table. */ /// <summary>Returns the cosine in radians from a lookup table.</summary>
static public float CosDeg (float degrees) { static public float CosDeg (float degrees) {
return sin[(int)((degrees + 90) * degToIndex) & SIN_MASK]; return sin[(int)((degrees + 90) * degToIndex) & SIN_MASK];
} }
@ -91,5 +91,11 @@ namespace Spine {
atan = PI / 2 - z / (z * z + 0.28f); atan = PI / 2 - z / (z * z + 0.28f);
return y < 0f ? atan - PI : atan; return y < 0f ? atan - PI : atan;
} }
static public float Clamp (float value, float min, float max) {
if (value < min) return min;
if (value > max) return max;
return value;
}
} }
} }

View File

@ -0,0 +1,400 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.3
*
* Copyright (c) 2013-2015, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable and
* non-transferable license to use, install, execute and perform the Spine
* Runtimes Software (the "Software") and derivative works solely for personal
* or internal use. Without the written permission of Esoteric Software (see
* Section 2 of the Spine Software License Agreement), you may not (a) modify,
* translate, adapt or otherwise create derivative works, improvements of the
* Software or develop new applications using the Software or (b) remove,
* delete, alter or obscure any trademarks or any copyright, trademark, patent
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "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 ESOTERIC SOFTWARE 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 PathConstraint : IUpdatable {
private const int NONE = -1, BEFORE = -2, AFTER = -3;
internal PathConstraintData data;
internal ExposedList<Bone> bones;
internal Slot target;
internal float position, spacing, rotateMix, translateMix;
internal ExposedList<float> spaces = new ExposedList<float>(), positions = new ExposedList<float>();
internal ExposedList<float> world = new ExposedList<float>(), curves = new ExposedList<float>(), lengths = new ExposedList<float>();
internal float[] segments = new float[10];
public float Position { get { return position; } set { position = value; } }
public float Spacing { get { return spacing; } set { spacing = value; } }
public float RotateMix { get { return rotateMix; } set { rotateMix = value; } }
public float TranslateMix { get { return translateMix; } set { translateMix = value; } }
public ExposedList<Bone> Bones { get { return bones; } }
public Slot Target { get { return target; } set { target = value; } }
public PathConstraintData Data { get { return data; } }
public PathConstraint (PathConstraintData data, Skeleton skeleton) {
if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null.");
this.data = data;
bones = new ExposedList<Bone>(data.Bones.Count);
foreach (BoneData boneData in data.bones)
bones.Add(skeleton.FindBone(boneData.name));
target = skeleton.FindSlot(data.target.name);
position = data.position;
spacing = data.spacing;
rotateMix = data.rotateMix;
translateMix = data.translateMix;
}
public void Apply () {
Update();
}
public void Update () {
PathAttachment attachment = target.Attachment as PathAttachment;
if (attachment == null) return;
float rotateMix = this.rotateMix, translateMix = this.translateMix;
bool translate = translateMix > 0, rotate = rotateMix > 0;
if (!translate && !rotate) return;
PathConstraintData data = this.data;
SpacingMode spacingMode = data.spacingMode;
bool lengthSpacing = spacingMode == SpacingMode.Length;
RotateMode rotateMode = data.rotateMode;
bool tangents = rotateMode == RotateMode.Tangent, scale = rotateMode == RotateMode.ChainScale;
int boneCount = this.bones.Count, spacesCount = tangents ? boneCount : boneCount + 1;
Bone[] bones = this.bones.Items;
ExposedList<float> spaces = this.spaces.Resize(spacesCount), lengths = null;
float spacing = this.spacing;
if (scale || lengthSpacing) {
if (scale) lengths = this.lengths.Resize(boneCount);
for (int i = 0, n = spacesCount - 1; i < n;) {
Bone bone = bones[i];
float length = bone.data.length, x = length * bone.a, y = length * bone.c;
length = (float)Math.Sqrt(x * x + y * y);
if (scale) lengths.Items[i] = length;
spaces.Items[++i] = lengthSpacing ? Math.Max(0, length + spacing) : spacing;
}
} else {
for (int i = 1; i < spacesCount; i++)
spaces.Items[i] = spacing;
}
float[] positions = ComputeWorldPositions(attachment, spacesCount, tangents,
data.positionMode == PositionMode.Percent, spacingMode == SpacingMode.Percent);
Skeleton skeleton = target.Skeleton;
float skeletonX = skeleton.x, skeletonY = skeleton.y;
float boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation;
bool tip = rotateMode == RotateMode.Chain && offsetRotation == 0;
for (int i = 0, p = 3; i < boneCount; i++, p += 3) {
Bone bone = (Bone)bones[i];
bone.worldX += (boneX - skeletonX - bone.worldX) * translateMix;
bone.worldY += (boneY - skeletonY - bone.worldY) * translateMix;
float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY;
if (scale) {
float length = lengths.Items[i];
if (length != 0) {
float s = ((float)Math.Sqrt(dx * dx + dy * dy) / length - 1) * rotateMix + 1;
bone.a *= s;
bone.c *= s;
}
}
boneX = x;
boneY = y;
if (rotate) {
float a = bone.a, b = bone.b, c = bone.c, d = bone.d, r, cos, sin;
if (tangents)
r = positions[p - 1];
else if (spaces.Items[i + 1] == 0)
r = positions[p + 2];
else
r = MathUtils.Atan2(dy, dx);
r -= MathUtils.Atan2(c, a) - offsetRotation * MathUtils.degRad;
if (tip) {
cos = MathUtils.Cos(r);
sin = MathUtils.Sin(r);
float length = bone.data.length;
boneX += (length * (cos * a - sin * c) - dx) * rotateMix;
boneY += (length * (sin * a + cos * c) - dy) * rotateMix;
}
if (r > MathUtils.PI)
r -= MathUtils.PI2;
else if (r < -MathUtils.PI) //
r += MathUtils.PI2;
r *= rotateMix;
cos = MathUtils.Cos(r);
sin = MathUtils.Sin(r);
bone.a = cos * a - sin * c;
bone.b = cos * b - sin * d;
bone.c = sin * a + cos * c;
bone.d = sin * b + cos * d;
}
}
}
float[] ComputeWorldPositions (PathAttachment path, int spacesCount, bool tangents, bool percentPosition,
bool percentSpacing) {
Slot target = this.target;
float position = this.position;
float[] spaces = this.spaces.Items, output = this.positions.Resize(spacesCount * 3 + 2).Items, world;
bool closed = path.Closed;
int verticesLength = path.WorldVerticesLength, curveCount = verticesLength / 6, prevCurve = NONE;
float pathLength;
if (!path.ConstantSpeed) {
float[] lengths = path.Lengths;
curveCount -= closed ? 1 : 2;
pathLength = lengths[curveCount];
if (percentPosition) position *= pathLength;
if (percentSpacing) {
for (int i = 0; i < spacesCount; i++)
spaces[i] *= pathLength;
}
world = this.world.Resize(8).Items;
for (int i = 0, o = 0, curve = 0; i < spacesCount; i++, o += 3) {
float space = spaces[i];
position += space;
float p = position;
if (closed) {
p %= pathLength;
if (p < 0) p += pathLength;
curve = 0;
} else if (p < 0) {
if (prevCurve != BEFORE) {
prevCurve = BEFORE;
path.ComputeWorldVertices(target, 2, 4, world, 0);
}
AddBeforePosition(p, world, 0, output, o);
continue;
} else if (p > pathLength) {
if (prevCurve != AFTER) {
prevCurve = AFTER;
path.ComputeWorldVertices(target, verticesLength - 6, 4, world, 0);
}
AddAfterPosition(p - pathLength, world, 0, output, o);
continue;
}
// Determine curve containing position.
for (;; curve++) {
float length = lengths[curve];
if (p > length) continue;
if (curve == 0)
p /= length;
else {
float prev = lengths[curve - 1];
p = (p - prev) / (length - prev);
}
break;
}
if (curve != prevCurve) {
prevCurve = curve;
if (closed && curve == curveCount) {
path.ComputeWorldVertices(target, verticesLength - 4, 4, world, 0);
path.ComputeWorldVertices(target, 0, 4, world, 4);
} else
path.ComputeWorldVertices(target, curve * 6 + 2, 8, world, 0);
}
AddCurvePosition(p, world[0], world[1], world[2], world[3], world[4], world[5], world[6], world[7], output, o,
tangents || (i > 0 && space == 0));
}
return output;
}
// World vertices.
if (closed) {
verticesLength += 2;
world = this.world.Resize(verticesLength).Items;
path.ComputeWorldVertices(target, 2, verticesLength - 4, world, 0);
path.ComputeWorldVertices(target, 0, 2, world, verticesLength - 4);
world[verticesLength - 2] = world[0];
world[verticesLength - 1] = world[1];
} else {
curveCount--;
verticesLength -= 4;
world = this.world.Resize(verticesLength).Items;
path.ComputeWorldVertices(target, 2, verticesLength, world, 0);
}
// Curve lengths.
float[] curves = this.curves.Resize(curveCount).Items;
pathLength = 0;
float x1 = world[0], y1 = world[1], cx1 = 0, cy1 = 0, cx2 = 0, cy2 = 0, x2 = 0, y2 = 0;
float tmpx, tmpy, dddfx, dddfy, ddfx, ddfy, dfx, dfy;
for (int i = 0, w = 2; i < curveCount; i++, w += 6) {
cx1 = world[w];
cy1 = world[w + 1];
cx2 = world[w + 2];
cy2 = world[w + 3];
x2 = world[w + 4];
y2 = world[w + 5];
tmpx = (x1 - cx1 * 2 + cx2) * 0.1875f;
tmpy = (y1 - cy1 * 2 + cy2) * 0.1875f;
dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.09375f;
dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.09375f;
ddfx = tmpx * 2 + dddfx;
ddfy = tmpy * 2 + dddfy;
dfx = (cx1 - x1) * 0.75f + tmpx + dddfx * 0.16666667f;
dfy = (cy1 - y1) * 0.75f + tmpy + dddfy * 0.16666667f;
pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy);
dfx += ddfx;
dfy += ddfy;
ddfx += dddfx;
ddfy += dddfy;
pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy);
dfx += ddfx;
dfy += ddfy;
pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy);
dfx += ddfx + dddfx;
dfy += ddfy + dddfy;
pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy);
curves[i] = pathLength;
x1 = x2;
y1 = y2;
}
if (percentPosition) position *= pathLength;
if (percentSpacing) {
for (int i = 0; i < spacesCount; i++)
spaces[i] *= pathLength;
}
float[] segments = this.segments;
float curveLength = 0;
for (int i = 0, o = 0, curve = 0, segment = 0; i < spacesCount; i++, o += 3) {
float space = spaces[i];
position += space;
float p = position;
if (closed) {
p %= pathLength;
if (p < 0) p += pathLength;
curve = 0;
} else if (p < 0) {
AddBeforePosition(p, world, 0, output, o);
continue;
} else if (p > pathLength) {
AddAfterPosition(p - pathLength, world, verticesLength - 4, output, o);
continue;
}
// Determine curve containing position.
for (;; curve++) {
float length = curves[curve];
if (p > length) continue;
if (curve == 0)
p /= length;
else {
float prev = curves[curve - 1];
p = (p - prev) / (length - prev);
}
break;
}
// Curve segment lengths.
if (curve != prevCurve) {
prevCurve = curve;
int ii = curve * 6;
x1 = world[ii];
y1 = world[ii + 1];
cx1 = world[ii + 2];
cy1 = world[ii + 3];
cx2 = world[ii + 4];
cy2 = world[ii + 5];
x2 = world[ii + 6];
y2 = world[ii + 7];
tmpx = (x1 - cx1 * 2 + cx2) * 0.03f;
tmpy = (y1 - cy1 * 2 + cy2) * 0.03f;
dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.006f;
dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.006f;
ddfx = tmpx * 2 + dddfx;
ddfy = tmpy * 2 + dddfy;
dfx = (cx1 - x1) * 0.3f + tmpx + dddfx * 0.16666667f;
dfy = (cy1 - y1) * 0.3f + tmpy + dddfy * 0.16666667f;
curveLength = (float)Math.Sqrt(dfx * dfx + dfy * dfy);
segments[0] = curveLength;
for (ii = 1; ii < 8; ii++) {
dfx += ddfx;
dfy += ddfy;
ddfx += dddfx;
ddfy += dddfy;
curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy);
segments[ii] = curveLength;
}
dfx += ddfx;
dfy += ddfy;
curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy);
segments[8] = curveLength;
dfx += ddfx + dddfx;
dfy += ddfy + dddfy;
curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy);
segments[9] = curveLength;
segment = 0;
}
// Weight by segment length.
p *= curveLength;
for (;; segment++) {
float length = segments[segment];
if (p > length) continue;
if (segment == 0)
p /= length;
else {
float prev = segments[segment - 1];
p = segment + (p - prev) / (length - prev);
}
break;
}
AddCurvePosition(p * 0.1f, x1, y1, cx1, cy1, cx2, cy2, x2, y2, output, o, tangents || (i > 0 && space == 0));
}
return output;
}
private void AddBeforePosition (float p, float[] temp, int i, float[] output, int o) {
float x1 = temp[i], y1 = temp[i + 1], dx = temp[i + 2] - x1, dy = temp[i + 3] - y1, r = MathUtils.Atan2(dy, dx);
output[o] = x1 + p * MathUtils.Cos(r);
output[o + 1] = y1 + p * MathUtils.Sin(r);
output[o + 2] = r;
}
private void AddAfterPosition (float p, float[] temp, int i, float[] output, int o) {
float x1 = temp[i + 2], y1 = temp[i + 3], dx = x1 - temp[i], dy = y1 - temp[i + 1], r = MathUtils.Atan2(dy, dx);
output[o] = x1 + p * MathUtils.Cos(r);
output[o + 1] = y1 + p * MathUtils.Sin(r);
output[o + 2] = r;
}
private void AddCurvePosition (float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2,
float[] output, int o, bool tangents) {
if (p == 0) p = 0.0001f;
float tt = p * p, ttt = tt * p, u = 1 - p, uu = u * u, uuu = uu * u;
float ut = u * p, ut3 = ut * 3, uut3 = u * ut3, utt3 = ut3 * p;
float x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt, y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt;
output[o] = x;
output[o + 1] = y;
if (tangents) output[o + 2] = (float)Math.Atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt));
}
}
}

View File

@ -0,0 +1,74 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.3
*
* Copyright (c) 2013-2015, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable and
* non-transferable license to use, install, execute and perform the Spine
* Runtimes Software (the "Software") and derivative works solely for personal
* or internal use. Without the written permission of Esoteric Software (see
* Section 2 of the Spine Software License Agreement), you may not (a) modify,
* translate, adapt or otherwise create derivative works, improvements of the
* Software or develop new applications using the Software or (b) remove,
* delete, alter or obscure any trademarks or any copyright, trademark, patent
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "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 ESOTERIC SOFTWARE 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 PathConstraintData {
internal String name;
internal ExposedList<BoneData> bones = new ExposedList<BoneData>();
internal SlotData target;
internal PositionMode positionMode;
internal SpacingMode spacingMode;
internal RotateMode rotateMode;
internal float offsetRotation;
internal float position, spacing, rotateMix, translateMix;
public ExposedList<BoneData> Bones { get { return bones; } }
public SlotData Target { get { return target; } set { target = value; } }
public PositionMode PositionMode { get { return positionMode; } set { positionMode = value; } }
public SpacingMode SpacingMode { get { return spacingMode; } set { spacingMode = value; } }
public RotateMode RotateMode { get { return rotateMode; } set { rotateMode = value; } }
public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } }
public float Position { get { return position; } set { position = value; } }
public float Spacing { get { return spacing; } set { spacing = value; } }
public float RotateMix { get { return rotateMix; } set { rotateMix = value; } }
public float TranslateMix { get { return translateMix; } set { translateMix = value; } }
public String Name { get { return name; } }
public PathConstraintData (String name) {
if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
this.name = name;
}
}
public enum PositionMode {
Fixed, Percent
}
public enum SpacingMode {
Length, Fixed, Percent
}
public enum RotateMode {
Tangent, Chain, ChainScale
}
}

View File

@ -38,9 +38,10 @@ namespace Spine {
internal ExposedList<Bone> bones; internal ExposedList<Bone> bones;
internal ExposedList<Slot> slots; internal ExposedList<Slot> slots;
internal ExposedList<Slot> drawOrder; internal ExposedList<Slot> drawOrder;
internal ExposedList<IkConstraint> ikConstraints; internal ExposedList<IkConstraint> ikConstraints, ikConstraintsSorted;
internal ExposedList<TransformConstraint> transformConstraints; internal ExposedList<TransformConstraint> transformConstraints;
private ExposedList<IUpdatable> updateCache = new ExposedList<IUpdatable>(); internal ExposedList<PathConstraint> pathConstraints;
internal ExposedList<IUpdatable> updateCache = new ExposedList<IUpdatable>();
internal Skin skin; internal Skin skin;
internal float r = 1, g = 1, b = 1, a = 1; internal float r = 1, g = 1, b = 1, a = 1;
internal float time; internal float time;
@ -49,9 +50,12 @@ namespace Spine {
public SkeletonData Data { get { return data; } } public SkeletonData Data { get { return data; } }
public ExposedList<Bone> Bones { get { return bones; } } public ExposedList<Bone> Bones { get { return bones; } }
public ExposedList<IUpdatable> UpdateCacheList { get { return updateCache; } }
public ExposedList<Slot> Slots { get { return slots; } } public ExposedList<Slot> Slots { get { return slots; } }
public ExposedList<Slot> DrawOrder { get { return drawOrder; } } public ExposedList<Slot> DrawOrder { get { return drawOrder; } }
public ExposedList<IkConstraint> IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } } public ExposedList<IkConstraint> IkConstraints { get { return ikConstraints; } }
public ExposedList<PathConstraint> PathConstraints { get { return pathConstraints; } }
public ExposedList<TransformConstraint> TransformConstraints { get { return transformConstraints; } }
public Skin Skin { get { return skin; } set { skin = value; } } public Skin Skin { get { return skin; } set { skin = value; } }
public float R { get { return r; } set { r = value; } } public float R { get { return r; } set { r = value; } }
public float G { get { return g; } set { g = value; } } public float G { get { return g; } set { g = value; } }
@ -64,33 +68,37 @@ namespace Spine {
public bool FlipY { get { return flipY; } set { flipY = value; } } public bool FlipY { get { return flipY; } set { flipY = value; } }
public Bone RootBone { public Bone RootBone {
get { get { return bones.Count == 0 ? null : bones.Items[0]; }
return bones.Count == 0 ? null : bones.Items[0];
}
} }
public Skeleton (SkeletonData data) { public Skeleton (SkeletonData data) {
if (data == null) throw new ArgumentNullException("data cannot be null."); if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
this.data = data; this.data = data;
bones = new ExposedList<Bone>(data.bones.Count); bones = new ExposedList<Bone>(data.bones.Count);
foreach (BoneData boneData in data.bones) { foreach (BoneData boneData in data.bones) {
Bone parent = boneData.parent == null ? null : bones.Items[data.bones.IndexOf(boneData.parent)]; Bone bone;
Bone bone = new Bone(boneData, this, parent); if (boneData.parent == null) {
if (parent != null) parent.children.Add(bone); bone = new Bone (boneData, this, null);
} else {
Bone parent = bones.Items[boneData.parent.index];
bone = new Bone (boneData, this, parent);
parent.children.Add (bone);
}
bones.Add(bone); bones.Add(bone);
} }
slots = new ExposedList<Slot>(data.slots.Count); slots = new ExposedList<Slot>(data.slots.Count);
drawOrder = new ExposedList<Slot>(data.slots.Count); drawOrder = new ExposedList<Slot>(data.slots.Count);
foreach (SlotData slotData in data.slots) { foreach (SlotData slotData in data.slots) {
Bone bone = bones.Items[data.bones.IndexOf(slotData.boneData)]; Bone bone = bones.Items[slotData.boneData.index];
Slot slot = new Slot(slotData, bone); Slot slot = new Slot(slotData, bone);
slots.Add(slot); slots.Add(slot);
drawOrder.Add(slot); drawOrder.Add(slot);
} }
ikConstraints = new ExposedList<IkConstraint>(data.ikConstraints.Count); ikConstraints = new ExposedList<IkConstraint>(data.ikConstraints.Count);
ikConstraintsSorted = new ExposedList<IkConstraint>(data.ikConstraints.Count);
foreach (IkConstraintData ikConstraintData in data.ikConstraints) foreach (IkConstraintData ikConstraintData in data.ikConstraints)
ikConstraints.Add(new IkConstraint(ikConstraintData, this)); ikConstraints.Add(new IkConstraint(ikConstraintData, this));
@ -98,46 +106,150 @@ namespace Spine {
foreach (TransformConstraintData transformConstraintData in data.transformConstraints) foreach (TransformConstraintData transformConstraintData in data.transformConstraints)
transformConstraints.Add(new TransformConstraint(transformConstraintData, this)); transformConstraints.Add(new TransformConstraint(transformConstraintData, this));
pathConstraints = new ExposedList<PathConstraint> (data.pathConstraints.Count);
foreach (PathConstraintData pathConstraintData in data.pathConstraints)
pathConstraints.Add(new PathConstraint(pathConstraintData, this));
UpdateCache(); UpdateCache();
UpdateWorldTransform(); UpdateWorldTransform();
} }
/// <summary>Caches information about bones and constraints. Must be called if bones or constraints are added /// <summary>Caches information about bones and constraints. Must be called if bones, constraints or weighted path attachments are added
/// or removed.</summary> /// or removed.</summary>
public void UpdateCache () { public void UpdateCache () {
ExposedList<Bone> bones = this.bones;
ExposedList<IUpdatable> updateCache = this.updateCache; ExposedList<IUpdatable> updateCache = this.updateCache;
ExposedList<IkConstraint> ikConstraints = this.ikConstraints;
ExposedList<TransformConstraint> transformConstraints = this.transformConstraints;
int ikConstraintsCount = ikConstraints.Count;
int transformConstraintsCount = transformConstraints.Count;
updateCache.Clear(); updateCache.Clear();
for (int i = 0, n = bones.Count; i < n; i++) {
Bone bone = bones.Items[i]; ExposedList<Bone> bones = this.bones;
updateCache.Add(bone); for (int i = 0, n = bones.Count; i < n; i++)
for (int ii = 0; ii < ikConstraintsCount; ii++) { bones.Items[i].sorted = false;
IkConstraint ikConstraint = ikConstraints.Items[ii];
if (bone == ikConstraint.bones.Items[ikConstraint.bones.Count - 1]) { ExposedList<IkConstraint> ikConstraints = this.ikConstraintsSorted;
updateCache.Add(ikConstraint); ikConstraints.Clear();
break; ikConstraints.AddRange(this.ikConstraints);
} int ikCount = ikConstraints.Count;
for (int i = 0, level, n = ikCount; i < n; i++) {
IkConstraint ik = ikConstraints.Items[i];
Bone bone = ik.bones.Items[0].Parent;
for (level = 0; bone != null; level++)
bone = bone.Parent;
ik.level = level;
}
for (int i = 1, ii; i < ikCount; i++) {
IkConstraint ik = ikConstraints.Items [i];
int level = ik.level;
for (ii = i - 1; ii >= 0; ii--) {
IkConstraint other = ikConstraints.Items[ii];
if (other.level < level) break;
ikConstraints.Items[ii + 1] = other;
} }
ikConstraints.Items[ii + 1] = ik;
}
for (int i = 0, n = ikConstraints.Count; i < n; i++) {
IkConstraint constraint = ikConstraints.Items[i];
Bone target = constraint.target;
SortBone(target);
ExposedList<Bone> constrained = constraint.bones;
Bone parent = constrained.Items [0];
SortBone(parent);
updateCache.Add(constraint);
SortReset(parent.children);
constrained.Items[constrained.Count - 1].sorted = true;
} }
for (int i = 0; i < transformConstraintsCount; i++) { ExposedList<PathConstraint> pathConstraints = this.pathConstraints;
TransformConstraint transformConstraint = transformConstraints.Items[i]; for (int i = 0, n = pathConstraints.Count; i < n; i++) {
for (int ii = updateCache.Count - 1; i >= 0; ii--) { PathConstraint constraint = pathConstraints.Items[i];
if (updateCache.Items[ii] == transformConstraint.bone) {
updateCache.Insert(ii + 1, transformConstraint); Slot slot = constraint.Target;
break; int slotIndex = slot.data.index;
} Bone slotBone = slot.bone;
} if (skin != null) SortPathConstraintAttachment(skin, slotIndex, slotBone);
if (data.defaultSkin != null && data.defaultSkin != skin)
SortPathConstraintAttachment(data.defaultSkin, slotIndex, slotBone);
for (int ii = 0, nn = data.skins.Count; ii < nn; ii++)
SortPathConstraintAttachment(data.skins.Items[ii], slotIndex, slotBone);
PathAttachment attachment = slot.Attachment as PathAttachment;
if (attachment != null) SortPathConstraintAttachment(attachment, slotBone);
ExposedList<Bone> constrained = constraint.bones;
int boneCount = constrained.Count;
for (int ii = 0; ii < boneCount; ii++)
SortBone(constrained.Items[ii]);
updateCache.Add(constraint);
for (int ii = 0; ii < boneCount; ii++)
SortReset(constrained.Items[ii].children);
for (int ii = 0; ii < boneCount; ii++)
constrained.Items[ii].sorted = true;
}
ExposedList<TransformConstraint> transformConstraints = this.transformConstraints;
for (int i = 0, n = transformConstraints.Count; i < n; i++) {
TransformConstraint constraint = transformConstraints.Items[i];
SortBone(constraint.target);
ExposedList<Bone> constrained = constraint.bones;
int boneCount = constrained.Count;
for (int ii = 0; ii < boneCount; ii++)
SortBone(constrained.Items[ii]);
updateCache.Add(constraint);
for (int ii = 0; ii < boneCount; ii++)
SortReset(constrained.Items[ii].children);
for (int ii = 0; ii < boneCount; ii++)
constrained.Items[ii].sorted = true;
}
for (int i = 0, n = bones.Count; i < n; i++)
SortBone(bones.Items[i]);
}
private void SortPathConstraintAttachment (Skin skin, int slotIndex, Bone slotBone) {
foreach (var entry in skin.Attachments)
if (entry.Key.slotIndex == slotIndex) SortPathConstraintAttachment(entry.Value, slotBone);
}
private void SortPathConstraintAttachment (Attachment attachment, Bone slotBone) {
var pathAttachment = attachment as PathAttachment;
if (pathAttachment == null) return;
int[] pathBones = pathAttachment.bones;
if (pathBones == null)
SortBone(slotBone);
else {
var bones = this.bones;
for (int i = 0, n = pathBones.Length; i < n; i++)
SortBone(bones.Items[pathBones[i]]);
}
}
private void SortBone (Bone bone) {
if (bone.sorted) return;
Bone parent = bone.parent;
if (parent != null) SortBone(parent);
bone.sorted = true;
updateCache.Add(bone);
}
private void SortReset (ExposedList<Bone> bones) {
var bonesItems = bones.Items;
for (int i = 0, n = bones.Count; i < n; i++) {
Bone bone = bonesItems[i];
if (bone.sorted) SortReset(bone.children);
bone.sorted = false;
} }
} }
/// <summary>Updates the world transform for each bone and applies constraints.</summary> /// <summary>Updates the world transform for each bone and applies constraints.</summary>
public void UpdateWorldTransform () { public void UpdateWorldTransform () {
ExposedList<IUpdatable> updateCache = this.updateCache; var updateCache = this.updateCache;
for (int i = 0, n = updateCache.Count; i < n; i++) for (int i = 0, n = updateCache.Count; i < n; i++)
updateCache.Items[i].Update(); updateCache.Items[i].Update();
} }
@ -150,44 +262,56 @@ namespace Spine {
/// <summary>Sets the bones and constraints to their setup pose values.</summary> /// <summary>Sets the bones and constraints to their setup pose values.</summary>
public void SetBonesToSetupPose () { public void SetBonesToSetupPose () {
ExposedList<Bone> bones = this.bones; var bonesItems = this.bones.Items;
for (int i = 0, n = bones.Count; i < n; i++) for (int i = 0, n = bones.Count; i < n; i++)
bones.Items[i].SetToSetupPose(); bonesItems[i].SetToSetupPose();
ExposedList<IkConstraint> ikConstraints = this.ikConstraints; var ikConstraintsItems = this.ikConstraints.Items;
for (int i = 0, n = ikConstraints.Count; i < n; i++) { for (int i = 0, n = ikConstraints.Count; i < n; i++) {
IkConstraint constraint = ikConstraints.Items[i]; IkConstraint constraint = ikConstraintsItems[i];
constraint.bendDirection = constraint.data.bendDirection; constraint.bendDirection = constraint.data.bendDirection;
constraint.mix = constraint.data.mix; constraint.mix = constraint.data.mix;
} }
ExposedList<TransformConstraint> transformConstraints = this.transformConstraints; var transformConstraintsItems = this.transformConstraints.Items;
for (int i = 0, n = transformConstraints.Count; i < n; i++) { for (int i = 0, n = transformConstraints.Count; i < n; i++) {
TransformConstraint constraint = transformConstraints.Items[i]; TransformConstraint constraint = transformConstraintsItems[i];
TransformConstraintData data = constraint.data; TransformConstraintData data = constraint.data;
constraint.rotateMix = data.rotateMix; constraint.rotateMix = data.rotateMix;
constraint.translateMix = data.translateMix; constraint.translateMix = data.translateMix;
constraint.scaleMix = data.scaleMix; constraint.scaleMix = data.scaleMix;
constraint.shearMix = data.shearMix; constraint.shearMix = data.shearMix;
} }
var pathConstraints = this.pathConstraints;
for (int i = 0, n = pathConstraints.Count; i < n; i++) {
PathConstraint constraint = pathConstraints.Items[i];
PathConstraintData data = constraint.data;
constraint.position = data.position;
constraint.spacing = data.spacing;
constraint.rotateMix = data.rotateMix;
constraint.translateMix = data.translateMix;
}
} }
public void SetSlotsToSetupPose () { public void SetSlotsToSetupPose () {
ExposedList<Slot> slots = this.slots; var slots = this.slots;
var slotsItems = slots.Items;
drawOrder.Clear(); drawOrder.Clear();
for (int i = 0, n = slots.Count; i < n; i++) for (int i = 0, n = slots.Count; i < n; i++)
drawOrder.Add(slots.Items[i]); drawOrder.Add(slotsItems[i]);
for (int i = 0, n = slots.Count; i < n; i++) for (int i = 0, n = slots.Count; i < n; i++)
slots.Items[i].SetToSetupPose(i); slotsItems[i].SetToSetupPose();
} }
/// <returns>May be null.</returns> /// <returns>May be null.</returns>
public Bone FindBone (String boneName) { public Bone FindBone (String boneName) {
if (boneName == null) throw new ArgumentNullException("boneName cannot be null."); if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null.");
ExposedList<Bone> bones = this.bones; var bones = this.bones;
var bonesItems = bones.Items;
for (int i = 0, n = bones.Count; i < n; i++) { for (int i = 0, n = bones.Count; i < n; i++) {
Bone bone = bones.Items[i]; Bone bone = bonesItems[i];
if (bone.data.name == boneName) return bone; if (bone.data.name == boneName) return bone;
} }
return null; return null;
@ -195,19 +319,21 @@ namespace Spine {
/// <returns>-1 if the bone was not found.</returns> /// <returns>-1 if the bone was not found.</returns>
public int FindBoneIndex (String boneName) { public int FindBoneIndex (String boneName) {
if (boneName == null) throw new ArgumentNullException("boneName cannot be null."); if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null.");
ExposedList<Bone> bones = this.bones; var bones = this.bones;
var bonesItems = bones.Items;
for (int i = 0, n = bones.Count; i < n; i++) for (int i = 0, n = bones.Count; i < n; i++)
if (bones.Items[i].data.name == boneName) return i; if (bonesItems[i].data.name == boneName) return i;
return -1; return -1;
} }
/// <returns>May be null.</returns> /// <returns>May be null.</returns>
public Slot FindSlot (String slotName) { public Slot FindSlot (String slotName) {
if (slotName == null) throw new ArgumentNullException("slotName cannot be null."); if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
ExposedList<Slot> slots = this.slots; var slots = this.slots;
var slotsItems = slots.Items;
for (int i = 0, n = slots.Count; i < n; i++) { for (int i = 0, n = slots.Count; i < n; i++) {
Slot slot = slots.Items[i]; Slot slot = slotsItems[i];
if (slot.data.name == slotName) return slot; if (slot.data.name == slotName) return slot;
} }
return null; return null;
@ -215,17 +341,18 @@ namespace Spine {
/// <returns>-1 if the bone was not found.</returns> /// <returns>-1 if the bone was not found.</returns>
public int FindSlotIndex (String slotName) { public int FindSlotIndex (String slotName) {
if (slotName == null) throw new ArgumentNullException("slotName cannot be null."); if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
ExposedList<Slot> slots = this.slots; var slots = this.slots;
var slotsItems = slots.Items;
for (int i = 0, n = slots.Count; i < n; i++) for (int i = 0, n = slots.Count; i < n; i++)
if (slots.Items[i].data.name.Equals(slotName)) return i; if (slotsItems[i].data.name.Equals(slotName)) return i;
return -1; return -1;
} }
/// <summary>Sets a skin by name (see SetSkin).</summary> /// <summary>Sets a skin by name (see SetSkin).</summary>
public void SetSkin (String skinName) { public void SetSkin (String skinName) {
Skin skin = data.FindSkin(skinName); Skin skin = data.FindSkin(skinName);
if (skin == null) throw new ArgumentException("Skin not found: " + skinName); if (skin == null) throw new ArgumentException("Skin not found: " + skinName, "skinName");
SetSkin(skin); SetSkin(skin);
} }
@ -259,7 +386,7 @@ namespace Spine {
/// <returns>May be null.</returns> /// <returns>May be null.</returns>
public Attachment GetAttachment (int slotIndex, String attachmentName) { public Attachment GetAttachment (int slotIndex, String attachmentName) {
if (attachmentName == null) throw new ArgumentNullException("attachmentName cannot be null."); if (attachmentName == null) throw new ArgumentNullException("attachmentName", "attachmentName cannot be null.");
if (skin != null) { if (skin != null) {
Attachment attachment = skin.GetAttachment(slotIndex, attachmentName); Attachment attachment = skin.GetAttachment(slotIndex, attachmentName);
if (attachment != null) return attachment; if (attachment != null) return attachment;
@ -270,7 +397,7 @@ namespace Spine {
/// <param name="attachmentName">May be null.</param> /// <param name="attachmentName">May be null.</param>
public void SetAttachment (String slotName, String attachmentName) { public void SetAttachment (String slotName, String attachmentName) {
if (slotName == null) throw new ArgumentNullException("slotName cannot be null."); if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
ExposedList<Slot> slots = this.slots; ExposedList<Slot> slots = this.slots;
for (int i = 0, n = slots.Count; i < n; i++) { for (int i = 0, n = slots.Count; i < n; i++) {
Slot slot = slots.Items[i]; Slot slot = slots.Items[i];
@ -286,10 +413,10 @@ namespace Spine {
} }
throw new Exception("Slot not found: " + slotName); throw new Exception("Slot not found: " + slotName);
} }
/** @return May be null. */ /// <returns>May be null.</returns>
public IkConstraint FindIkConstraint (String constraintName) { public IkConstraint FindIkConstraint (String constraintName) {
if (constraintName == null) throw new ArgumentNullException("constraintName cannot be null."); if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
ExposedList<IkConstraint> ikConstraints = this.ikConstraints; ExposedList<IkConstraint> ikConstraints = this.ikConstraints;
for (int i = 0, n = ikConstraints.Count; i < n; i++) { for (int i = 0, n = ikConstraints.Count; i < n; i++) {
IkConstraint ikConstraint = ikConstraints.Items[i]; IkConstraint ikConstraint = ikConstraints.Items[i];
@ -298,9 +425,9 @@ namespace Spine {
return null; return null;
} }
/** @return May be null. */ /// <returns>May be null.</returns>
public TransformConstraint FindTransformConstraint (String constraintName) { public TransformConstraint FindTransformConstraint (String constraintName) {
if (constraintName == null) throw new ArgumentNullException("constraintName cannot be null."); if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
ExposedList<TransformConstraint> transformConstraints = this.transformConstraints; ExposedList<TransformConstraint> transformConstraints = this.transformConstraints;
for (int i = 0, n = transformConstraints.Count; i < n; i++) { for (int i = 0, n = transformConstraints.Count; i < n; i++) {
TransformConstraint transformConstraint = transformConstraints.Items[i]; TransformConstraint transformConstraint = transformConstraints.Items[i];
@ -309,6 +436,17 @@ namespace Spine {
return null; return null;
} }
/// <returns>May be null.</returns>
public PathConstraint FindPathConstraint (String constraintName) {
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
ExposedList<PathConstraint> pathConstraints = this.pathConstraints;
for (int i = 0, n = pathConstraints.Count; i < n; i++) {
PathConstraint constraint = pathConstraints.Items[i];
if (constraint.data.name.Equals(constraintName)) return constraint;
}
return null;
}
public void Update (float delta) { public void Update (float delta) {
time += delta; time += delta;
} }

View File

@ -44,12 +44,17 @@ using Windows.Storage;
namespace Spine { namespace Spine {
public class SkeletonBinary { public class SkeletonBinary {
public const int TIMELINE_ROTATE = 0; public const int BONE_ROTATE = 0;
public const int TIMELINE_TRANSLATE = 1; public const int BONE_TRANSLATE = 1;
public const int TIMELINE_SCALE = 2; public const int BONE_SCALE = 2;
public const int TIMELINE_SHEAR = 3; public const int BONE_SHEAR = 3;
public const int TIMELINE_ATTACHMENT = 4;
public const int TIMELINE_COLOR = 5; public const int SLOT_ATTACHMENT = 0;
public const int SLOT_COLOR = 1;
public const int PATH_POSITION = 0;
public const int PATH_SPACING = 1;
public const int PATH_MIX = 2;
public const int CURVE_LINEAR = 0; public const int CURVE_LINEAR = 0;
public const int CURVE_STEPPED = 1; public const int CURVE_STEPPED = 1;
@ -122,55 +127,26 @@ namespace Spine {
for (int i = 0, n = ReadVarint(input, true); i < n; i++) { for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
String name = ReadString(input); String name = ReadString(input);
BoneData parent = i == 0 ? null : skeletonData.bones.Items[ReadVarint(input, true)]; BoneData parent = i == 0 ? null : skeletonData.bones.Items[ReadVarint(input, true)];
BoneData boneData = new BoneData(name, parent); BoneData data = new BoneData(i, name, parent);
boneData.rotation = ReadFloat(input); data.rotation = ReadFloat(input);
boneData.x = ReadFloat(input) * scale; data.x = ReadFloat(input) * scale;
boneData.y = ReadFloat(input) * scale; data.y = ReadFloat(input) * scale;
boneData.scaleX = ReadFloat(input); data.scaleX = ReadFloat(input);
boneData.scaleY = ReadFloat(input); data.scaleY = ReadFloat(input);
boneData.shearX = ReadFloat(input); data.shearX = ReadFloat(input);
boneData.shearY = ReadFloat(input); data.shearY = ReadFloat(input);
boneData.length = ReadFloat(input) * scale; data.length = ReadFloat(input) * scale;
boneData.inheritRotation = ReadBoolean(input); data.inheritRotation = ReadBoolean(input);
boneData.inheritScale = ReadBoolean(input); data.inheritScale = ReadBoolean(input);
if (nonessential) ReadInt(input); // Skip bone color. if (nonessential) ReadInt(input); // Skip bone color.
skeletonData.bones.Add(boneData); skeletonData.bones.Add(data);
}
// IK constraints.
for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
IkConstraintData ikConstraintData = new IkConstraintData(ReadString(input));
for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++)
ikConstraintData.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]);
ikConstraintData.target = skeletonData.bones.Items[ReadVarint(input, true)];
ikConstraintData.mix = ReadFloat(input);
ikConstraintData.bendDirection = ReadSByte(input);
skeletonData.ikConstraints.Add(ikConstraintData);
}
// Transform constraints.
for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
TransformConstraintData transformConstraintData = new TransformConstraintData(ReadString(input));
transformConstraintData.bone = skeletonData.bones.Items[ReadVarint(input, true)];
transformConstraintData.target = skeletonData.bones.Items[ReadVarint(input, true)];
transformConstraintData.offsetRotation = ReadFloat(input);
transformConstraintData.offsetX = ReadFloat(input) * scale;
transformConstraintData.offsetY = ReadFloat(input) * scale;
transformConstraintData.offsetScaleX = ReadFloat(input);
transformConstraintData.offsetScaleY = ReadFloat(input);
transformConstraintData.offsetShearY = ReadFloat(input);
transformConstraintData.rotateMix = ReadFloat(input);
transformConstraintData.translateMix = ReadFloat(input);
transformConstraintData.scaleMix = ReadFloat(input);
transformConstraintData.shearMix = ReadFloat(input);
skeletonData.transformConstraints.Add(transformConstraintData);
} }
// Slots. // Slots.
for (int i = 0, n = ReadVarint(input, true); i < n; i++) { for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
String slotName = ReadString(input); String slotName = ReadString(input);
BoneData boneData = skeletonData.bones.Items[ReadVarint(input, true)]; BoneData boneData = skeletonData.bones.Items[ReadVarint(input, true)];
SlotData slotData = new SlotData(slotName, boneData); SlotData slotData = new SlotData(i, slotName, boneData);
int color = ReadInt(input); int color = ReadInt(input);
slotData.r = ((color & 0xff000000) >> 24) / 255f; slotData.r = ((color & 0xff000000) >> 24) / 255f;
slotData.g = ((color & 0x00ff0000) >> 16) / 255f; slotData.g = ((color & 0x00ff0000) >> 16) / 255f;
@ -181,6 +157,55 @@ namespace Spine {
skeletonData.slots.Add(slotData); skeletonData.slots.Add(slotData);
} }
// IK constraints.
for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
IkConstraintData data = new IkConstraintData(ReadString(input));
for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++)
data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]);
data.target = skeletonData.bones.Items[ReadVarint(input, true)];
data.mix = ReadFloat(input);
data.bendDirection = ReadSByte(input);
skeletonData.ikConstraints.Add(data);
}
// Transform constraints.
for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
TransformConstraintData data = new TransformConstraintData(ReadString(input));
for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++)
data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]);
data.target = skeletonData.bones.Items[ReadVarint(input, true)];
data.offsetRotation = ReadFloat(input);
data.offsetX = ReadFloat(input) * scale;
data.offsetY = ReadFloat(input) * scale;
data.offsetScaleX = ReadFloat(input);
data.offsetScaleY = ReadFloat(input);
data.offsetShearY = ReadFloat(input);
data.rotateMix = ReadFloat(input);
data.translateMix = ReadFloat(input);
data.scaleMix = ReadFloat(input);
data.shearMix = ReadFloat(input);
skeletonData.transformConstraints.Add(data);
}
// Path constraints
for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
PathConstraintData data = new PathConstraintData(ReadString(input));
for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++)
data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]);
data.target = skeletonData.slots.Items[ReadVarint(input, true)];
data.positionMode = (PositionMode)Enum.GetValues(typeof(PositionMode)).GetValue(ReadVarint(input, true));
data.spacingMode = (SpacingMode)Enum.GetValues(typeof(SpacingMode)).GetValue(ReadVarint(input, true));
data.rotateMode = (RotateMode)Enum.GetValues(typeof(RotateMode)).GetValue(ReadVarint(input, true));
data.offsetRotation = ReadFloat(input);
data.position = ReadFloat(input);
if (data.positionMode == PositionMode.Fixed) data.position *= scale;
data.spacing = ReadFloat(input);
if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale;
data.rotateMix = ReadFloat(input);
data.translateMix = ReadFloat(input);
skeletonData.pathConstraints.Add(data);
}
// Default skin. // Default skin.
Skin defaultSkin = ReadSkin(input, "default", nonessential); Skin defaultSkin = ReadSkin(input, "default", nonessential);
if (defaultSkin != null) { if (defaultSkin != null) {
@ -199,25 +224,18 @@ namespace Spine {
if (skin == null) throw new Exception("Skin not found: " + linkedMesh.skin); if (skin == null) throw new Exception("Skin not found: " + linkedMesh.skin);
Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent);
if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent);
if (linkedMesh.mesh is MeshAttachment) { linkedMesh.mesh.ParentMesh = (MeshAttachment)parent;
MeshAttachment mesh = (MeshAttachment)linkedMesh.mesh; linkedMesh.mesh.UpdateUVs();
mesh.ParentMesh = (MeshAttachment)parent;
mesh.UpdateUVs();
} else {
WeightedMeshAttachment mesh = (WeightedMeshAttachment)linkedMesh.mesh;
mesh.ParentMesh = (WeightedMeshAttachment)parent;
mesh.UpdateUVs();
}
} }
linkedMeshes.Clear(); linkedMeshes.Clear();
// Events. // Events.
for (int i = 0, n = ReadVarint(input, true); i < n; i++) { for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
EventData eventData = new EventData(ReadString(input)); EventData data = new EventData(ReadString(input));
eventData.Int = ReadVarint(input, false); data.Int = ReadVarint(input, false);
eventData.Float = ReadFloat(input); data.Float = ReadFloat(input);
eventData.String = ReadString(input); data.String = ReadString(input);
skeletonData.events.Add(eventData); skeletonData.events.Add(data);
} }
// Animations. // Animations.
@ -230,10 +248,12 @@ namespace Spine {
skeletonData.events.TrimExcess(); skeletonData.events.TrimExcess();
skeletonData.animations.TrimExcess(); skeletonData.animations.TrimExcess();
skeletonData.ikConstraints.TrimExcess(); skeletonData.ikConstraints.TrimExcess();
skeletonData.pathConstraints.TrimExcess();
return skeletonData; return skeletonData;
} }
/** @return May be null. */
/// <returns>May be null.</returns>
private Skin ReadSkin (Stream input, String skinName, bool nonessential) { private Skin ReadSkin (Stream input, String skinName, bool nonessential) {
int slotCount = ReadVarint(input, true); int slotCount = ReadVarint(input, true);
if (slotCount == 0) return null; if (slotCount == 0) return null;
@ -256,7 +276,7 @@ namespace Spine {
AttachmentType type = (AttachmentType)input.ReadByte(); AttachmentType type = (AttachmentType)input.ReadByte();
switch (type) { switch (type) {
case AttachmentType.region: { case AttachmentType.Region: {
String path = ReadString(input); String path = ReadString(input);
float rotation = ReadFloat(input); float rotation = ReadFloat(input);
float x = ReadFloat(input); float x = ReadFloat(input);
@ -285,96 +305,25 @@ namespace Spine {
region.UpdateOffset(); region.UpdateOffset();
return region; return region;
} }
case AttachmentType.boundingbox: { case AttachmentType.Boundingbox: {
float[] vertices = ReadFloatArray(input, ReadVarint(input, true) * 2, scale); int vertexCount = ReadVarint(input, true);
Vertices vertices = ReadVertices(input, vertexCount);
if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0; // Avoid unused local warning.
BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name);
if (box == null) return null; if (box == null) return null;
box.vertices = vertices; box.worldVerticesLength = vertexCount << 1;
box.vertices = vertices.vertices;
box.bones = vertices.bones;
return box; return box;
} }
case AttachmentType.mesh: { case AttachmentType.Mesh: {
String path = ReadString(input); String path = ReadString(input);
int color = ReadInt(input); int color = ReadInt(input);
int hullLength = 0; int vertexCount = ReadVarint(input, true);
int verticesLength = ReadVarint(input, true) * 2; float[] uvs = ReadFloatArray(input, vertexCount << 1, 1);
float[] uvs = ReadFloatArray(input, verticesLength, 1);
int[] triangles = ReadShortArray(input); int[] triangles = ReadShortArray(input);
float[] vertices = ReadFloatArray(input, verticesLength, scale); Vertices vertices = ReadVertices(input, vertexCount);
hullLength = ReadVarint(input, true);
int[] edges = null;
float width = 0, height = 0;
if (nonessential) {
edges = ReadShortArray(input);
width = ReadFloat(input);
height = ReadFloat(input);
}
if (path == null) path = name;
MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path);
if (mesh == null) return null;
mesh.Path = path;
mesh.r = ((color & 0xff000000) >> 24) / 255f;
mesh.g = ((color & 0x00ff0000) >> 16) / 255f;
mesh.b = ((color & 0x0000ff00) >> 8) / 255f;
mesh.a = ((color & 0x000000ff)) / 255f;
mesh.vertices = vertices;
mesh.triangles = triangles;
mesh.regionUVs = uvs;
mesh.UpdateUVs();
mesh.HullLength = hullLength;
if (nonessential) {
mesh.Edges = edges;
mesh.Width = width * scale;
mesh.Height = height * scale;
}
return mesh;
}
case AttachmentType.linkedmesh: {
String path = ReadString(input);
int color = ReadInt(input);
String skinName = ReadString(input);
String parent = ReadString(input);
bool inheritFFD = ReadBoolean(input);
float width = 0, height = 0;
if (nonessential) {
width = ReadFloat(input);
height = ReadFloat(input);
}
if (path == null) path = name;
MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path);
if (mesh == null) return null;
mesh.Path = path;
mesh.r = ((color & 0xff000000) >> 24) / 255f;
mesh.g = ((color & 0x00ff0000) >> 16) / 255f;
mesh.b = ((color & 0x0000ff00) >> 8) / 255f;
mesh.a = ((color & 0x000000ff)) / 255f;
mesh.inheritFFD = inheritFFD;
if (nonessential) {
mesh.Width = width * scale;
mesh.Height = height * scale;
}
linkedMeshes.Add(new SkeletonJson.LinkedMesh(mesh, skinName, slotIndex, parent));
return mesh;
}
case AttachmentType.weightedmesh: {
String path = ReadString(input);
int color = ReadInt(input);
int vertexCount = ReadVarint(input, true);
float[] uvs = ReadFloatArray(input, vertexCount * 2, 1);
int[] triangles = ReadShortArray(input);
var weights = new List<float>(uvs.Length * 3 * 3);
var bones = new List<int>(uvs.Length * 3);
for (int i = 0; i < vertexCount; i++) {
int boneCount = (int)ReadFloat(input);
bones.Add(boneCount);
for (int ii = 0; ii < boneCount; ii++) {
bones.Add((int)ReadFloat(input));
weights.Add(ReadFloat(input) * scale);
weights.Add(ReadFloat(input) * scale);
weights.Add(ReadFloat(input));
}
}
int hullLength = ReadVarint(input, true); int hullLength = ReadVarint(input, true);
int[] edges = null; int[] edges = null;
float width = 0, height = 0; float width = 0, height = 0;
@ -385,33 +334,33 @@ namespace Spine {
} }
if (path == null) path = name; if (path == null) path = name;
WeightedMeshAttachment mesh = attachmentLoader.NewWeightedMeshAttachment(skin, name, path); MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path);
if (mesh == null) return null; if (mesh == null) return null;
mesh.Path = path; mesh.Path = path;
mesh.r = ((color & 0xff000000) >> 24) / 255f; mesh.r = ((color & 0xff000000) >> 24) / 255f;
mesh.g = ((color & 0x00ff0000) >> 16) / 255f; mesh.g = ((color & 0x00ff0000) >> 16) / 255f;
mesh.b = ((color & 0x0000ff00) >> 8) / 255f; mesh.b = ((color & 0x0000ff00) >> 8) / 255f;
mesh.a = ((color & 0x000000ff)) / 255f; mesh.a = ((color & 0x000000ff)) / 255f;
mesh.bones = bones.ToArray(); mesh.bones = vertices.bones;
mesh.weights = weights.ToArray(); mesh.vertices = vertices.vertices;
mesh.WorldVerticesLength = vertexCount << 1;
mesh.triangles = triangles; mesh.triangles = triangles;
mesh.regionUVs = uvs; mesh.regionUVs = uvs;
mesh.UpdateUVs(); mesh.UpdateUVs();
mesh.HullLength = hullLength * 2; mesh.HullLength = hullLength << 1;
if (nonessential) { if (nonessential) {
mesh.Edges = edges; mesh.Edges = edges;
mesh.Width = width * scale; mesh.Width = width * scale;
mesh.Height = height * scale; mesh.Height = height * scale;
} }
//
return mesh; return mesh;
} }
case AttachmentType.weightedlinkedmesh: { case AttachmentType.Linkedmesh: {
String path = ReadString(input); String path = ReadString(input);
int color = ReadInt(input); int color = ReadInt(input);
String skinName = ReadString(input); String skinName = ReadString(input);
String parent = ReadString(input); String parent = ReadString(input);
bool inheritFFD = ReadBoolean(input); bool inheritDeform = ReadBoolean(input);
float width = 0, height = 0; float width = 0, height = 0;
if (nonessential) { if (nonessential) {
width = ReadFloat(input); width = ReadFloat(input);
@ -419,14 +368,14 @@ namespace Spine {
} }
if (path == null) path = name; if (path == null) path = name;
WeightedMeshAttachment mesh = attachmentLoader.NewWeightedMeshAttachment(skin, name, path); MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path);
if (mesh == null) return null; if (mesh == null) return null;
mesh.Path = path; mesh.Path = path;
mesh.r = ((color & 0xff000000) >> 24) / 255f; mesh.r = ((color & 0xff000000) >> 24) / 255f;
mesh.g = ((color & 0x00ff0000) >> 16) / 255f; mesh.g = ((color & 0x00ff0000) >> 16) / 255f;
mesh.b = ((color & 0x0000ff00) >> 8) / 255f; mesh.b = ((color & 0x0000ff00) >> 8) / 255f;
mesh.a = ((color & 0x000000ff)) / 255f; mesh.a = ((color & 0x000000ff)) / 255f;
mesh.inheritFFD = inheritFFD; mesh.inheritDeform = inheritDeform;
if (nonessential) { if (nonessential) {
mesh.Width = width * scale; mesh.Width = width * scale;
mesh.Height = height * scale; mesh.Height = height * scale;
@ -434,10 +383,56 @@ namespace Spine {
linkedMeshes.Add(new SkeletonJson.LinkedMesh(mesh, skinName, slotIndex, parent)); linkedMeshes.Add(new SkeletonJson.LinkedMesh(mesh, skinName, slotIndex, parent));
return mesh; return mesh;
} }
case AttachmentType.Path: {
bool closed = ReadBoolean(input);
bool constantSpeed = ReadBoolean(input);
int vertexCount = ReadVarint(input, true);
Vertices vertices = ReadVertices(input, vertexCount);
float[] lengths = new float[vertexCount / 3];
for (int i = 0, n = lengths.Length; i < n; i++)
lengths[i] = ReadFloat(input) * scale;
if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0; // Avoid unused local warning.
PathAttachment path = attachmentLoader.NewPathAttachment(skin, name);
if (path == null) return null;
path.closed = closed;
path.constantSpeed = constantSpeed;
path.worldVerticesLength = vertexCount << 1;
path.vertices = vertices.vertices;
path.bones = vertices.bones;
path.lengths = lengths;
return path;
}
} }
return null; return null;
} }
private Vertices ReadVertices (Stream input, int vertexCount) {
float scale = Scale;
int verticesLength = vertexCount << 1;
Vertices vertices = new Vertices();
if(!ReadBoolean(input)) {
vertices.vertices = ReadFloatArray(input, verticesLength, scale);
return vertices;
}
var weights = new ExposedList<float>(verticesLength * 3 * 3);
var bonesArray = new ExposedList<int>(verticesLength * 3);
for (int i = 0; i < vertexCount; i++) {
int boneCount = ReadVarint(input, true);
bonesArray.Add(boneCount);
for (int ii = 0; ii < boneCount; ii++) {
bonesArray.Add(ReadVarint(input, true));
weights.Add(ReadFloat(input) * scale);
weights.Add(ReadFloat(input) * scale);
weights.Add(ReadFloat(input));
}
}
vertices.vertices = weights.ToArray();
vertices.bones = bonesArray.ToArray();
return vertices;
}
private float[] ReadFloatArray (Stream input, int n, float scale) { private float[] ReadFloatArray (Stream input, int n, float scale) {
float[] array = new float[n]; float[] array = new float[n];
if (scale == 1) { if (scale == 1) {
@ -470,7 +465,7 @@ namespace Spine {
int timelineType = input.ReadByte(); int timelineType = input.ReadByte();
int frameCount = ReadVarint(input, true); int frameCount = ReadVarint(input, true);
switch (timelineType) { switch (timelineType) {
case TIMELINE_COLOR: { case SLOT_COLOR: {
ColorTimeline timeline = new ColorTimeline(frameCount); ColorTimeline timeline = new ColorTimeline(frameCount);
timeline.slotIndex = slotIndex; timeline.slotIndex = slotIndex;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
@ -484,10 +479,10 @@ namespace Spine {
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
} }
timelines.Add(timeline); timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[frameCount * 5 - 5]); duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * ColorTimeline.ENTRIES]);
break; break;
} }
case TIMELINE_ATTACHMENT: { case SLOT_ATTACHMENT: {
AttachmentTimeline timeline = new AttachmentTimeline(frameCount); AttachmentTimeline timeline = new AttachmentTimeline(frameCount);
timeline.slotIndex = slotIndex; timeline.slotIndex = slotIndex;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) for (int frameIndex = 0; frameIndex < frameCount; frameIndex++)
@ -507,7 +502,7 @@ namespace Spine {
int timelineType = input.ReadByte(); int timelineType = input.ReadByte();
int frameCount = ReadVarint(input, true); int frameCount = ReadVarint(input, true);
switch (timelineType) { switch (timelineType) {
case TIMELINE_ROTATE: { case BONE_ROTATE: {
RotateTimeline timeline = new RotateTimeline(frameCount); RotateTimeline timeline = new RotateTimeline(frameCount);
timeline.boneIndex = boneIndex; timeline.boneIndex = boneIndex;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
@ -515,17 +510,17 @@ namespace Spine {
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
} }
timelines.Add(timeline); timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[frameCount * 2 - 2]); duration = Math.Max(duration, timeline.frames[(frameCount - 1) * RotateTimeline.ENTRIES]);
break; break;
} }
case TIMELINE_TRANSLATE: case BONE_TRANSLATE:
case TIMELINE_SCALE: case BONE_SCALE:
case TIMELINE_SHEAR: { case BONE_SHEAR: {
TranslateTimeline timeline; TranslateTimeline timeline;
float timelineScale = 1; float timelineScale = 1;
if (timelineType == TIMELINE_SCALE) if (timelineType == BONE_SCALE)
timeline = new ScaleTimeline(frameCount); timeline = new ScaleTimeline(frameCount);
else if (timelineType == TIMELINE_SHEAR) else if (timelineType == BONE_SHEAR)
timeline = new ShearTimeline(frameCount); timeline = new ShearTimeline(frameCount);
else { else {
timeline = new TranslateTimeline(frameCount); timeline = new TranslateTimeline(frameCount);
@ -538,7 +533,7 @@ namespace Spine {
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
} }
timelines.Add(timeline); timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[frameCount * 3 - 3]); duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TranslateTimeline.ENTRIES]);
break; break;
} }
} }
@ -546,81 +541,118 @@ namespace Spine {
} }
// IK timelines. // IK timelines.
for (int i = 0, n = ReadVarint(input, true); i < n; i++) { for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
IkConstraintData constraint = skeletonData.ikConstraints.Items[ReadVarint(input, true)]; int index = ReadVarint(input, true);
int frameCount = ReadVarint(input, true); int frameCount = ReadVarint(input, true);
IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount); IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount);
timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(constraint); timeline.ikConstraintIndex = index;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadSByte(input)); timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadSByte(input));
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
} }
timelines.Add(timeline); timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[frameCount * 3 - 3]); duration = Math.Max(duration, timeline.frames[(frameCount - 1) * IkConstraintTimeline.ENTRIES]);
} }
// Transform constraint timelines. // Transform constraint timelines.
for (int i = 0, n = ReadVarint(input, true); i < n; i++) { for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
TransformConstraintData constraint = skeletonData.transformConstraints.Items[ReadVarint(input, true)]; int index = ReadVarint(input, true);
int frameCount = ReadVarint(input, true); int frameCount = ReadVarint(input, true);
TransformConstraintTimeline timeline = new TransformConstraintTimeline(frameCount); TransformConstraintTimeline timeline = new TransformConstraintTimeline(frameCount);
timeline.transformConstraintIndex = skeletonData.transformConstraints.IndexOf(constraint); timeline.transformConstraintIndex = index;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input)); timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input));
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
} }
timelines.Add(timeline); timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[frameCount * 5 - 5]); duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TransformConstraintTimeline.ENTRIES]);
} }
// FFD timelines. // Path constraint timelines.
for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
int index = ReadVarint(input, true);
PathConstraintData data = skeletonData.pathConstraints.Items[index];
for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) {
int timelineType = ReadSByte(input);
int frameCount = ReadVarint(input, true);
switch(timelineType) {
case PATH_POSITION:
case PATH_SPACING: {
PathConstraintPositionTimeline timeline;
float timelineScale = 1;
if (timelineType == PATH_SPACING) {
timeline = new PathConstraintSpacingTimeline(frameCount);
if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale;
} else {
timeline = new PathConstraintPositionTimeline(frameCount);
if (data.positionMode == PositionMode.Fixed) timelineScale = scale;
}
timeline.pathConstraintIndex = index;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input) * timelineScale);
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintPositionTimeline.ENTRIES]);
break;
}
case PATH_MIX: {
PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(frameCount);
timeline.pathConstraintIndex = index;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input));
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintMixTimeline.ENTRIES]);
break;
}
}
}
}
// Deform timelines.
for (int i = 0, n = ReadVarint(input, true); i < n; i++) { for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
Skin skin = skeletonData.skins.Items[ReadVarint(input, true)]; Skin skin = skeletonData.skins.Items[ReadVarint(input, true)];
for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) { for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) {
int slotIndex = ReadVarint(input, true); int slotIndex = ReadVarint(input, true);
for (int iii = 0, nnn = ReadVarint(input, true); iii < nnn; iii++) { for (int iii = 0, nnn = ReadVarint(input, true); iii < nnn; iii++) {
Attachment attachment = skin.GetAttachment(slotIndex, ReadString(input)); VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, ReadString(input));
bool weighted = attachment.bones != null;
float[] vertices = attachment.vertices;
int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length;
int frameCount = ReadVarint(input, true); int frameCount = ReadVarint(input, true);
FfdTimeline timeline = new FfdTimeline(frameCount); DeformTimeline timeline = new DeformTimeline(frameCount);
timeline.slotIndex = slotIndex; timeline.slotIndex = slotIndex;
timeline.attachment = attachment; timeline.attachment = attachment;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
float time = ReadFloat(input); float time = ReadFloat(input);
float[] deform;
float[] vertices;
int vertexCount;
if (attachment is MeshAttachment)
vertexCount = ((MeshAttachment)attachment).vertices.Length;
else
vertexCount = ((WeightedMeshAttachment)attachment).weights.Length / 3 * 2;
int end = ReadVarint(input, true); int end = ReadVarint(input, true);
if (end == 0) { if (end == 0)
if (attachment is MeshAttachment) deform = weighted ? new float[deformLength] : vertices;
vertices = ((MeshAttachment)attachment).vertices; else {
else deform = new float[deformLength];
vertices = new float[vertexCount];
} else {
vertices = new float[vertexCount];
int start = ReadVarint(input, true); int start = ReadVarint(input, true);
end += start; end += start;
if (scale == 1) { if (scale == 1) {
for (int v = start; v < end; v++) for (int v = start; v < end; v++)
vertices[v] = ReadFloat(input); deform[v] = ReadFloat(input);
} else { } else {
for (int v = start; v < end; v++) for (int v = start; v < end; v++)
vertices[v] = ReadFloat(input) * scale; deform[v] = ReadFloat(input) * scale;
} }
if (attachment is MeshAttachment) { if (!weighted) {
float[] meshVertices = ((MeshAttachment)attachment).vertices; for (int v = 0, vn = deform.Length; v < vn; v++)
for (int v = 0, vn = vertices.Length; v < vn; v++) deform[v] += vertices[v];
vertices[v] += meshVertices[v];
} }
} }
timeline.SetFrame(frameIndex, time, vertices); timeline.SetFrame(frameIndex, time, deform);
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
} }
timelines.Add(timeline); timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[frameCount - 1]); duration = Math.Max(duration, timeline.frames[frameCount - 1]);
} }
@ -756,5 +788,10 @@ namespace Spine {
length -= count; length -= count;
} }
} }
internal class Vertices {
public int[] bones;
public float[] vertices;
}
} }
} }

View File

@ -30,7 +30,6 @@
*****************************************************************************/ *****************************************************************************/
using System; using System;
using System.Collections.Generic;
namespace Spine { namespace Spine {
public class SkeletonBounds { public class SkeletonBounds {
@ -80,7 +79,7 @@ namespace Spine {
int count = boundingBox.Vertices.Length; int count = boundingBox.Vertices.Length;
polygon.Count = count; polygon.Count = count;
if (polygon.Vertices.Length < count) polygon.Vertices = new float[count]; if (polygon.Vertices.Length < count) polygon.Vertices = new float[count];
boundingBox.ComputeWorldVertices(slot.bone, polygon.Vertices); boundingBox.ComputeWorldVertices(slot, polygon.Vertices);
} }
if (updateAabb) aabbCompute(); if (updateAabb) aabbCompute();

View File

@ -30,7 +30,6 @@
*****************************************************************************/ *****************************************************************************/
using System; using System;
using System.Collections.Generic;
namespace Spine { namespace Spine {
public class SkeletonData { public class SkeletonData {
@ -43,6 +42,7 @@ namespace Spine {
internal ExposedList<Animation> animations = new ExposedList<Animation>(); internal ExposedList<Animation> animations = new ExposedList<Animation>();
internal ExposedList<IkConstraintData> ikConstraints = new ExposedList<IkConstraintData>(); internal ExposedList<IkConstraintData> ikConstraints = new ExposedList<IkConstraintData>();
internal ExposedList<TransformConstraintData> transformConstraints = new ExposedList<TransformConstraintData>(); internal ExposedList<TransformConstraintData> transformConstraints = new ExposedList<TransformConstraintData>();
internal ExposedList<PathConstraintData> pathConstraints = new ExposedList<PathConstraintData>();
internal float width, height; internal float width, height;
internal String version, hash, imagesPath; internal String version, hash, imagesPath;
@ -55,6 +55,8 @@ namespace Spine {
public ExposedList<EventData> Events { get { return events; } set { events = value; } } public ExposedList<EventData> Events { get { return events; } set { events = value; } }
public ExposedList<Animation> Animations { get { return animations; } set { animations = value; } } public ExposedList<Animation> Animations { get { return animations; } set { animations = value; } }
public ExposedList<IkConstraintData> IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } } public ExposedList<IkConstraintData> IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } }
public ExposedList<TransformConstraintData> TransformConstraints { get { return transformConstraints; } set { transformConstraints = value; } }
public ExposedList<PathConstraintData> PathConstraints { get { return pathConstraints; } set { pathConstraints = value; } }
public float Width { get { return width; } set { width = value; } } public float Width { get { return width; } set { width = value; } }
public float Height { get { return height; } set { height = value; } } public float Height { get { return height; } set { height = value; } }
/// <summary>The Spine version used to export this data.</summary> /// <summary>The Spine version used to export this data.</summary>
@ -65,7 +67,7 @@ namespace Spine {
/// <returns>May be null.</returns> /// <returns>May be null.</returns>
public BoneData FindBone (String boneName) { public BoneData FindBone (String boneName) {
if (boneName == null) throw new ArgumentNullException("boneName cannot be null."); if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null.");
ExposedList<BoneData> bones = this.bones; ExposedList<BoneData> bones = this.bones;
for (int i = 0, n = bones.Count; i < n; i++) { for (int i = 0, n = bones.Count; i < n; i++) {
BoneData bone = bones.Items[i]; BoneData bone = bones.Items[i];
@ -76,7 +78,7 @@ namespace Spine {
/// <returns>-1 if the bone was not found.</returns> /// <returns>-1 if the bone was not found.</returns>
public int FindBoneIndex (String boneName) { public int FindBoneIndex (String boneName) {
if (boneName == null) throw new ArgumentNullException("boneName cannot be null."); if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null.");
ExposedList<BoneData> bones = this.bones; ExposedList<BoneData> bones = this.bones;
for (int i = 0, n = bones.Count; i < n; i++) for (int i = 0, n = bones.Count; i < n; i++)
if (bones.Items[i].name == boneName) return i; if (bones.Items[i].name == boneName) return i;
@ -87,7 +89,7 @@ namespace Spine {
/// <returns>May be null.</returns> /// <returns>May be null.</returns>
public SlotData FindSlot (String slotName) { public SlotData FindSlot (String slotName) {
if (slotName == null) throw new ArgumentNullException("slotName cannot be null."); if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
ExposedList<SlotData> slots = this.slots; ExposedList<SlotData> slots = this.slots;
for (int i = 0, n = slots.Count; i < n; i++) { for (int i = 0, n = slots.Count; i < n; i++) {
SlotData slot = slots.Items[i]; SlotData slot = slots.Items[i];
@ -96,9 +98,9 @@ namespace Spine {
return null; return null;
} }
/// <returns>-1 if the bone was not found.</returns> /// <returns>-1 if the slot was not found.</returns>
public int FindSlotIndex (String slotName) { public int FindSlotIndex (String slotName) {
if (slotName == null) throw new ArgumentNullException("slotName cannot be null."); if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
ExposedList<SlotData> slots = this.slots; ExposedList<SlotData> slots = this.slots;
for (int i = 0, n = slots.Count; i < n; i++) for (int i = 0, n = slots.Count; i < n; i++)
if (slots.Items[i].name == slotName) return i; if (slots.Items[i].name == slotName) return i;
@ -109,7 +111,7 @@ namespace Spine {
/// <returns>May be null.</returns> /// <returns>May be null.</returns>
public Skin FindSkin (String skinName) { public Skin FindSkin (String skinName) {
if (skinName == null) throw new ArgumentNullException("skinName cannot be null."); if (skinName == null) throw new ArgumentNullException("skinName", "skinName cannot be null.");
foreach (Skin skin in skins) foreach (Skin skin in skins)
if (skin.name == skinName) return skin; if (skin.name == skinName) return skin;
return null; return null;
@ -119,7 +121,7 @@ namespace Spine {
/// <returns>May be null.</returns> /// <returns>May be null.</returns>
public EventData FindEvent (String eventDataName) { public EventData FindEvent (String eventDataName) {
if (eventDataName == null) throw new ArgumentNullException("eventDataName cannot be null."); if (eventDataName == null) throw new ArgumentNullException("eventDataName", "eventDataName cannot be null.");
foreach (EventData eventData in events) foreach (EventData eventData in events)
if (eventData.name == eventDataName) return eventData; if (eventData.name == eventDataName) return eventData;
return null; return null;
@ -129,7 +131,7 @@ namespace Spine {
/// <returns>May be null.</returns> /// <returns>May be null.</returns>
public Animation FindAnimation (String animationName) { public Animation FindAnimation (String animationName) {
if (animationName == null) throw new ArgumentNullException("animationName cannot be null."); if (animationName == null) throw new ArgumentNullException("animationName", "animationName cannot be null.");
ExposedList<Animation> animations = this.animations; ExposedList<Animation> animations = this.animations;
for (int i = 0, n = animations.Count; i < n; i++) { for (int i = 0, n = animations.Count; i < n; i++) {
Animation animation = animations.Items[i]; Animation animation = animations.Items[i];
@ -142,7 +144,7 @@ namespace Spine {
/// <returns>May be null.</returns> /// <returns>May be null.</returns>
public IkConstraintData FindIkConstraint (String constraintName) { public IkConstraintData FindIkConstraint (String constraintName) {
if (constraintName == null) throw new ArgumentNullException("constraintName cannot be null."); if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
ExposedList<IkConstraintData> ikConstraints = this.ikConstraints; ExposedList<IkConstraintData> ikConstraints = this.ikConstraints;
for (int i = 0, n = ikConstraints.Count; i < n; i++) { for (int i = 0, n = ikConstraints.Count; i < n; i++) {
IkConstraintData ikConstraint = ikConstraints.Items[i]; IkConstraintData ikConstraint = ikConstraints.Items[i];
@ -155,7 +157,7 @@ namespace Spine {
/// <returns>May be null.</returns> /// <returns>May be null.</returns>
public TransformConstraintData FindTransformConstraint (String constraintName) { public TransformConstraintData FindTransformConstraint (String constraintName) {
if (constraintName == null) throw new ArgumentNullException("constraintName cannot be null."); if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
ExposedList<TransformConstraintData> transformConstraints = this.transformConstraints; ExposedList<TransformConstraintData> transformConstraints = this.transformConstraints;
for (int i = 0, n = transformConstraints.Count; i < n; i++) { for (int i = 0, n = transformConstraints.Count; i < n; i++) {
TransformConstraintData transformConstraint = transformConstraints.Items[i]; TransformConstraintData transformConstraint = transformConstraints.Items[i];
@ -164,6 +166,28 @@ namespace Spine {
return null; return null;
} }
// --- Path constraints.
/// <returns>May be null.</returns>
public PathConstraintData FindPathConstraint (String constraintName) {
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
ExposedList<PathConstraintData> pathConstraints = this.pathConstraints;
for (int i = 0, n = pathConstraints.Count; i < n; i++) {
PathConstraintData constraint = pathConstraints.Items[i];
if (constraint.name.Equals(constraintName)) return constraint;
}
return null;
}
/// <returns>-1 if the path constraint was not found.</returns>
public int FindPathConstraintIndex (String pathConstraintName) {
if (pathConstraintName == null) throw new ArgumentNullException("pathConstraintName", "pathConstraintName cannot be null.");
ExposedList<PathConstraintData> pathConstraints = this.pathConstraints;
for (int i = 0, n = pathConstraints.Count; i < n; i++)
if (pathConstraints.Items[i].name.Equals(pathConstraintName)) return i;
return -1;
}
// --- // ---
override public String ToString () { override public String ToString () {

View File

@ -54,7 +54,7 @@ namespace Spine {
} }
public SkeletonJson (AttachmentLoader attachmentLoader) { public SkeletonJson (AttachmentLoader attachmentLoader) {
if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader cannot be null."); if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader", "attachmentLoader cannot be null.");
this.attachmentLoader = attachmentLoader; this.attachmentLoader = attachmentLoader;
Scale = 1; Scale = 1;
} }
@ -79,7 +79,7 @@ namespace Spine {
Stream stream = Microsoft.Xna.Framework.TitleContainer.OpenStream(path); Stream stream = Microsoft.Xna.Framework.TitleContainer.OpenStream(path);
using (StreamReader reader = new StreamReader(stream)) { using (StreamReader reader = new StreamReader(stream)) {
#else #else
using (StreamReader reader = new StreamReader(path)) { using (var reader = new StreamReader(path)) {
#endif // WINDOWS_PHONE #endif // WINDOWS_PHONE
SkeletonData skeletonData = ReadSkeletonData(reader); SkeletonData skeletonData = ReadSkeletonData(reader);
skeletonData.name = Path.GetFileNameWithoutExtension(path); skeletonData.name = Path.GetFileNameWithoutExtension(path);
@ -89,7 +89,7 @@ namespace Spine {
#endif // WINDOWS_STOREAPP #endif // WINDOWS_STOREAPP
public SkeletonData ReadSkeletonData (TextReader reader) { public SkeletonData ReadSkeletonData (TextReader reader) {
if (reader == null) throw new ArgumentNullException("reader cannot be null."); if (reader == null) throw new ArgumentNullException("reader", "reader cannot be null.");
var scale = this.Scale; var scale = this.Scale;
var skeletonData = new SkeletonData(); var skeletonData = new SkeletonData();
@ -114,69 +114,19 @@ namespace Spine {
if (parent == null) if (parent == null)
throw new Exception("Parent bone not found: " + boneMap["parent"]); throw new Exception("Parent bone not found: " + boneMap["parent"]);
} }
var boneData = new BoneData((String)boneMap["name"], parent); var data = new BoneData(skeletonData.Bones.Count, (String)boneMap["name"], parent);
boneData.length = GetFloat(boneMap, "length", 0) * scale; data.length = GetFloat(boneMap, "length", 0) * scale;
boneData.x = GetFloat(boneMap, "x", 0) * scale; data.x = GetFloat(boneMap, "x", 0) * scale;
boneData.y = GetFloat(boneMap, "y", 0) * scale; data.y = GetFloat(boneMap, "y", 0) * scale;
boneData.rotation = GetFloat(boneMap, "rotation", 0); data.rotation = GetFloat(boneMap, "rotation", 0);
boneData.scaleX = GetFloat(boneMap, "scaleX", 1); data.scaleX = GetFloat(boneMap, "scaleX", 1);
boneData.scaleY = GetFloat(boneMap, "scaleY", 1); data.scaleY = GetFloat(boneMap, "scaleY", 1);
boneData.shearX = GetFloat(boneMap, "shearX", 0); data.shearX = GetFloat(boneMap, "shearX", 0);
boneData.shearY = GetFloat(boneMap, "shearY", 0); data.shearY = GetFloat(boneMap, "shearY", 0);
boneData.inheritScale = GetBoolean(boneMap, "inheritScale", true); data.inheritRotation = GetBoolean(boneMap, "inheritRotation", true);
boneData.inheritRotation = GetBoolean(boneMap, "inheritRotation", true); data.inheritScale = GetBoolean(boneMap, "inheritScale", true);
skeletonData.bones.Add(boneData);
}
// IK constraints. skeletonData.bones.Add(data);
if (root.ContainsKey("ik")) {
foreach (Dictionary<String, Object> ikMap in (List<Object>)root["ik"]) {
IkConstraintData ikConstraintData = new IkConstraintData((String)ikMap["name"]);
foreach (String boneName in (List<Object>)ikMap["bones"]) {
BoneData bone = skeletonData.FindBone(boneName);
if (bone == null) throw new Exception("IK bone not found: " + boneName);
ikConstraintData.bones.Add(bone);
}
String targetName = (String)ikMap["target"];
ikConstraintData.target = skeletonData.FindBone(targetName);
if (ikConstraintData.target == null) throw new Exception("Target bone not found: " + targetName);
ikConstraintData.bendDirection = GetBoolean(ikMap, "bendPositive", true) ? 1 : -1;
ikConstraintData.mix = GetFloat(ikMap, "mix", 1);
skeletonData.ikConstraints.Add(ikConstraintData);
}
}
// Transform constraints.
if (root.ContainsKey("transform")) {
foreach (Dictionary<String, Object> transformMap in (List<Object>)root["transform"]) {
TransformConstraintData transformConstraintData = new TransformConstraintData((String)transformMap["name"]);
String boneName = (String)transformMap["bone"];
transformConstraintData.bone = skeletonData.FindBone(boneName);
if (transformConstraintData.bone == null) throw new Exception("Bone not found: " + boneName);
String targetName = (String)transformMap["target"];
transformConstraintData.target = skeletonData.FindBone(targetName);
if (transformConstraintData.target == null) throw new Exception("Target bone not found: " + targetName);
transformConstraintData.offsetRotation = GetFloat(transformMap, "rotation", 0);
transformConstraintData.offsetX = GetFloat(transformMap, "x", 0) * scale;
transformConstraintData.offsetY = GetFloat(transformMap, "y", 0) * scale;
transformConstraintData.offsetScaleX = GetFloat(transformMap, "scaleX", 0);
transformConstraintData.offsetScaleY = GetFloat(transformMap, "scaleY", 0);
transformConstraintData.offsetShearY = GetFloat(transformMap, "shearY", 0);
transformConstraintData.rotateMix = GetFloat(transformMap, "rotateMix", 1);
transformConstraintData.translateMix = GetFloat(transformMap, "translateMix", 1);
transformConstraintData.scaleMix = GetFloat(transformMap, "scaleMix", 1);
transformConstraintData.shearMix = GetFloat(transformMap, "shearMix", 1);
skeletonData.transformConstraints.Add(transformConstraintData);
}
} }
// Slots. // Slots.
@ -185,44 +135,126 @@ namespace Spine {
var slotName = (String)slotMap["name"]; var slotName = (String)slotMap["name"];
var boneName = (String)slotMap["bone"]; var boneName = (String)slotMap["bone"];
BoneData boneData = skeletonData.FindBone(boneName); BoneData boneData = skeletonData.FindBone(boneName);
if (boneData == null) if (boneData == null) throw new Exception("Slot bone not found: " + boneName);
throw new Exception("Slot bone not found: " + boneName); var data = new SlotData(skeletonData.Slots.Count, slotName, boneData);
var slotData = new SlotData(slotName, boneData);
if (slotMap.ContainsKey("color")) { if (slotMap.ContainsKey("color")) {
var color = (String)slotMap["color"]; var color = (String)slotMap["color"];
slotData.r = ToColor(color, 0); data.r = ToColor(color, 0);
slotData.g = ToColor(color, 1); data.g = ToColor(color, 1);
slotData.b = ToColor(color, 2); data.b = ToColor(color, 2);
slotData.a = ToColor(color, 3); data.a = ToColor(color, 3);
}
data.attachmentName = GetString(slotMap, "attachment", null);
if (slotMap.ContainsKey("blend"))
data.blendMode = (BlendMode)Enum.Parse(typeof(BlendMode), (String)slotMap["blend"], false);
else
data.blendMode = BlendMode.normal;
skeletonData.slots.Add(data);
}
}
// IK constraints.
if (root.ContainsKey("ik")) {
foreach (Dictionary<String, Object> constraintMap in (List<Object>)root["ik"]) {
IkConstraintData data = new IkConstraintData((String)constraintMap["name"]);
foreach (String boneName in (List<Object>)constraintMap["bones"]) {
BoneData bone = skeletonData.FindBone(boneName);
if (bone == null) throw new Exception("IK constraint bone not found: " + boneName);
data.bones.Add(bone);
}
String targetName = (String)constraintMap["target"];
data.target = skeletonData.FindBone(targetName);
if (data.target == null) throw new Exception("Target bone not found: " + targetName);
data.bendDirection = GetBoolean(constraintMap, "bendPositive", true) ? 1 : -1;
data.mix = GetFloat(constraintMap, "mix", 1);
skeletonData.ikConstraints.Add(data);
}
}
// Transform constraints.
if (root.ContainsKey("transform")) {
foreach (Dictionary<String, Object> constraintMap in (List<Object>)root["transform"]) {
TransformConstraintData data = new TransformConstraintData((String)constraintMap["name"]);
foreach (String boneName in (List<Object>)constraintMap["bones"]) {
BoneData bone = skeletonData.FindBone(boneName);
if (bone == null) throw new Exception("Transform constraint bone not found: " + boneName);
data.bones.Add(bone);
} }
if (slotMap.ContainsKey("attachment")) String targetName = (String)constraintMap["target"];
slotData.attachmentName = (String)slotMap["attachment"]; data.target = skeletonData.FindBone(targetName);
if (data.target == null) throw new Exception("Target bone not found: " + targetName);
if (slotMap.ContainsKey("blend")) data.offsetRotation = GetFloat(constraintMap, "rotation", 0);
slotData.blendMode = (BlendMode)Enum.Parse(typeof(BlendMode), (String)slotMap["blend"], false); data.offsetX = GetFloat(constraintMap, "x", 0) * scale;
else data.offsetY = GetFloat(constraintMap, "y", 0) * scale;
slotData.blendMode = BlendMode.normal; data.offsetScaleX = GetFloat(constraintMap, "scaleX", 0);
data.offsetScaleY = GetFloat(constraintMap, "scaleY", 0);
data.offsetShearY = GetFloat(constraintMap, "shearY", 0);
skeletonData.slots.Add(slotData); data.rotateMix = GetFloat(constraintMap, "rotateMix", 1);
data.translateMix = GetFloat(constraintMap, "translateMix", 1);
data.scaleMix = GetFloat(constraintMap, "scaleMix", 1);
data.shearMix = GetFloat(constraintMap, "shearMix", 1);
skeletonData.transformConstraints.Add(data);
}
}
// Path constraints.
if(root.ContainsKey("path")) {
foreach (Dictionary<String, Object> constraintMap in (List<Object>)root["path"]) {
PathConstraintData data = new PathConstraintData((String)constraintMap["name"]);
foreach (String boneName in (List<Object>)constraintMap["bones"]) {
BoneData bone = skeletonData.FindBone(boneName);
if (bone == null) throw new Exception("Path bone not found: " + boneName);
data.bones.Add(bone);
}
String targetName = (String)constraintMap["target"];
data.target = skeletonData.FindSlot(targetName);
if (data.target == null) throw new Exception("Target slot not found: " + targetName);
data.positionMode = (PositionMode)Enum.Parse(typeof(PositionMode), GetString(constraintMap, "positionMode", "percent"), true);
data.spacingMode = (SpacingMode)Enum.Parse(typeof(SpacingMode), GetString(constraintMap, "spacingMode", "length"), true);
data.rotateMode = (RotateMode)Enum.Parse(typeof(RotateMode), GetString(constraintMap, "rotateMode", "tangent"), true);
data.offsetRotation = GetFloat(constraintMap, "rotation", 0);
data.position = GetFloat(constraintMap, "position", 0);
if (data.positionMode == PositionMode.Fixed) data.position *= scale;
data.spacing = GetFloat(constraintMap, "spacing", 0);
if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale;
data.rotateMix = GetFloat(constraintMap, "rotateMix", 1);
data.translateMix = GetFloat(constraintMap, "translateMix", 1);
skeletonData.pathConstraints.Add(data);
} }
} }
// Skins. // Skins.
if (root.ContainsKey("skins")) { if (root.ContainsKey("skins")) {
foreach (KeyValuePair<String, Object> entry in (Dictionary<String, Object>)root["skins"]) { foreach (KeyValuePair<String, Object> skinMap in (Dictionary<String, Object>)root["skins"]) {
var skin = new Skin(entry.Key); var skin = new Skin(skinMap.Key);
foreach (KeyValuePair<String, Object> slotEntry in (Dictionary<String, Object>)entry.Value) { foreach (KeyValuePair<String, Object> slotEntry in (Dictionary<String, Object>)skinMap.Value) {
int slotIndex = skeletonData.FindSlotIndex(slotEntry.Key); int slotIndex = skeletonData.FindSlotIndex(slotEntry.Key);
foreach (KeyValuePair<String, Object> attachmentEntry in ((Dictionary<String, Object>)slotEntry.Value)) { foreach (KeyValuePair<String, Object> entry in ((Dictionary<String, Object>)slotEntry.Value)) {
Attachment attachment = ReadAttachment(skin, slotIndex, attachmentEntry.Key, (Dictionary<String, Object>)attachmentEntry.Value); try {
if (attachment != null) skin.AddAttachment(slotIndex, attachmentEntry.Key, attachment); Attachment attachment = ReadAttachment((Dictionary<String, Object>)entry.Value, skin, slotIndex, entry.Key);
} if (attachment != null) skin.AddAttachment(slotIndex, entry.Key, attachment);
} catch (Exception e) {
throw new Exception("Error reading attachment: " + entry.Key + ", skin: " + skin, e);
}
}
} }
skeletonData.skins.Add(skin); skeletonData.skins.Add(skin);
if (skin.name == "default") if (skin.name == "default") skeletonData.defaultSkin = skin;
skeletonData.defaultSkin = skin;
} }
} }
@ -233,15 +265,8 @@ namespace Spine {
if (skin == null) throw new Exception("Slot not found: " + linkedMesh.skin); if (skin == null) throw new Exception("Slot not found: " + linkedMesh.skin);
Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent);
if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent);
if (linkedMesh.mesh is MeshAttachment) { linkedMesh.mesh.ParentMesh = (MeshAttachment)parent;
MeshAttachment mesh = (MeshAttachment)linkedMesh.mesh; linkedMesh.mesh.UpdateUVs();
mesh.ParentMesh = (MeshAttachment)parent;
mesh.UpdateUVs();
} else {
WeightedMeshAttachment mesh = (WeightedMeshAttachment)linkedMesh.mesh;
mesh.ParentMesh = (WeightedMeshAttachment)parent;
mesh.UpdateUVs();
}
} }
linkedMeshes.Clear(); linkedMeshes.Clear();
@ -249,18 +274,23 @@ namespace Spine {
if (root.ContainsKey("events")) { if (root.ContainsKey("events")) {
foreach (KeyValuePair<String, Object> entry in (Dictionary<String, Object>)root["events"]) { foreach (KeyValuePair<String, Object> entry in (Dictionary<String, Object>)root["events"]) {
var entryMap = (Dictionary<String, Object>)entry.Value; var entryMap = (Dictionary<String, Object>)entry.Value;
var eventData = new EventData(entry.Key); var data = new EventData(entry.Key);
eventData.Int = GetInt(entryMap, "int", 0); data.Int = GetInt(entryMap, "int", 0);
eventData.Float = GetFloat(entryMap, "float", 0); data.Float = GetFloat(entryMap, "float", 0);
eventData.String = GetString(entryMap, "string", null); data.String = GetString(entryMap, "string", null);
skeletonData.events.Add(eventData); skeletonData.events.Add(data);
} }
} }
// Animations. // Animations.
if (root.ContainsKey("animations")) { if (root.ContainsKey("animations")) {
foreach (KeyValuePair<String, Object> entry in (Dictionary<String, Object>)root["animations"]) foreach (KeyValuePair<String, Object> entry in (Dictionary<String, Object>)root["animations"]) {
ReadAnimation(entry.Key, (Dictionary<String, Object>)entry.Value, skeletonData); try {
ReadAnimation((Dictionary<String, Object>)entry.Value, entry.Key, skeletonData);
} catch (Exception e) {
throw new Exception("Error reading animation: " + entry.Key, e);
}
}
} }
skeletonData.bones.TrimExcess(); skeletonData.bones.TrimExcess();
@ -272,25 +302,20 @@ namespace Spine {
return skeletonData; return skeletonData;
} }
private Attachment ReadAttachment (Skin skin, int slotIndex, String name, Dictionary<String, Object> map) { private Attachment ReadAttachment (Dictionary<String, Object> map, Skin skin, int slotIndex, String name) {
if (map.ContainsKey("name"))
name = (String)map["name"];
var scale = this.Scale; var scale = this.Scale;
name = GetString(map, "name", name);
var type = AttachmentType.region; var typeName = GetString(map, "type", "region");
if (map.ContainsKey("type")) { if (typeName == "skinnedmesh") typeName = "weightedmesh";
var typeName = (String)map["type"]; if (typeName == "weightedmesh") typeName = "mesh";
if (typeName == "skinnedmesh") typeName = "weightedmesh"; if (typeName == "weightedlinkedmesh") typeName = "linkedmesh";
type = (AttachmentType)Enum.Parse(typeof(AttachmentType), typeName , false); var type = (AttachmentType)Enum.Parse(typeof(AttachmentType), typeName, true);
}
String path = name; String path = GetString(map, "path", name);
if (map.ContainsKey("path"))
path = (String)map["path"];
switch (type) { switch (type) {
case AttachmentType.region: case AttachmentType.Region:
RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path);
if (region == null) return null; if (region == null) return null;
region.Path = path; region.Path = path;
@ -311,9 +336,15 @@ namespace Spine {
region.a = ToColor(color, 3); region.a = ToColor(color, 3);
} }
region.UpdateOffset();
return region; return region;
case AttachmentType.mesh: case AttachmentType.Boundingbox:
case AttachmentType.linkedmesh: { BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name);
if (box == null) return null;
ReadVertices(map, box, GetInt(map, "vertexCount", 0) << 1);
return box;
case AttachmentType.Mesh:
case AttachmentType.Linkedmesh: {
MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path);
if (mesh == null) return null; if (mesh == null) return null;
mesh.Path = path; mesh.Path = path;
@ -326,145 +357,83 @@ namespace Spine {
mesh.a = ToColor(color, 3); mesh.a = ToColor(color, 3);
} }
mesh.Width = GetInt(map, "width", 0) * scale; mesh.Width = GetFloat(map, "width", 0) * scale;
mesh.Height = GetInt(map, "height", 0) * scale; mesh.Height = GetFloat(map, "height", 0) * scale;
String parent = GetString(map, "parent", null); String parent = GetString(map, "parent", null);
if (parent == null) { if (parent != null) {
mesh.vertices = GetFloatArray(map, "vertices", scale); mesh.InheritDeform = GetBoolean(map, "deform", true);
mesh.triangles = GetIntArray(map, "triangles");
mesh.regionUVs = GetFloatArray(map, "uvs", 1);
mesh.UpdateUVs();
mesh.HullLength = GetInt(map, "hull", 0) * 2;
if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges");
} else {
mesh.InheritFFD = GetBoolean(map, "ffd", true);
linkedMeshes.Add(new LinkedMesh(mesh, GetString(map, "skin", null), slotIndex, parent)); linkedMeshes.Add(new LinkedMesh(mesh, GetString(map, "skin", null), slotIndex, parent));
return mesh;
} }
float[] uvs = GetFloatArray(map, "uvs", 1);
ReadVertices(map, mesh, uvs.Length);
mesh.triangles = GetIntArray(map, "triangles");
mesh.regionUVs = uvs;
mesh.UpdateUVs();
if (map.ContainsKey("hull")) mesh.HullLength = GetInt(map, "hull", 0) * 2;
if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges");
return mesh; return mesh;
} }
case AttachmentType.weightedmesh: case AttachmentType.Path: {
case AttachmentType.weightedlinkedmesh: { PathAttachment pathAttachment = attachmentLoader.NewPathAttachment(skin, name);
WeightedMeshAttachment mesh = attachmentLoader.NewWeightedMeshAttachment(skin, name, path); if (pathAttachment == null) return null;
if (mesh == null) return null; pathAttachment.closed = GetBoolean(map, "closed", false);
pathAttachment.constantSpeed = GetBoolean(map, "constantSpeed", true);
mesh.Path = path; int vertexCount = GetInt(map, "vertexCount", 0);
ReadVertices(map, pathAttachment, vertexCount << 1);
if (map.ContainsKey("color")) { // potential BOZO see Java impl
var color = (String)map["color"]; pathAttachment.lengths = GetFloatArray(map, "lengths", scale);
mesh.r = ToColor(color, 0); return pathAttachment;
mesh.g = ToColor(color, 1);
mesh.b = ToColor(color, 2);
mesh.a = ToColor(color, 3);
}
mesh.Width = GetInt(map, "width", 0) * scale;
mesh.Height = GetInt(map, "height", 0) * scale;
String parent = GetString(map, "parent", null);
if (parent == null) {
float[] uvs = GetFloatArray(map, "uvs", 1);
float[] vertices = GetFloatArray(map, "vertices", 1);
var weights = new List<float>(uvs.Length * 3 * 3);
var bones = new List<int>(uvs.Length * 3);
for (int i = 0, n = vertices.Length; i < n;) {
int boneCount = (int)vertices[i++];
bones.Add(boneCount);
for (int nn = i + boneCount * 4; i < nn; i += 4) {
bones.Add((int)vertices[i]);
weights.Add(vertices[i + 1] * scale);
weights.Add(vertices[i + 2] * scale);
weights.Add(vertices[i + 3]);
}
}
mesh.bones = bones.ToArray();
mesh.weights = weights.ToArray();
mesh.triangles = GetIntArray(map, "triangles");
mesh.regionUVs = uvs;
mesh.UpdateUVs();
mesh.HullLength = GetInt(map, "hull", 0) * 2;
if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges");
} else {
mesh.InheritFFD = GetBoolean(map, "ffd", true);
linkedMeshes.Add(new LinkedMesh(mesh, GetString(map, "skin", null), slotIndex, parent));
}
return mesh;
} }
case AttachmentType.boundingbox:
BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name);
if (box == null) return null;
box.vertices = GetFloatArray(map, "vertices", scale);
return box;
} }
return null; return null;
} }
private float[] GetFloatArray (Dictionary<String, Object> map, String name, float scale) { private void ReadVertices (Dictionary<String, Object> map, VertexAttachment attachment, int verticesLength) {
var list = (List<Object>)map[name]; attachment.WorldVerticesLength = verticesLength;
var values = new float[list.Count]; float[] vertices = GetFloatArray(map, "vertices", 1);
if (scale == 1) { float scale = Scale;
for (int i = 0, n = list.Count; i < n; i++) if (verticesLength == vertices.Length) {
values[i] = (float)list[i]; if (scale != 1) {
} else { for (int i = 0; i < vertices.Length; i++) {
for (int i = 0, n = list.Count; i < n; i++) vertices[i] *= scale;
values[i] = (float)list[i] * scale; }
}
attachment.vertices = vertices;
return;
} }
return values; ExposedList<float> weights = new ExposedList<float>(verticesLength * 3 * 3);
ExposedList<int> bones = new ExposedList<int>(verticesLength * 3);
for (int i = 0, n = vertices.Length; i < n;) {
int boneCount = (int)vertices[i++];
bones.Add(boneCount);
for (int nn = i + boneCount * 4; i < nn; i += 4) {
bones.Add((int)vertices[i]);
weights.Add(vertices[i + 1] * this.Scale);
weights.Add(vertices[i + 2] * this.Scale);
weights.Add(vertices[i + 3]);
}
}
attachment.bones = bones.ToArray();
attachment.vertices = weights.ToArray();
} }
private int[] GetIntArray (Dictionary<String, Object> map, String name) { private void ReadAnimation (Dictionary<String, Object> map, String name, SkeletonData skeletonData) {
var list = (List<Object>)map[name]; var scale = this.Scale;
var values = new int[list.Count];
for (int i = 0, n = list.Count; i < n; i++)
values[i] = (int)(float)list[i];
return values;
}
private float GetFloat (Dictionary<String, Object> map, String name, float defaultValue) {
if (!map.ContainsKey(name))
return defaultValue;
return (float)map[name];
}
private int GetInt (Dictionary<String, Object> map, String name, int defaultValue) {
if (!map.ContainsKey(name))
return defaultValue;
return (int)(float)map[name];
}
private bool GetBoolean (Dictionary<String, Object> map, String name, bool defaultValue) {
if (!map.ContainsKey(name))
return defaultValue;
return (bool)map[name];
}
private String GetString (Dictionary<String, Object> map, String name, String defaultValue) {
if (!map.ContainsKey(name))
return defaultValue;
return (String)map[name];
}
private 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 ExposedList<Timeline>(); var timelines = new ExposedList<Timeline>();
float duration = 0; float duration = 0;
var scale = this.Scale;
// Slot timelines.
if (map.ContainsKey("slots")) { if (map.ContainsKey("slots")) {
foreach (KeyValuePair<String, Object> entry in (Dictionary<String, Object>)map["slots"]) { foreach (KeyValuePair<String, Object> entry in (Dictionary<String, Object>)map["slots"]) {
String slotName = entry.Key; String slotName = entry.Key;
int slotIndex = skeletonData.FindSlotIndex(slotName); int slotIndex = skeletonData.FindSlotIndex(slotName);
var timelineMap = (Dictionary<String, Object>)entry.Value; var timelineMap = (Dictionary<String, Object>)entry.Value;
foreach (KeyValuePair<String, Object> timelineEntry in timelineMap) { foreach (KeyValuePair<String, Object> timelineEntry in timelineMap) {
var values = (List<Object>)timelineEntry.Value; var values = (List<Object>)timelineEntry.Value;
var timelineName = (String)timelineEntry.Key; var timelineName = (String)timelineEntry.Key;
@ -477,11 +446,11 @@ namespace Spine {
float time = (float)valueMap["time"]; float time = (float)valueMap["time"];
String c = (String)valueMap["color"]; String c = (String)valueMap["color"];
timeline.SetFrame(frameIndex, time, ToColor(c, 0), ToColor(c, 1), ToColor(c, 2), ToColor(c, 3)); timeline.SetFrame(frameIndex, time, ToColor(c, 0), ToColor(c, 1), ToColor(c, 2), ToColor(c, 3));
ReadCurve(timeline, frameIndex, valueMap); ReadCurve(valueMap, timeline, frameIndex);
frameIndex++; frameIndex++;
} }
timelines.Add(timeline); timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 5 - 5]); duration = Math.Max(duration, timeline.frames[timeline.FrameCount -1] * ColorTimeline.ENTRIES);
} else if (timelineName == "attachment") { } else if (timelineName == "attachment") {
var timeline = new AttachmentTimeline(values.Count); var timeline = new AttachmentTimeline(values.Count);
@ -501,13 +470,12 @@ namespace Spine {
} }
} }
// Bone timelines.
if (map.ContainsKey("bones")) { if (map.ContainsKey("bones")) {
foreach (KeyValuePair<String, Object> entry in (Dictionary<String, Object>)map["bones"]) { foreach (KeyValuePair<String, Object> entry in (Dictionary<String, Object>)map["bones"]) {
String boneName = entry.Key; String boneName = entry.Key;
int boneIndex = skeletonData.FindBoneIndex(boneName); int boneIndex = skeletonData.FindBoneIndex(boneName);
if (boneIndex == -1) if (boneIndex == -1) throw new Exception("Bone not found: " + boneName);
throw new Exception("Bone not found: " + boneName);
var timelineMap = (Dictionary<String, Object>)entry.Value; var timelineMap = (Dictionary<String, Object>)entry.Value;
foreach (KeyValuePair<String, Object> timelineEntry in timelineMap) { foreach (KeyValuePair<String, Object> timelineEntry in timelineMap) {
var values = (List<Object>)timelineEntry.Value; var values = (List<Object>)timelineEntry.Value;
@ -518,13 +486,12 @@ namespace Spine {
int frameIndex = 0; int frameIndex = 0;
foreach (Dictionary<String, Object> valueMap in values) { foreach (Dictionary<String, Object> valueMap in values) {
float time = (float)valueMap["time"]; timeline.SetFrame(frameIndex, (float)valueMap["time"], (float)valueMap["angle"]);
timeline.SetFrame(frameIndex, time, (float)valueMap["angle"]); ReadCurve(valueMap, timeline, frameIndex);
ReadCurve(timeline, frameIndex, valueMap);
frameIndex++; frameIndex++;
} }
timelines.Add(timeline); timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 2 - 2]); duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * RotateTimeline.ENTRIES]);
} else if (timelineName == "translate" || timelineName == "scale" || timelineName == "shear") { } else if (timelineName == "translate" || timelineName == "scale" || timelineName == "shear") {
TranslateTimeline timeline; TranslateTimeline timeline;
@ -544,12 +511,12 @@ namespace Spine {
float time = (float)valueMap["time"]; float time = (float)valueMap["time"];
float x = GetFloat(valueMap, "x", 0); float x = GetFloat(valueMap, "x", 0);
float y = GetFloat(valueMap, "y", 0); float y = GetFloat(valueMap, "y", 0);
timeline.SetFrame(frameIndex, time, (float)x * timelineScale, (float)y * timelineScale); timeline.SetFrame(frameIndex, time, x * timelineScale, y * timelineScale);
ReadCurve(timeline, frameIndex, valueMap); ReadCurve(valueMap, timeline, frameIndex);
frameIndex++; frameIndex++;
} }
timelines.Add(timeline); timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 3 - 3]); duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TranslateTimeline.ENTRIES]);
} else } else
throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")"); throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")");
@ -557,7 +524,7 @@ namespace Spine {
} }
} }
// IK timelines. // IK constraint timelines.
if (map.ContainsKey("ik")) { if (map.ContainsKey("ik")) {
foreach (KeyValuePair<String, Object> constraintMap in (Dictionary<String, Object>)map["ik"]) { foreach (KeyValuePair<String, Object> constraintMap in (Dictionary<String, Object>)map["ik"]) {
IkConstraintData constraint = skeletonData.FindIkConstraint(constraintMap.Key); IkConstraintData constraint = skeletonData.FindIkConstraint(constraintMap.Key);
@ -570,11 +537,11 @@ namespace Spine {
float mix = GetFloat(valueMap, "mix", 1); float mix = GetFloat(valueMap, "mix", 1);
bool bendPositive = GetBoolean(valueMap, "bendPositive", true); bool bendPositive = GetBoolean(valueMap, "bendPositive", true);
timeline.SetFrame(frameIndex, time, mix, bendPositive ? 1 : -1); timeline.SetFrame(frameIndex, time, mix, bendPositive ? 1 : -1);
ReadCurve(timeline, frameIndex, valueMap); ReadCurve(valueMap, timeline, frameIndex);
frameIndex++; frameIndex++;
} }
timelines.Add(timeline); timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 3 - 3]); duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * IkConstraintTimeline.ENTRIES]);
} }
} }
@ -593,62 +560,103 @@ namespace Spine {
float scaleMix = GetFloat(valueMap, "scaleMix", 1); float scaleMix = GetFloat(valueMap, "scaleMix", 1);
float shearMix = GetFloat(valueMap, "shearMix", 1); float shearMix = GetFloat(valueMap, "shearMix", 1);
timeline.SetFrame(frameIndex, time, rotateMix, translateMix, scaleMix, shearMix); timeline.SetFrame(frameIndex, time, rotateMix, translateMix, scaleMix, shearMix);
ReadCurve(timeline, frameIndex, valueMap); ReadCurve(valueMap, timeline, frameIndex);
frameIndex++; frameIndex++;
} }
timelines.Add(timeline); timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 5 - 5]); duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TransformConstraintTimeline.ENTRIES]);
} }
} }
// FFD timelines. // Path constraint timelines.
if (map.ContainsKey("ffd")) { if (map.ContainsKey("paths")) {
foreach (KeyValuePair<String, Object> ffdMap in (Dictionary<String, Object>)map["ffd"]) { foreach (KeyValuePair<String, Object> constraintMap in (Dictionary<String, Object>)map["paths"]) {
Skin skin = skeletonData.FindSkin(ffdMap.Key); int index = skeletonData.FindPathConstraintIndex(constraintMap.Key);
foreach (KeyValuePair<String, Object> slotMap in (Dictionary<String, Object>)ffdMap.Value) { if (index == -1) throw new Exception("Path constraint not found: " + constraintMap.Key);
int slotIndex = skeletonData.FindSlotIndex(slotMap.Key); PathConstraintData data = skeletonData.pathConstraints.Items[index];
foreach (KeyValuePair<String, Object> meshMap in (Dictionary<String, Object>)slotMap.Value) { var timelineMap = (Dictionary<String, Object>)constraintMap.Value;
var values = (List<Object>)meshMap.Value; foreach (KeyValuePair<String, Object> timelineEntry in timelineMap) {
var timeline = new FfdTimeline(values.Count); var values = (List<Object>)timelineEntry.Value;
Attachment attachment = skin.GetAttachment(slotIndex, meshMap.Key); var timelineName = (String)timelineEntry.Key;
if (attachment == null) throw new Exception("FFD attachment not found: " + meshMap.Key); if (timelineName == "position" || timelineName == "spacing") {
timeline.slotIndex = slotIndex; PathConstraintPositionTimeline timeline;
timeline.attachment = attachment; float timelineScale = 1;
if (timelineName == "spacing") {
int vertexCount; timeline = new PathConstraintSpacingTimeline(values.Count);
if (attachment is MeshAttachment) if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale;
vertexCount = ((MeshAttachment)attachment).vertices.Length; }
else else {
vertexCount = ((WeightedMeshAttachment)attachment).Weights.Length / 3 * 2; timeline = new PathConstraintPositionTimeline(values.Count);
if (data.positionMode == PositionMode.Fixed) timelineScale = scale;
}
timeline.pathConstraintIndex = index;
int frameIndex = 0; int frameIndex = 0;
foreach (Dictionary<String, Object> valueMap in values) { foreach (Dictionary<String, Object> valueMap in values) {
float[] vertices; timeline.SetFrame(frameIndex, (float)valueMap["time"], GetFloat(valueMap, timelineName, 0) * timelineScale);
ReadCurve(valueMap, timeline, frameIndex);
frameIndex++;
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintPositionTimeline.ENTRIES]);
}
else if (timelineName == "mix") {
PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(values.Count);
timeline.pathConstraintIndex = index;
int frameIndex = 0;
foreach (Dictionary<String, Object> valueMap in values) {
timeline.SetFrame(frameIndex, (float)valueMap["time"], GetFloat(valueMap, "rotateMix", 1), GetFloat(valueMap, "translateMix", 1));
ReadCurve(valueMap, timeline, frameIndex);
frameIndex++;
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintMixTimeline.ENTRIES]);
}
}
}
}
// Deform timelines.
if (map.ContainsKey("deform")) {
foreach (KeyValuePair<String, Object> deformMap in (Dictionary<String, Object>)map["deform"]) {
Skin skin = skeletonData.FindSkin(deformMap.Key);
foreach (KeyValuePair<String, Object> slotMap in (Dictionary<String, Object>)deformMap.Value) {
int slotIndex = skeletonData.FindSlotIndex(slotMap.Key);
if (slotIndex == -1) throw new Exception("Slot not found: " + slotMap.Key);
foreach (KeyValuePair<String, Object> timelineMap in (Dictionary<String, Object>)slotMap.Value) {
var values = (List<Object>)timelineMap.Value;
VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, timelineMap.Key);
if (attachment == null) throw new Exception("Deform attachment not found: " + timelineMap.Key);
bool weighted = attachment.bones != null;
float[] vertices = attachment.vertices;
int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length;
var timeline = new DeformTimeline(values.Count);
timeline.slotIndex = slotIndex;
timeline.attachment = attachment;
int frameIndex = 0;
foreach (Dictionary<String, Object> valueMap in values) {
float[] deform;
if (!valueMap.ContainsKey("vertices")) { if (!valueMap.ContainsKey("vertices")) {
if (attachment is MeshAttachment) deform = weighted ? new float[deformLength] : vertices;
vertices = ((MeshAttachment)attachment).vertices;
else
vertices = new float[vertexCount];
} else { } else {
var verticesValue = (List<Object>)valueMap["vertices"]; deform = new float[deformLength];
vertices = new float[vertexCount];
int start = GetInt(valueMap, "offset", 0); int start = GetInt(valueMap, "offset", 0);
if (scale == 1) { float[] verticesValue = GetFloatArray(valueMap, "vertices", 1);
for (int i = 0, n = verticesValue.Count; i < n; i++) Array.Copy(verticesValue, 0, deform, start, verticesValue.Length);
vertices[i + start] = (float)verticesValue[i]; if (scale != 1) {
} else { for (int i = start, n = i + verticesValue.Length; i < n; i++)
for (int i = 0, n = verticesValue.Count; i < n; i++) deform[i] *= scale;
vertices[i + start] = (float)verticesValue[i] * scale;
} }
if (attachment is MeshAttachment) {
float[] meshVertices = ((MeshAttachment)attachment).vertices; if (!weighted) {
for (int i = 0; i < vertexCount; i++) for (int i = 0; i < deformLength; i++)
vertices[i] += meshVertices[i]; deform[i] += vertices[i];
} }
} }
timeline.SetFrame(frameIndex, (float)valueMap["time"], vertices); timeline.SetFrame(frameIndex, (float)valueMap["time"], deform);
ReadCurve(timeline, frameIndex, valueMap); ReadCurve(valueMap, timeline, frameIndex);
frameIndex++; frameIndex++;
} }
timelines.Add(timeline); timelines.Add(timeline);
@ -658,6 +666,7 @@ namespace Spine {
} }
} }
// Draw order timeline.
if (map.ContainsKey("drawOrder") || map.ContainsKey("draworder")) { if (map.ContainsKey("drawOrder") || map.ContainsKey("draworder")) {
var values = (List<Object>)map[map.ContainsKey("drawOrder") ? "drawOrder" : "draworder"]; var values = (List<Object>)map[map.ContainsKey("drawOrder") ? "drawOrder" : "draworder"];
var timeline = new DrawOrderTimeline(values.Count); var timeline = new DrawOrderTimeline(values.Count);
@ -695,6 +704,7 @@ namespace Spine {
duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]);
} }
// Event timeline.
if (map.ContainsKey("events")) { if (map.ContainsKey("events")) {
var eventsMap = (List<Object>)map["events"]; var eventsMap = (List<Object>)map["events"];
var timeline = new EventTimeline(eventsMap.Count); var timeline = new EventTimeline(eventsMap.Count);
@ -716,29 +726,81 @@ namespace Spine {
skeletonData.animations.Add(new Animation(name, timelines, duration)); skeletonData.animations.Add(new Animation(name, timelines, duration));
} }
private void ReadCurve (CurveTimeline timeline, int frameIndex, Dictionary<String, Object> valueMap) { static void ReadCurve (Dictionary<String, Object> valueMap, CurveTimeline timeline, int frameIndex) {
if (!valueMap.ContainsKey("curve")) if (!valueMap.ContainsKey("curve"))
return; return;
Object curveObject = valueMap["curve"]; Object curveObject = valueMap["curve"];
if (curveObject.Equals("stepped")) if (curveObject.Equals("stepped"))
timeline.SetStepped(frameIndex); timeline.SetStepped(frameIndex);
else if (curveObject is List<Object>) { else {
var curve = (List<Object>)curveObject; var curve = curveObject as List<Object>;
timeline.SetCurve(frameIndex, (float)curve[0], (float)curve[1], (float)curve[2], (float)curve[3]); if (curve != null)
timeline.SetCurve(frameIndex, (float)curve[0], (float)curve[1], (float)curve[2], (float)curve[3]);
} }
} }
internal class LinkedMesh { internal class LinkedMesh {
internal String parent, skin; internal String parent, skin;
internal int slotIndex; internal int slotIndex;
internal Attachment mesh; internal MeshAttachment mesh;
public LinkedMesh (Attachment mesh, String skin, int slotIndex, String parent) { public LinkedMesh (MeshAttachment mesh, String skin, int slotIndex, String parent) {
this.mesh = mesh; this.mesh = mesh;
this.skin = skin; this.skin = skin;
this.slotIndex = slotIndex; this.slotIndex = slotIndex;
this.parent = parent; this.parent = parent;
} }
} }
static float[] GetFloatArray(Dictionary<String, Object> map, String name, float scale) {
var list = (List<Object>)map[name];
var values = new float[list.Count];
if (scale == 1) {
for (int i = 0, n = list.Count; i < n; i++)
values[i] = (float)list[i];
} else {
for (int i = 0, n = list.Count; i < n; i++)
values[i] = (float)list[i] * scale;
}
return values;
}
static int[] GetIntArray(Dictionary<String, Object> map, String name) {
var list = (List<Object>)map[name];
var values = new int[list.Count];
for (int i = 0, n = list.Count; i < n; i++)
values[i] = (int)(float)list[i];
return values;
}
static float GetFloat(Dictionary<String, Object> map, String name, float defaultValue) {
if (!map.ContainsKey(name))
return defaultValue;
return (float)map[name];
}
static int GetInt(Dictionary<String, Object> map, String name, int defaultValue) {
if (!map.ContainsKey(name))
return defaultValue;
return (int)(float)map[name];
}
static bool GetBoolean(Dictionary<String, Object> map, String name, bool defaultValue) {
if (!map.ContainsKey(name))
return defaultValue;
return (bool)map[name];
}
static String GetString(Dictionary<String, Object> map, String name, String defaultValue) {
if (!map.ContainsKey(name))
return defaultValue;
return (String)map[name];
}
static float ToColor(String hexString, int colorIndex) {
if (hexString.Length != 8)
throw new ArgumentException("Color hexidecimal length must be 8, recieved: " + hexString, "hexString");
return Convert.ToInt32(hexString.Substring(colorIndex * 2, 2), 16) / (float)255;
}
} }
} }

View File

@ -40,14 +40,15 @@ namespace Spine {
new Dictionary<AttachmentKeyTuple, Attachment>(AttachmentKeyTupleComparer.Instance); new Dictionary<AttachmentKeyTuple, Attachment>(AttachmentKeyTupleComparer.Instance);
public String Name { get { return name; } } public String Name { get { return name; } }
public Dictionary<AttachmentKeyTuple, Attachment> Attachments { get { return attachments; } }
public Skin (String name) { public Skin (String name) {
if (name == null) throw new ArgumentNullException("name cannot be null."); if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
this.name = name; this.name = name;
} }
public void AddAttachment (int slotIndex, String name, Attachment attachment) { public void AddAttachment (int slotIndex, String name, Attachment attachment) {
if (attachment == null) throw new ArgumentNullException("attachment cannot be null."); if (attachment == null) throw new ArgumentNullException("attachment", "attachment cannot be null.");
attachments[new AttachmentKeyTuple(slotIndex, name)] = attachment; attachments[new AttachmentKeyTuple(slotIndex, name)] = attachment;
} }
@ -59,13 +60,13 @@ namespace Spine {
} }
public void FindNamesForSlot (int slotIndex, List<String> names) { public void FindNamesForSlot (int slotIndex, List<String> names) {
if (names == null) throw new ArgumentNullException("names cannot be null."); if (names == null) throw new ArgumentNullException("names", "names cannot be null.");
foreach (AttachmentKeyTuple key in attachments.Keys) foreach (AttachmentKeyTuple key in attachments.Keys)
if (key.slotIndex == slotIndex) names.Add(key.name); if (key.slotIndex == slotIndex) names.Add(key.name);
} }
public void FindAttachmentsForSlot (int slotIndex, List<Attachment> attachments) { public void FindAttachmentsForSlot (int slotIndex, List<Attachment> attachments) {
if (attachments == null) throw new ArgumentNullException("attachments cannot be null."); if (attachments == null) throw new ArgumentNullException("attachments", "attachments cannot be null.");
foreach (KeyValuePair<AttachmentKeyTuple, Attachment> entry in this.attachments) foreach (KeyValuePair<AttachmentKeyTuple, Attachment> entry in this.attachments)
if (entry.Key.slotIndex == slotIndex) attachments.Add(entry.Value); if (entry.Key.slotIndex == slotIndex) attachments.Add(entry.Value);
} }
@ -86,7 +87,7 @@ namespace Spine {
} }
} }
struct AttachmentKeyTuple { public struct AttachmentKeyTuple {
public readonly int slotIndex; public readonly int slotIndex;
public readonly string name; public readonly string name;
internal readonly int nameHashCode; internal readonly int nameHashCode;

View File

@ -38,8 +38,7 @@ namespace Spine {
internal float r, g, b, a; internal float r, g, b, a;
internal Attachment attachment; internal Attachment attachment;
internal float attachmentTime; internal float attachmentTime;
internal float[] attachmentVertices = new float[0]; internal ExposedList<float> attachmentVertices = new ExposedList<float>();
internal int attachmentVerticesCount;
public SlotData Data { get { return data; } } public SlotData Data { get { return data; } }
public Bone Bone { get { return bone; } } public Bone Bone { get { return bone; } }
@ -51,38 +50,31 @@ namespace Spine {
/// <summary>May be null.</summary> /// <summary>May be null.</summary>
public Attachment Attachment { public Attachment Attachment {
get { get { return attachment; }
return attachment;
}
set { set {
if (attachment == value) return; if (attachment == value) return;
attachment = value; attachment = value;
attachmentTime = bone.skeleton.time; attachmentTime = bone.skeleton.time;
attachmentVerticesCount = 0; attachmentVertices.Clear(false);
} }
} }
public float AttachmentTime { public float AttachmentTime {
get { get { return bone.skeleton.time - attachmentTime; }
return bone.skeleton.time - attachmentTime; set { attachmentTime = bone.skeleton.time - value; }
}
set {
attachmentTime = bone.skeleton.time - value;
}
} }
public float[] AttachmentVertices { get { return attachmentVertices; } set { attachmentVertices = value; } } public ExposedList<float> AttachmentVertices { get { return attachmentVertices; } set { attachmentVertices = value; } }
public int AttachmentVerticesCount { get { return attachmentVerticesCount; } set { attachmentVerticesCount = value; } }
public Slot (SlotData data, Bone bone) { public Slot (SlotData data, Bone bone) {
if (data == null) throw new ArgumentNullException("data cannot be null."); if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
if (bone == null) throw new ArgumentNullException("bone cannot be null."); if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null.");
this.data = data; this.data = data;
this.bone = bone; this.bone = bone;
SetToSetupPose(); SetToSetupPose();
} }
internal void SetToSetupPose (int slotIndex) { public void SetToSetupPose () {
r = data.r; r = data.r;
g = data.g; g = data.g;
b = data.b; b = data.b;
@ -91,14 +83,10 @@ namespace Spine {
Attachment = null; Attachment = null;
else { else {
attachment = null; attachment = null;
Attachment = bone.skeleton.GetAttachment(slotIndex, data.attachmentName); Attachment = bone.skeleton.GetAttachment(data.index, data.attachmentName);
} }
} }
public void SetToSetupPose () {
SetToSetupPose(bone.skeleton.data.slots.IndexOf(data));
}
override public String ToString () { override public String ToString () {
return data.name; return data.name;
} }

View File

@ -33,12 +33,14 @@ using System;
namespace Spine { namespace Spine {
public class SlotData { public class SlotData {
internal int index;
internal String name; internal String name;
internal BoneData boneData; internal BoneData boneData;
internal float r = 1, g = 1, b = 1, a = 1; internal float r = 1, g = 1, b = 1, a = 1;
internal String attachmentName; internal String attachmentName;
internal BlendMode blendMode; internal BlendMode blendMode;
public int Index { get { return index; } }
public String Name { get { return name; } } public String Name { get { return name; } }
public BoneData BoneData { get { return boneData; } } public BoneData BoneData { get { return boneData; } }
public float R { get { return r; } set { r = value; } } public float R { get { return r; } set { r = value; } }
@ -49,9 +51,11 @@ namespace Spine {
public String AttachmentName { get { return attachmentName; } set { attachmentName = value; } } public String AttachmentName { get { return attachmentName; } set { attachmentName = value; } }
public BlendMode BlendMode { get { return blendMode; } set { blendMode = value; } } public BlendMode BlendMode { get { return blendMode; } set { blendMode = value; } }
public SlotData (String name, BoneData boneData) { public SlotData (int index, String name, BoneData boneData) {
if (name == null) throw new ArgumentNullException("name cannot be null."); if (index < 0) throw new ArgumentException ("index must be >= 0.", "index");
if (boneData == null) throw new ArgumentNullException("boneData cannot be null."); if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
if (boneData == null) throw new ArgumentNullException("boneData", "boneData cannot be null.");
this.index = index;
this.name = name; this.name = name;
this.boneData = boneData; this.boneData = boneData;
} }

View File

@ -30,46 +30,35 @@
*****************************************************************************/ *****************************************************************************/
using System; using System;
using System.Collections.Generic;
namespace Spine { namespace Spine {
public class TransformConstraint : IUpdatable { public class TransformConstraint : IUpdatable {
internal TransformConstraintData data; internal TransformConstraintData data;
internal Bone bone, target; internal ExposedList<Bone> bones;
internal Bone target;
internal float rotateMix, translateMix, scaleMix, shearMix; internal float rotateMix, translateMix, scaleMix, shearMix;
internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY;
public TransformConstraintData Data { get { return data; } } public TransformConstraintData Data { get { return data; } }
public Bone Bone { get { return bone; } set { bone = value; } } public ExposedList<Bone> Bones { get { return bones; } }
public Bone Target { get { return target; } set { target = value; } } public Bone Target { get { return target; } set { target = value; } }
public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } public float RotateMix { get { return rotateMix; } set { rotateMix = value; } }
public float TranslateMix { get { return translateMix; } set { translateMix = value; } } public float TranslateMix { get { return translateMix; } set { translateMix = value; } }
public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } } public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } }
public float ShearMix { get { return shearMix; } set { shearMix = value; } } public float ShearMix { get { return shearMix; } set { shearMix = value; } }
public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } }
public float OffsetX { get { return offsetX; } set { offsetX = value; } }
public float OffsetY { get { return offsetY; } set { offsetY = value; } }
public float OffsetScaleX { get { return offsetScaleX; } set { offsetScaleX = value; } }
public float OffsetScaleY { get { return offsetScaleY; } set { offsetScaleY = value; } }
public float OffsetShearY { get { return offsetShearY; } set { offsetShearY = value; } }
public TransformConstraint (TransformConstraintData data, Skeleton skeleton) { public TransformConstraint (TransformConstraintData data, Skeleton skeleton) {
if (data == null) throw new ArgumentNullException("data cannot be null."); if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null.");
this.data = data; this.data = data;
translateMix = data.translateMix;
rotateMix = data.rotateMix; rotateMix = data.rotateMix;
translateMix = data.translateMix;
scaleMix = data.scaleMix; scaleMix = data.scaleMix;
shearMix = data.shearMix; shearMix = data.shearMix;
offsetRotation = data.offsetRotation;
offsetX = data.offsetX;
offsetY = data.offsetY;
offsetScaleX = data.offsetScaleX;
offsetScaleY = data.offsetScaleY;
offsetShearY = data.offsetShearY;
bone = skeleton.FindBone(data.bone.name); bones = new ExposedList<Bone>();
foreach (BoneData boneData in data.bones)
bones.Add (skeleton.FindBone (boneData.name));
target = skeleton.FindBone(data.target.name); target = skeleton.FindBone(data.target.name);
} }
@ -78,55 +67,59 @@ namespace Spine {
} }
public void Update () { public void Update () {
Bone bone = this.bone; float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix;
Bone target = this.target; Bone target = this.target;
float ta = target.a, tb = target.b, tc = target.c, td = target.d;
ExposedList<Bone> bones = this.bones;
for (int i = 0, n = bones.Count; i < n; i++) {
Bone bone = bones.Items[i];
if (rotateMix > 0) { if (rotateMix > 0) {
float a = bone.a, b = bone.b, c = bone.c, d = bone.d; float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
float r = MathUtils.Atan2(target.c, target.a) - MathUtils.Atan2(c, a) + offsetRotation * MathUtils.degRad; float r = (float)Math.Atan2(tc, ta) - (float)Math.Atan2(c, a) + data.offsetRotation * MathUtils.degRad;
if (r > MathUtils.PI) if (r > MathUtils.PI)
r -= MathUtils.PI2; r -= MathUtils.PI2;
else if (r < -MathUtils.PI) r += MathUtils.PI2; else if (r < -MathUtils.PI) r += MathUtils.PI2;
r *= rotateMix; r *= rotateMix;
float cos = MathUtils.Cos(r), sin = MathUtils.Sin(r); float cos = MathUtils.Cos(r), sin = MathUtils.Sin(r);
bone.a = cos * a - sin * c; bone.a = cos * a - sin * c;
bone.b = cos * b - sin * d; bone.b = cos * b - sin * d;
bone.c = sin * a + cos * c; bone.c = sin * a + cos * c;
bone.d = sin * b + cos * d; bone.d = sin * b + cos * d;
} }
if (scaleMix > 0) { if (translateMix > 0) {
float bs = (float)Math.Sqrt(bone.a * bone.a + bone.c * bone.c); float tempx, tempy;
float ts = (float)Math.Sqrt(target.a * target.a + target.c * target.c); target.LocalToWorld(data.offsetX, data.offsetY, out tempx, out tempy);
float s = bs > 0.00001f ? (bs + (ts - bs + offsetScaleX) * scaleMix) / bs : 0; bone.worldX += (tempx - bone.worldX) * translateMix;
bone.a *= s; bone.worldY += (tempy - bone.worldY) * translateMix;
bone.c *= s; }
bs = (float)Math.Sqrt(bone.b * bone.b + bone.d * bone.d);
ts = (float)Math.Sqrt(target.b * target.b + target.d * target.d);
s = bs > 0.00001f ? (bs + (ts - bs + offsetScaleY) * scaleMix) / bs : 0;
bone.b *= s;
bone.d *= s;
}
if (shearMix > 0) { if (scaleMix > 0) {
float b = bone.b, d = bone.d; float bs = (float)Math.Sqrt(bone.a * bone.a + bone.c * bone.c);
float by = MathUtils.Atan2(d, b); float ts = (float)Math.Sqrt(ta * ta + tc * tc);
float r = MathUtils.Atan2(target.d, target.b) - MathUtils.Atan2(target.c, target.a) - (by - MathUtils.Atan2(bone.c, bone.a)); float s = bs > 0.00001f ? (bs + (ts - bs + data.offsetScaleX) * scaleMix) / bs : 0;
if (r > MathUtils.PI) bone.a *= s;
r -= MathUtils.PI2; bone.c *= s;
else if (r < -MathUtils.PI) r += MathUtils.PI2; bs = (float)Math.Sqrt(bone.b * bone.b + bone.d * bone.d);
r = by + (r + offsetShearY * MathUtils.degRad) * shearMix; ts = (float)Math.Sqrt(tb * tb + td * td);
float s = (float)Math.Sqrt(b * b + d * d); s = bs > 0.00001f ? (bs + (ts - bs + data.offsetScaleY) * scaleMix) / bs : 0;
bone.b = MathUtils.Cos(r) * s; bone.b *= s;
bone.d = MathUtils.Sin(r) * s; bone.d *= s;
} }
float translateMix = this.translateMix; if (shearMix > 0) {
if (translateMix > 0) { float b = bone.b, d = bone.d;
float tx, ty; float by = MathUtils.Atan2(d, b);
target.LocalToWorld(offsetX, offsetY, out tx, out ty); float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta) - (by - MathUtils.Atan2(bone.c, bone.a));
bone.worldX += (tx - bone.worldX) * translateMix; if (r > MathUtils.PI)
bone.worldY += (ty - bone.worldY) * translateMix; r -= MathUtils.PI2;
else if (r < -MathUtils.PI) r += MathUtils.PI2;
r = by + (r + data.offsetShearY * MathUtils.degRad) * shearMix;
float s = (float)Math.Sqrt(b * b + d * d);
bone.b = MathUtils.Cos(r) * s;
bone.d = MathUtils.Sin(r) * s;
}
} }
} }

View File

@ -30,17 +30,17 @@
*****************************************************************************/ *****************************************************************************/
using System; using System;
using System.Collections.Generic;
namespace Spine { namespace Spine {
public class TransformConstraintData { public class TransformConstraintData {
internal String name; internal String name;
internal BoneData bone, target; internal ExposedList<BoneData> bones = new ExposedList<BoneData>();
internal BoneData target;
internal float rotateMix, translateMix, scaleMix, shearMix; internal float rotateMix, translateMix, scaleMix, shearMix;
internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY; internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY;
public String Name { get { return name; } } public String Name { get { return name; } }
public BoneData Bone { get { return bone; } set { bone = value; } } public ExposedList<BoneData> Bones { get { return bones; } }
public BoneData Target { get { return target; } set { target = value; } } public BoneData Target { get { return target; } set { target = value; } }
public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } public float RotateMix { get { return rotateMix; } set { rotateMix = value; } }
public float TranslateMix { get { return translateMix; } set { translateMix = value; } } public float TranslateMix { get { return translateMix; } set { translateMix = value; } }
@ -55,7 +55,7 @@ namespace Spine {
public float OffsetShearY { get { return offsetShearY; } set { offsetShearY = value; } } public float OffsetShearY { get { return offsetShearY; } set { offsetShearY = value; } }
public TransformConstraintData (String name) { public TransformConstraintData (String name) {
if (name == null) throw new ArgumentNullException("name cannot be null."); if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
this.name = name; this.name = name;
} }

View File

@ -167,7 +167,7 @@ public class SkeletonJson {
String targetName = constraintMap.getString("target"); String targetName = constraintMap.getString("target");
data.target = skeletonData.findBone(targetName); data.target = skeletonData.findBone(targetName);
if (data.target == null) throw new SerializationException("Target bone not found: " + targetName); if (data.target == null) throw new SerializationException("IK target bone not found: " + targetName);
data.bendDirection = constraintMap.getBoolean("bendPositive", true) ? 1 : -1; data.bendDirection = constraintMap.getBoolean("bendPositive", true) ? 1 : -1;
data.mix = constraintMap.getFloat("mix", 1); data.mix = constraintMap.getFloat("mix", 1);
@ -182,13 +182,13 @@ public class SkeletonJson {
for (JsonValue boneMap = constraintMap.getChild("bones"); boneMap != null; boneMap = boneMap.next) { for (JsonValue boneMap = constraintMap.getChild("bones"); boneMap != null; boneMap = boneMap.next) {
String boneName = boneMap.asString(); String boneName = boneMap.asString();
BoneData bone = skeletonData.findBone(boneName); BoneData bone = skeletonData.findBone(boneName);
if (bone == null) throw new SerializationException("Path bone not found: " + boneName); if (bone == null) throw new SerializationException("Transform constraint bone not found: " + boneName);
data.bones.add(bone); data.bones.add(bone);
} }
String targetName = constraintMap.getString("target"); String targetName = constraintMap.getString("target");
data.target = skeletonData.findBone(targetName); data.target = skeletonData.findBone(targetName);
if (data.target == null) throw new SerializationException("Target bone not found: " + targetName); if (data.target == null) throw new SerializationException("Transform constraint target bone not found: " + targetName);
data.offsetRotation = constraintMap.getFloat("rotation", 0); data.offsetRotation = constraintMap.getFloat("rotation", 0);
data.offsetX = constraintMap.getFloat("x", 0) * scale; data.offsetX = constraintMap.getFloat("x", 0) * scale;
@ -218,7 +218,7 @@ public class SkeletonJson {
String targetName = constraintMap.getString("target"); String targetName = constraintMap.getString("target");
data.target = skeletonData.findSlot(targetName); data.target = skeletonData.findSlot(targetName);
if (data.target == null) throw new SerializationException("Target slot not found: " + targetName); if (data.target == null) throw new SerializationException("Path target slot not found: " + targetName);
data.positionMode = PositionMode.valueOf(constraintMap.getString("positionMode", "percent")); data.positionMode = PositionMode.valueOf(constraintMap.getString("positionMode", "percent"));
data.spacingMode = SpacingMode.valueOf(constraintMap.getString("spacingMode", "length")); data.spacingMode = SpacingMode.valueOf(constraintMap.getString("spacingMode", "length"));

View File

@ -10,7 +10,7 @@ The Spine Runtimes are developed with the intent to be used with data exported f
## Spine version ## Spine version
spine-monogame works with data exported from Spine 3.2.01. Updating spine-monogame to [v3.3](https://github.com/EsotericSoftware/spine-runtimes/issues/613) is in progress. spine-monogame works with data exported from Spine 3.3.07.
spine-monogame supports all Spine features. spine-monogame supports all Spine features.

View File

@ -50,9 +50,27 @@ public class SpineboyBeginnerModel : MonoBehaviour {
IEnumerator JumpRoutine () { IEnumerator JumpRoutine () {
if (state == SpineBeginnerBodyState.Jumping) yield break; // Don't jump when already jumping. if (state == SpineBeginnerBodyState.Jumping) yield break; // Don't jump when already jumping.
// Fake jumping.
state = SpineBeginnerBodyState.Jumping; state = SpineBeginnerBodyState.Jumping;
yield return new WaitForSeconds(1.2f);
// Terribly-coded Fake jumping.
{
var pos = transform.localPosition;
const float jumpTime = 1.2f;
const float half = jumpTime * 0.5f;
const float jumpPower = 20f;
for (float t = 0; t < half; t += Time.deltaTime) {
float d = jumpPower * (half - t);
transform.Translate((d * Time.deltaTime) * Vector3.up);
yield return null;
}
for (float t = 0; t < half; t += Time.deltaTime) {
float d = jumpPower * t;
transform.Translate((d * Time.deltaTime) * Vector3.down);
yield return null;
}
transform.localPosition = pos;
}
state = SpineBeginnerBodyState.Idle; state = SpineBeginnerBodyState.Idle;
} }

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 6bc52290ef03f2846ba38d67e2823598 guid: 6bc52290ef03f2846ba38d67e2823598
timeCreated: 1455501336 timeCreated: 1467205225
licenseType: Free licenseType: Free
TextureImporter: TextureImporter:
fileIDToRecycleName: {} fileIDToRecycleName: {}

View File

@ -1,6 +1,6 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 12c126994123f12468cf4c5a2684078a guid: 12c126994123f12468cf4c5a2684078a
timeCreated: 1455501336 timeCreated: 1467205225
licenseType: Free licenseType: Free
TextureImporter: TextureImporter:
fileIDToRecycleName: {} fileIDToRecycleName: {}

View File

@ -1 +1,36 @@
{"skeleton":{"hash":"LxkXoqW3SPQ5weFYd4V71R86WUk","spine":"2.0.20","width":868,"height":322},"bones":[{"name":"root"},{"name":"L_Eye","parent":"root","x":-223.18,"y":2.99},{"name":"R_Eye","parent":"root","x":237.32,"y":6.4}],"slots":[{"name":"EyeWhite","bone":"root","attachment":"EyeWhite"},{"name":"R_Eye","bone":"R_Eye","attachment":"R_Eye"},{"name":"L_Eye","bone":"L_Eye","attachment":"L_Eye"},{"name":"EyeLines","bone":"root","attachment":"EyeLines"}],"skins":{"default":{"EyeLines":{"EyeLines":{"y":28,"width":868,"height":322}},"EyeWhite":{"EyeWhite":{"x":3,"y":2,"width":700,"height":148}},"L_Eye":{"L_Eye":{"x":-0.82,"y":2,"width":148,"height":148}},"R_Eye":{"R_Eye":{"x":0.67,"y":-1.4,"width":148,"height":148}}}}} {
"skeleton": {
"hash": "4JK7uGWbzO7qeQSyyuOyKLXQ5oI",
"spine": "3.3.07",
"width": 868,
"height": 322,
"images": "C:\\Users\\John Eric\\Desktop\\old exports"
},
"bones": [
{ "name": "root" },
{ "name": "L_Eye", "parent": "root", "x": -223.18, "y": 2.99 },
{ "name": "R_Eye", "parent": "root", "x": 237.32, "y": 6.4 }
],
"slots": [
{ "name": "EyeWhite", "bone": "root", "attachment": "EyeWhite" },
{ "name": "R_Eye", "bone": "R_Eye", "attachment": "R_Eye" },
{ "name": "L_Eye", "bone": "L_Eye", "attachment": "L_Eye" },
{ "name": "EyeLines", "bone": "root", "attachment": "EyeLines" }
],
"skins": {
"default": {
"EyeLines": {
"EyeLines": { "y": 28, "width": 868, "height": 322 }
},
"EyeWhite": {
"EyeWhite": { "x": 3, "y": 2, "width": 700, "height": 148 }
},
"L_Eye": {
"L_Eye": { "x": -0.82, "y": 2, "width": 148, "height": 148 }
},
"R_Eye": {
"R_Eye": { "x": 0.67, "y": -1.4, "width": 148, "height": 148 }
}
}
}
}

View File

@ -1,6 +1,6 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 49441e5a1682e564694545bd9b509785 guid: 49441e5a1682e564694545bd9b509785
timeCreated: 1455501336 timeCreated: 1467205225
licenseType: Free licenseType: Free
TextureImporter: TextureImporter:
fileIDToRecycleName: {} fileIDToRecycleName: {}

View File

@ -1,6 +1,6 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: ddb89f63d0296cf4f8572b0448bb6b30 guid: ddb89f63d0296cf4f8572b0448bb6b30
timeCreated: 1455501337 timeCreated: 1467205225
licenseType: Free licenseType: Free
TextureImporter: TextureImporter:
fileIDToRecycleName: {} fileIDToRecycleName: {}

View File

@ -1,6 +1,6 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 57b57f94df266f94ea0981915a4472e1 guid: 57b57f94df266f94ea0981915a4472e1
timeCreated: 1455501336 timeCreated: 1467205225
licenseType: Free licenseType: Free
TextureImporter: TextureImporter:
fileIDToRecycleName: {} fileIDToRecycleName: {}

View File

@ -1,18 +1,18 @@
{ {
"skeleton": { "hash": "uZfvh80BNvvngM3EGMfAkHebg00", "spine": "2.1.08", "width": 147.68, "height": 268.92, "images": "./images/" }, "skeleton": { "hash": "Nxia9637znam+9FXwv6fILb3hpo", "spine": "3.3.07", "width": 0, "height": 0, "images": "./images/" },
"bones": [ "bones": [
{ "name": "Root" }, { "name": "Root" },
{ "name": "Hip", "parent": "Root", "x": -0.93, "y": 73.4 }, { "name": "Hip", "parent": "Root", "x": -0.93, "y": 73.4 },
{ "name": "Body", "parent": "Hip", "length": 60.98, "x": 2.46, "y": -7.69, "rotation": 89.52 }, { "name": "Body", "parent": "Hip", "length": 60.98, "rotation": 89.52, "x": 2.46, "y": -7.69 },
{ "name": "Leg", "parent": "Hip", "length": 31.39, "x": -20.32, "y": -13.85, "rotation": -105.82 }, { "name": "Arm", "parent": "Body", "length": 51.63, "rotation": 166.67, "x": 49.91, "y": 37.34 },
{ "name": "Leg2", "parent": "Hip", "length": 31.09, "x": 22.48, "y": -12.01, "rotation": -74.17 }, { "name": "Arm2", "parent": "Body", "length": 52.61, "rotation": -157.17, "x": 53.81, "y": -28.52 },
{ "name": "Arm", "parent": "Body", "length": 51.63, "x": 49.91, "y": 37.35, "rotation": 166.67 }, { "name": "Leg", "parent": "Hip", "length": 31.39, "rotation": -105.82, "x": -20.32, "y": -13.85 },
{ "name": "Arm2", "parent": "Body", "length": 52.61, "x": 53.81, "y": -28.52, "rotation": -157.17 }, { "name": "Feet", "parent": "Leg", "length": 15.4, "rotation": 14.56, "x": 39.56, "y": 1.59 },
{ "name": "Feet", "parent": "Leg", "length": 15.4, "x": 39.56, "y": 1.59, "rotation": 14.56 }, { "name": "Leg2", "parent": "Hip", "length": 31.09, "rotation": -74.17, "x": 22.48, "y": -12.01 },
{ "name": "Feet2", "parent": "Leg2", "length": 12.32, "x": 41.33, "y": 0.12, "rotation": -17.19 }, { "name": "Feet2", "parent": "Leg2", "length": 12.32, "rotation": -17.19, "x": 41.33, "y": 0.12 },
{ "name": "Head", "parent": "Body", "length": 65.29, "x": 73.6, "y": 1.09, "rotation": -88.23 }, { "name": "Head", "parent": "Body", "length": 65.29, "rotation": -88.23, "x": 73.6, "y": 1.09 },
{ "name": "Shield", "parent": "Arm", "x": 45.01, "y": -2.1, "rotation": 123.56 }, { "name": "Shield", "parent": "Arm", "rotation": 123.56, "x": 45.01, "y": -2.09 },
{ "name": "Weapon", "parent": "Arm2", "length": 137.65, "x": 48.2, "y": 12.78, "rotation": 92.5 } { "name": "Weapon", "parent": "Arm2", "length": 137.64, "rotation": 92.5, "x": 48.2, "y": 12.78 }
], ],
"slots": [ "slots": [
{ "name": "Arm2", "bone": "Arm2", "attachment": "Arm2" }, { "name": "Arm2", "bone": "Arm2", "attachment": "Arm2" },
@ -61,7 +61,7 @@
"Leg": { "name": "leg", "path": "White/leg", "x": 16.86, "y": -4.3, "rotation": 104.82, "width": 48, "height": 55 } "Leg": { "name": "leg", "path": "White/leg", "x": 16.86, "y": -4.3, "rotation": 104.82, "width": 48, "height": 55 }
}, },
"Leg2": { "Leg2": {
"Leg2": { "name": "leg 2", "path": "White/leg 2", "x": 16.44, "y": -2.11, "rotation": 74.17, "width": 50, "height": 58 } "Leg2": { "name": "leg 2", "path": "White/leg 2", "x": 16.44, "y": -2.1, "rotation": 74.17, "width": 50, "height": 58 }
}, },
"Mouth": { "Mouth": {
"Closed": { "path": "White/mouth", "x": 10.96, "y": 3.69, "rotation": -1.29, "width": 28, "height": 21 } "Closed": { "path": "White/mouth", "x": 10.96, "y": 3.69, "rotation": -1.29, "width": 28, "height": 21 }
@ -239,11 +239,11 @@
"curve": [ 0.25, 0, 0.75, 1 ] "curve": [ 0.25, 0, 0.75, 1 ]
}, },
{ "time": 0.3333, "x": 4.5, "y": -0.72 }, { "time": 0.3333, "x": 4.5, "y": -0.72 },
{ "time": 0.4666, "x": -1.04, "y": -2.35, "curve": "stepped" }, { "time": 0.4666, "x": -1.04, "y": -2.34, "curve": "stepped" },
{ {
"time": 0.6, "time": 0.6,
"x": -1.04, "x": -1.04,
"y": -2.35, "y": -2.34,
"curve": [ 0.25, 0, 0.75, 1 ] "curve": [ 0.25, 0, 0.75, 1 ]
}, },
{ "time": 0.8333, "x": 0, "y": 0 } { "time": 0.8333, "x": 0, "y": 0 }
@ -345,7 +345,7 @@
"translate": [ "translate": [
{ "time": 0, "x": 0, "y": 0 }, { "time": 0, "x": 0, "y": 0 },
{ "time": 0.2, "x": -50.02, "y": -6.59 }, { "time": 0.2, "x": -50.02, "y": -6.59 },
{ "time": 0.3333, "x": -83.36, "y": -37.67 } { "time": 0.3333, "x": -83.36, "y": -37.66 }
] ]
}, },
"Body": { "Body": {
@ -491,7 +491,7 @@
"y": 0, "y": 0,
"curve": [ 0.25, 0, 0.758, 0.67 ] "curve": [ 0.25, 0, 0.758, 0.67 ]
}, },
{ "time": 0.3333, "x": -4.47, "y": 3.29 } { "time": 0.3333, "x": -4.46, "y": 3.29 }
] ]
}, },
"Leg2": { "Leg2": {
@ -686,7 +686,7 @@
}, },
{ {
"time": 0.3666, "time": 0.3666,
"angle": -2.34, "angle": -2.33,
"curve": [ 0.25, 0, 0.75, 1 ] "curve": [ 0.25, 0, 0.75, 1 ]
}, },
{ "time": 0.8666, "angle": 0 }, { "time": 0.8666, "angle": 0 },
@ -1518,17 +1518,17 @@
{ {
"time": 0, "time": 0,
"x": -0.84, "x": -0.84,
"y": -4.45, "y": -4.44,
"curve": [ 0.25, 0, 0.75, 1 ] "curve": [ 0.25, 0, 0.75, 1 ]
}, },
{ "time": 0.2, "x": -1.03, "y": -4.7, "curve": "stepped" }, { "time": 0.2, "x": -1.03, "y": -4.69, "curve": "stepped" },
{ {
"time": 0.5333, "time": 0.5333,
"x": -1.03, "x": -1.03,
"y": -4.7, "y": -4.69,
"curve": [ 0.25, 0, 0.75, 1 ] "curve": [ 0.25, 0, 0.75, 1 ]
}, },
{ "time": 0.6666, "x": -0.84, "y": -4.45 } { "time": 0.6666, "x": -0.84, "y": -4.44 }
] ]
}, },
"Feet2": { "Feet2": {

View File

@ -1,5 +1,5 @@
{ {
"skeleton": { "hash": "IKLl/62j+Y1bsQ8rdHoVK9PDip8", "spine": "2.1.27", "width": 250, "height": 60, "images": "./images/" }, "skeleton": { "hash": "C69Pg+RG3DYyHmt9bOPYVrESJBQ", "spine": "3.3.07", "width": 250, "height": 60, "images": "./images/" },
"bones": [ "bones": [
{ "name": "root" }, { "name": "root" },
{ "name": "Bar", "parent": "root", "x": -112.29 } { "name": "Bar", "parent": "root", "x": -112.29 }

View File

@ -1,6 +1,6 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: a11301aad15ed6b4995485a02a81b132 guid: a11301aad15ed6b4995485a02a81b132
timeCreated: 1455501336 timeCreated: 1467205225
licenseType: Free licenseType: Free
TextureImporter: TextureImporter:
fileIDToRecycleName: {} fileIDToRecycleName: {}

View File

@ -1,4 +0,0 @@
fileFormatVersion: 2
guid: 3586e5ccd2041c24eb20eb4764168abd
TextScriptImporter:
userData:

File diff suppressed because one or more lines are too long

View File

@ -1,4 +0,0 @@
fileFormatVersion: 2
guid: f3a3248bc50115241ae81702fde448eb
TextScriptImporter:
userData:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 KiB

View File

@ -1,4 +0,0 @@
fileFormatVersion: 2
guid: df2c4ad0c6709fd4f9b1c19ab43878ae
NativeFormatImporter:
userData:

View File

@ -1,4 +0,0 @@
fileFormatVersion: 2
guid: 64a66fecd89237b478156e7cc4d2da4a
NativeFormatImporter:
userData:

View File

@ -1,4 +0,0 @@
fileFormatVersion: 2
guid: 066917a2cc5e8824b9b7e2944feee6f1
NativeFormatImporter:
userData:

View File

@ -1,292 +1,292 @@
goblins-mesh.png goblins.png
size: 1024,128 size: 795,142
format: RGBA8888 format: RGBA8888
filter: Linear,Linear filter: Linear,Linear
repeat: none repeat: none
dagger dagger
rotate: true rotate: false
xy: 372, 100 xy: 1, 33
size: 26, 108 size: 26, 108
orig: 26, 108 orig: 26, 108
offset: 0, 0 offset: 0, 0
index: -1 index: -1
goblin/eyes-closed goblin/eyes-closed
rotate: false rotate: false
xy: 2, 7 xy: 760, 129
size: 34, 12 size: 34, 12
orig: 34, 12 orig: 34, 12
offset: 0, 0 offset: 0, 0
index: -1 index: -1
goblin/head goblin/head
rotate: false rotate: true
xy: 107, 36 xy: 110, 38
size: 103, 66 size: 103, 66
orig: 103, 66 orig: 103, 66
offset: 0, 0 offset: 0, 0
index: -1 index: -1
goblin/left-arm goblin/left-arm
rotate: false rotate: true
xy: 901, 56 xy: 659, 7
size: 37, 35 size: 37, 35
orig: 37, 35 orig: 37, 35
offset: 0, 0 offset: 0, 0
index: -1 index: -1
goblin/left-foot goblin/left-foot
rotate: false rotate: false
xy: 929, 95 xy: 1, 1
size: 65, 31 size: 65, 31
orig: 65, 31 orig: 65, 31
offset: 0, 0 offset: 0, 0
index: -1 index: -1
goblin/left-hand goblin/left-hand
rotate: false rotate: false
xy: 452, 2 xy: 347, 3
size: 36, 41 size: 36, 41
orig: 36, 41 orig: 36, 41
offset: 0, 0 offset: 0, 0
index: -1 index: -1
goblin/left-lower-leg goblin/left-lower-leg
rotate: true rotate: false
xy: 713, 93 xy: 420, 71
size: 33, 70 size: 33, 70
orig: 33, 70 orig: 33, 70
offset: 0, 0 offset: 0, 0
index: -1 index: -1
goblin/left-shoulder goblin/left-shoulder
rotate: false rotate: true
xy: 610, 44 xy: 684, 48
size: 29, 44 size: 29, 44
orig: 29, 44 orig: 29, 44
offset: 0, 0 offset: 0, 0
index: -1 index: -1
goblin/left-upper-leg goblin/left-upper-leg
rotate: true rotate: false
xy: 638, 93 xy: 315, 68
size: 33, 73 size: 33, 73
orig: 33, 73 orig: 33, 73
offset: 0, 0 offset: 0, 0
index: -1 index: -1
goblin/neck goblin/neck
rotate: false rotate: false
xy: 490, 2 xy: 384, 3
size: 36, 41 size: 36, 41
orig: 36, 41 orig: 36, 41
offset: 0, 0 offset: 0, 0
index: -1 index: -1
goblin/pelvis goblin/pelvis
rotate: false rotate: false
xy: 482, 45 xy: 221, 1
size: 62, 43 size: 62, 43
orig: 62, 43 orig: 62, 43
offset: 0, 0 offset: 0, 0
index: -1 index: -1
goblin/right-arm goblin/right-arm
rotate: true rotate: false
xy: 690, 2 xy: 732, 91
size: 23, 50 size: 23, 50
orig: 23, 50 orig: 23, 50
offset: 0, 0 offset: 0, 0
index: -1 index: -1
goblin/right-foot goblin/right-foot
rotate: false rotate: true
xy: 771, 58 xy: 624, 78
size: 63, 33 size: 63, 33
orig: 63, 33 orig: 63, 33
offset: 0, 0 offset: 0, 0
index: -1 index: -1
goblin/right-hand goblin/right-hand
rotate: false rotate: false
xy: 940, 56 xy: 585, 7
size: 36, 37 size: 36, 37
orig: 36, 37 orig: 36, 37
offset: 0, 0 offset: 0, 0
index: -1 index: -1
goblin/right-lower-leg goblin/right-lower-leg
rotate: true rotate: true
xy: 482, 90 xy: 67, 1
size: 36, 76 size: 36, 76
orig: 36, 76 orig: 36, 76
offset: 0, 0 offset: 0, 0
index: -1 index: -1
goblin/right-shoulder goblin/right-shoulder
rotate: true rotate: true
xy: 602, 3 xy: 493, 5
size: 39, 45 size: 39, 45
orig: 39, 45 orig: 39, 45
offset: 0, 0 offset: 0, 0
index: -1 index: -1
goblin/right-upper-leg goblin/right-upper-leg
rotate: true rotate: false
xy: 641, 57 xy: 554, 78
size: 34, 63 size: 34, 63
orig: 34, 63 orig: 34, 63
offset: 0, 0 offset: 0, 0
index: -1 index: -1
goblin/torso goblin/torso
rotate: true rotate: false
xy: 212, 34 xy: 177, 45
size: 68, 96 size: 68, 96
orig: 68, 96 orig: 68, 96
offset: 0, 0 offset: 0, 0
index: -1 index: -1
goblin/undie-straps goblin/undie-straps
rotate: false rotate: true
xy: 380, 5 xy: 692, 86
size: 55, 19 size: 55, 19
orig: 55, 19 orig: 55, 19
offset: 0, 0 offset: 0, 0
index: -1 index: -1
goblin/undies goblin/undies
rotate: false rotate: false
xy: 174, 5 xy: 756, 99
size: 36, 29 size: 36, 29
orig: 36, 29 orig: 36, 29
offset: 0, 0 offset: 0, 0
index: -1 index: -1
goblingirl/eyes-closed goblingirl/eyes-closed
rotate: false rotate: true
xy: 269, 11 xy: 729, 48
size: 37, 21 size: 37, 21
orig: 37, 21 orig: 37, 21
offset: 0, 0 offset: 0, 0
index: -1 index: -1
goblingirl/head goblingirl/head
rotate: false rotate: true
xy: 2, 21 xy: 28, 38
size: 103, 81 size: 103, 81
orig: 103, 81 orig: 103, 81
offset: 0, 0 offset: 0, 0
index: -1 index: -1
goblingirl/left-arm goblingirl/left-arm
rotate: true rotate: true
xy: 978, 56 xy: 724, 10
size: 37, 35 size: 37, 35
orig: 37, 35 orig: 37, 35
offset: 0, 0 offset: 0, 0
index: -1 index: -1
goblingirl/left-foot goblingirl/left-foot
rotate: false rotate: true
xy: 107, 3 xy: 522, 76
size: 65, 31 size: 65, 31
orig: 65, 31 orig: 65, 31
offset: 0, 0 offset: 0, 0
index: -1 index: -1
goblingirl/left-hand goblingirl/left-hand
rotate: false rotate: false
xy: 565, 2 xy: 457, 4
size: 35, 40 size: 35, 40
orig: 35, 40 orig: 35, 40
offset: 0, 0 offset: 0, 0
index: -1 index: -1
goblingirl/left-lower-leg goblingirl/left-lower-leg
rotate: true rotate: false
xy: 785, 93 xy: 454, 71
size: 33, 70 size: 33, 70
orig: 33, 70 orig: 33, 70
offset: 0, 0 offset: 0, 0
index: -1 index: -1
goblingirl/left-shoulder goblingirl/left-shoulder
rotate: true rotate: false
xy: 690, 27 xy: 695, 1
size: 28, 46 size: 28, 46
orig: 28, 46 orig: 28, 46
offset: 0, 0 offset: 0, 0
index: -1 index: -1
goblingirl/left-upper-leg goblingirl/left-upper-leg
rotate: true rotate: false
xy: 857, 93 xy: 488, 71
size: 33, 70 size: 33, 70
orig: 33, 70 orig: 33, 70
offset: 0, 0 offset: 0, 0
index: -1 index: -1
goblingirl/neck goblingirl/neck
rotate: false rotate: false
xy: 528, 2 xy: 421, 3
size: 35, 41 size: 35, 41
orig: 35, 41 orig: 35, 41
offset: 0, 0 offset: 0, 0
index: -1 index: -1
goblingirl/pelvis goblingirl/pelvis
rotate: false rotate: false
xy: 546, 45 xy: 284, 1
size: 62, 43 size: 62, 43
orig: 62, 43 orig: 62, 43
offset: 0, 0 offset: 0, 0
index: -1 index: -1
goblingirl/right-arm goblingirl/right-arm
rotate: false rotate: false
xy: 452, 48 xy: 756, 48
size: 28, 50 size: 28, 50
orig: 28, 50 orig: 28, 50
offset: 0, 0 offset: 0, 0
index: -1 index: -1
goblingirl/right-foot goblingirl/right-foot
rotate: false rotate: true
xy: 836, 58 xy: 658, 78
size: 63, 33 size: 63, 33
orig: 63, 33 orig: 63, 33
offset: 0, 0 offset: 0, 0
index: -1 index: -1
goblingirl/right-hand goblingirl/right-hand
rotate: true rotate: false
xy: 771, 20 xy: 622, 7
size: 36, 37 size: 36, 37
orig: 36, 37 orig: 36, 37
offset: 0, 0 offset: 0, 0
index: -1 index: -1
goblingirl/right-lower-leg goblingirl/right-lower-leg
rotate: true rotate: true
xy: 560, 90 xy: 144, 1
size: 36, 76 size: 36, 76
orig: 36, 76 orig: 36, 76
offset: 0, 0 offset: 0, 0
index: -1 index: -1
goblingirl/right-shoulder goblingirl/right-shoulder
rotate: false rotate: true
xy: 649, 10 xy: 539, 5
size: 39, 45 size: 39, 45
orig: 39, 45 orig: 39, 45
offset: 0, 0 offset: 0, 0
index: -1 index: -1
goblingirl/right-upper-leg goblingirl/right-upper-leg
rotate: true rotate: false
xy: 706, 57 xy: 589, 78
size: 34, 63 size: 34, 63
orig: 34, 63 orig: 34, 63
offset: 0, 0 offset: 0, 0
index: -1 index: -1
goblingirl/torso goblingirl/torso
rotate: false rotate: false
xy: 310, 2 xy: 246, 45
size: 68, 96 size: 68, 96
orig: 68, 96 orig: 68, 96
offset: 0, 0 offset: 0, 0
index: -1 index: -1
goblingirl/undie-straps goblingirl/undie-straps
rotate: false rotate: true
xy: 212, 13 xy: 712, 86
size: 55, 19 size: 55, 19
orig: 55, 19 orig: 55, 19
offset: 0, 0 offset: 0, 0
index: -1 index: -1
goblingirl/undies goblingirl/undies
rotate: false rotate: true
xy: 810, 27 xy: 760, 11
size: 36, 29 size: 36, 29
orig: 36, 29 orig: 36, 29
offset: 0, 0 offset: 0, 0
index: -1 index: -1
shield shield
rotate: false rotate: false
xy: 380, 26 xy: 349, 69
size: 70, 72 size: 70, 72
orig: 70, 72 orig: 70, 72
offset: 0, 0 offset: 0, 0
index: -1 index: -1
spear spear
rotate: true rotate: true
xy: 2, 104 xy: 315, 45
size: 22, 368 size: 22, 368
orig: 22, 368 orig: 22, 368
offset: 0, 0 offset: 0, 0

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 179f09b47e5402545a1aa69bf5cb2cba
timeCreated: 1467115504
licenseType: Free
TextScriptImporter:
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c3921acb20cbc25418859f1b213d3d3f
timeCreated: 1467115504
licenseType: Free
TextScriptImporter:
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

View File

@ -1,6 +1,6 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 803c2e614a63081439fde6276d110661 guid: 5fb7efec30c79cb46a705e0d04debb04
timeCreated: 1455501336 timeCreated: 1467205225
licenseType: Free licenseType: Free
TextureImporter: TextureImporter:
fileIDToRecycleName: {} fileIDToRecycleName: {}
@ -30,15 +30,15 @@ TextureImporter:
maxTextureSize: 2048 maxTextureSize: 2048
textureSettings: textureSettings:
filterMode: -1 filterMode: -1
aniso: -1 aniso: 16
mipBias: -1 mipBias: -1
wrapMode: -1 wrapMode: 1
nPOTScale: 1 nPOTScale: 0
lightmap: 0 lightmap: 0
rGBM: 0 rGBM: 0
compressionQuality: 50 compressionQuality: 50
allowsAlphaSplitting: 0 allowsAlphaSplitting: 0
spriteMode: 0 spriteMode: 1
spriteExtrude: 1 spriteExtrude: 1
spriteMeshType: 1 spriteMeshType: 1
alignment: 0 alignment: 0

View File

@ -0,0 +1,16 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: a6b194f808b1af6499c93410e504af42, type: 3}
m_Name: goblins_Atlas
m_EditorClassIdentifier:
atlasFile: {fileID: 4900000, guid: 179f09b47e5402545a1aa69bf5cb2cba, type: 3}
materials:
- {fileID: 2100000, guid: 54091ef934c41eb4192f72bfd8e3bcc9, type: 2}

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: bb54bdab69af2bb49b35577b80dcaad9
timeCreated: 1467115504
licenseType: Free
NativeFormatImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,30 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 6
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_Name: goblins_Material
m_Shader: {fileID: 4800000, guid: 1e8a610c9e01c3648bac42585e5fc676, type: 3}
m_ShaderKeywords:
m_LightmapFlags: 5
m_CustomRenderQueue: -1
stringTagMap: {}
m_SavedProperties:
serializedVersion: 2
m_TexEnvs:
data:
first:
name: _MainTex
second:
m_Texture: {fileID: 2800000, guid: 5fb7efec30c79cb46a705e0d04debb04, type: 3}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Floats:
data:
first:
name: _Cutoff
second: 0.1
m_Colors: {}

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 54091ef934c41eb4192f72bfd8e3bcc9
timeCreated: 1467115504
licenseType: Free
NativeFormatImporter:
userData:
assetBundleName:
assetBundleVariant:

Some files were not shown because too many files have changed in this diff Show More