mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2025-12-20 09:16:01 +08:00
IK constraints for spine-csharp.
This commit is contained in:
parent
e81806bb08
commit
e6e7f0bd5d
@ -105,6 +105,8 @@
|
||||
<Compile Include="src\Attachments\RegionAttachment.cs" />
|
||||
<Compile Include="src\Bone.cs" />
|
||||
<Compile Include="src\BoneData.cs" />
|
||||
<Compile Include="src\IkConstraint.cs" />
|
||||
<Compile Include="src\IkConstraintData.cs" />
|
||||
<Compile Include="src\Event.cs" />
|
||||
<Compile Include="src\EventData.cs" />
|
||||
<Compile Include="src\Json.cs" />
|
||||
|
||||
@ -98,6 +98,22 @@ namespace Spine {
|
||||
}
|
||||
}
|
||||
|
||||
/// <param name="target">After the first and before the last entry.</param>
|
||||
internal static int binarySearch (float[] values, float target) {
|
||||
int low = 0;
|
||||
int high = values.Length - 2;
|
||||
if (high == 0) return 1;
|
||||
int current = (int)((uint)high >> 1);
|
||||
while (true) {
|
||||
if (values[(current + 1)] <= target)
|
||||
low = current + 1;
|
||||
else
|
||||
high = current;
|
||||
if (low == high) return (low + 1);
|
||||
current = (int)((uint)(low + high) >> 1);
|
||||
}
|
||||
}
|
||||
|
||||
internal static int linearSearch (float[] values, float target, int step) {
|
||||
for (int i = 0, last = values.Length - step; i <= last; i += step)
|
||||
if (values[i] > target) return i;
|
||||
@ -107,78 +123,51 @@ namespace Spine {
|
||||
|
||||
public interface Timeline {
|
||||
/// <summary>Sets the value(s) for the specified time.</summary>
|
||||
void Apply (Skeleton skeleton, float lastTime, float time, List<Event> firedEvents, float alpha);
|
||||
/// <param name="events">May be null to not collect fired events.</param>
|
||||
void Apply (Skeleton skeleton, float lastTime, float time, List<Event> events, float alpha);
|
||||
}
|
||||
|
||||
/// <summary>Base class for frames that use an interpolation bezier curve.</summary>
|
||||
abstract public class CurveTimeline : Timeline {
|
||||
static protected float LINEAR = 0;
|
||||
static protected float STEPPED = -1;
|
||||
static protected int BEZIER_SEGMENTS = 10;
|
||||
protected const float LINEAR = 0, STEPPED = 1, BEZIER = 2;
|
||||
protected const int BEZIER_SEGMENTS = 10, BEZIER_SIZE = BEZIER_SEGMENTS * 2 - 1;
|
||||
|
||||
private float[] curves; // dfx, dfy, ddfx, ddfy, dddfx, dddfy, ...
|
||||
public int FrameCount { get { return curves.Length / 6 + 1; } }
|
||||
private float[] curves; // type, x, y, ...
|
||||
public int FrameCount { get { return curves.Length / BEZIER_SIZE + 1; } }
|
||||
|
||||
public CurveTimeline (int frameCount) {
|
||||
curves = new float[(frameCount - 1) * 6];
|
||||
curves = new float[(frameCount - 1) * BEZIER_SIZE];
|
||||
}
|
||||
|
||||
abstract public void Apply (Skeleton skeleton, float lastTime, float time, List<Event> firedEvents, float alpha);
|
||||
|
||||
public void SetLinear (int frameIndex) {
|
||||
curves[frameIndex * 6] = LINEAR;
|
||||
curves[frameIndex * BEZIER_SIZE] = LINEAR;
|
||||
}
|
||||
|
||||
public void SetStepped (int frameIndex) {
|
||||
curves[frameIndex * 6] = STEPPED;
|
||||
curves[frameIndex * BEZIER_SIZE] = STEPPED;
|
||||
}
|
||||
|
||||
/// <summary>Sets the control handle positions for an interpolation bezier curve used to transition from this keyframe to the next.
|
||||
/// cx1 and cx2 are from 0 to 1, representing the percent of time between the two keyframes. cy1 and cy2 are the percent of
|
||||
/// the difference between the keyframe's values.</summary>
|
||||
public void SetCurve (int frameIndex, float cx1, float cy1, float cx2, float cy2) {
|
||||
float subdiv_step = 1f / BEZIER_SEGMENTS;
|
||||
float subdiv_step2 = subdiv_step * subdiv_step;
|
||||
float subdiv_step3 = subdiv_step2 * subdiv_step;
|
||||
float pre1 = 3 * subdiv_step;
|
||||
float pre2 = 3 * subdiv_step2;
|
||||
float pre4 = 6 * subdiv_step2;
|
||||
float pre5 = 6 * subdiv_step3;
|
||||
float tmp1x = -cx1 * 2 + cx2;
|
||||
float tmp1y = -cy1 * 2 + cy2;
|
||||
float tmp2x = (cx1 - cx2) * 3 + 1;
|
||||
float tmp2y = (cy1 - cy2) * 3 + 1;
|
||||
int i = frameIndex * 6;
|
||||
float[] curves = this.curves;
|
||||
curves[i] = cx1 * pre1 + tmp1x * pre2 + tmp2x * subdiv_step3;
|
||||
curves[i + 1] = cy1 * pre1 + tmp1y * pre2 + tmp2y * subdiv_step3;
|
||||
curves[i + 2] = tmp1x * pre4 + tmp2x * pre5;
|
||||
curves[i + 3] = tmp1y * pre4 + tmp2y * pre5;
|
||||
curves[i + 4] = tmp2x * pre5;
|
||||
curves[i + 5] = tmp2y * pre5;
|
||||
}
|
||||
float subdiv1 = 1f / BEZIER_SEGMENTS, subdiv2 = subdiv1 * subdiv1, subdiv3 = subdiv2 * subdiv1;
|
||||
float pre1 = 3 * subdiv1, pre2 = 3 * subdiv2, pre4 = 6 * subdiv2, pre5 = 6 * subdiv3;
|
||||
float tmp1x = -cx1 * 2 + cx2, tmp1y = -cy1 * 2 + cy2, tmp2x = (cx1 - cx2) * 3 + 1, tmp2y = (cy1 - cy2) * 3 + 1;
|
||||
float dfx = cx1 * pre1 + tmp1x * pre2 + tmp2x * subdiv3, dfy = cy1 * pre1 + tmp1y * pre2 + tmp2y * subdiv3;
|
||||
float ddfx = tmp1x * pre4 + tmp2x * pre5, ddfy = tmp1y * pre4 + tmp2y * pre5;
|
||||
float dddfx = tmp2x * pre5, dddfy = tmp2y * pre5;
|
||||
|
||||
public float GetCurvePercent (int frameIndex, float percent) {
|
||||
int curveIndex = frameIndex * 6;
|
||||
int i = frameIndex * BEZIER_SIZE;
|
||||
float[] curves = this.curves;
|
||||
float dfx = curves[curveIndex];
|
||||
if (dfx == LINEAR) return percent;
|
||||
if (dfx == STEPPED) return 0;
|
||||
float dfy = curves[curveIndex + 1];
|
||||
float ddfx = curves[curveIndex + 2];
|
||||
float ddfy = curves[curveIndex + 3];
|
||||
float dddfx = curves[curveIndex + 4];
|
||||
float dddfy = curves[curveIndex + 5];
|
||||
curves[i++] = BEZIER;
|
||||
|
||||
float x = dfx, y = dfy;
|
||||
int i = BEZIER_SEGMENTS - 2;
|
||||
while (true) {
|
||||
if (x >= percent) {
|
||||
float lastX = x - dfx;
|
||||
float lastY = y - dfy;
|
||||
return lastY + (y - lastY) * (percent - lastX) / (x - lastX);
|
||||
}
|
||||
if (i == 0) break;
|
||||
i--;
|
||||
for (int n = i + BEZIER_SIZE - 1; i < n; i += 2) {
|
||||
curves[i] = x;
|
||||
curves[i + 1] = y;
|
||||
dfx += ddfx;
|
||||
dfy += ddfy;
|
||||
ddfx += dddfx;
|
||||
@ -186,13 +175,38 @@ namespace Spine {
|
||||
x += dfx;
|
||||
y += dfy;
|
||||
}
|
||||
}
|
||||
|
||||
public float GetCurvePercent (int frameIndex, float percent) {
|
||||
float[] curves = this.curves;
|
||||
int i = frameIndex * BEZIER_SIZE;
|
||||
float type = curves[i];
|
||||
if (type == LINEAR) return percent;
|
||||
if (type == STEPPED) return 0;
|
||||
i++;
|
||||
float x = 0;
|
||||
for (int start = i, n = i + BEZIER_SIZE - 1; i < n; i += 2) {
|
||||
x = curves[i];
|
||||
if (x >= percent) {
|
||||
float prevX, prevY;
|
||||
if (i == start) {
|
||||
prevX = 0;
|
||||
prevY = 0;
|
||||
} else {
|
||||
prevX = curves[i - 2];
|
||||
prevY = curves[i - 1];
|
||||
}
|
||||
return prevY + (curves[i + 1] - prevY) * (percent - prevX) / (x - prevX);
|
||||
}
|
||||
}
|
||||
float y = curves[i - 1];
|
||||
return y + (1 - y) * (percent - x) / (1 - x); // Last point is 1,1.
|
||||
}
|
||||
}
|
||||
|
||||
public class RotateTimeline : CurveTimeline {
|
||||
static protected int LAST_FRAME_TIME = -2;
|
||||
static protected int FRAME_VALUE = 1;
|
||||
protected const int LAST_FRAME_TIME = -2;
|
||||
protected const int FRAME_VALUE = 1;
|
||||
|
||||
internal int boneIndex;
|
||||
internal float[] frames;
|
||||
@ -235,7 +249,7 @@ namespace Spine {
|
||||
float lastFrameValue = frames[frameIndex - 1];
|
||||
float frameTime = frames[frameIndex];
|
||||
float percent = 1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime);
|
||||
percent = GetCurvePercent(frameIndex / 2 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
|
||||
percent = GetCurvePercent((frameIndex >> 1) - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
|
||||
|
||||
amount = frames[frameIndex + FRAME_VALUE] - lastFrameValue;
|
||||
while (amount > 180)
|
||||
@ -252,9 +266,9 @@ namespace Spine {
|
||||
}
|
||||
|
||||
public class TranslateTimeline : CurveTimeline {
|
||||
static protected int LAST_FRAME_TIME = -3;
|
||||
static protected int FRAME_X = 1;
|
||||
static protected int FRAME_Y = 2;
|
||||
protected const int LAST_FRAME_TIME = -3;
|
||||
protected const int FRAME_X = 1;
|
||||
protected const int FRAME_Y = 2;
|
||||
|
||||
internal int boneIndex;
|
||||
internal float[] frames;
|
||||
@ -330,11 +344,11 @@ namespace Spine {
|
||||
}
|
||||
|
||||
public class ColorTimeline : CurveTimeline {
|
||||
static protected int LAST_FRAME_TIME = -5;
|
||||
static protected int FRAME_R = 1;
|
||||
static protected int FRAME_G = 2;
|
||||
static protected int FRAME_B = 3;
|
||||
static protected int FRAME_A = 4;
|
||||
protected const int LAST_FRAME_TIME = -5;
|
||||
protected const int FRAME_R = 1;
|
||||
protected const int FRAME_G = 2;
|
||||
protected const int FRAME_B = 3;
|
||||
protected const int FRAME_A = 4;
|
||||
|
||||
internal int slotIndex;
|
||||
internal float[] frames;
|
||||
@ -423,13 +437,14 @@ namespace Spine {
|
||||
|
||||
public void Apply (Skeleton skeleton, float lastTime, float time, List<Event> firedEvents, float alpha) {
|
||||
float[] frames = this.frames;
|
||||
if (time < frames[0]) return; // Time is before first frame.
|
||||
if (time < frames[0]) {
|
||||
if (lastTime > time) Apply(skeleton, lastTime, int.MaxValue, null, 0);
|
||||
return;
|
||||
} else if (lastTime > time) //
|
||||
lastTime = -1;
|
||||
|
||||
int frameIndex;
|
||||
if (time >= frames[frames.Length - 1]) // Time is after last frame.
|
||||
frameIndex = frames.Length - 1;
|
||||
else
|
||||
frameIndex = Animation.binarySearch(frames, time, 1) - 1;
|
||||
int frameIndex = time >= frames[frames.Length - 1] ? frames.Length - 1 : Animation.binarySearch(frames, time) - 1;
|
||||
if (frames[frameIndex] <= lastTime) return;
|
||||
|
||||
String attachmentName = attachmentNames[frameIndex];
|
||||
skeleton.slots[slotIndex].Attachment =
|
||||
@ -473,7 +488,7 @@ namespace Spine {
|
||||
if (lastTime < frames[0])
|
||||
frameIndex = 0;
|
||||
else {
|
||||
frameIndex = Animation.binarySearch(frames, lastTime, 1);
|
||||
frameIndex = Animation.binarySearch(frames, lastTime);
|
||||
float frame = frames[frameIndex];
|
||||
while (frameIndex > 0) { // Fire multiple events with the same frame.
|
||||
if (frames[frameIndex - 1] != frame) break;
|
||||
@ -513,7 +528,7 @@ namespace Spine {
|
||||
if (time >= frames[frames.Length - 1]) // Time is after last frame.
|
||||
frameIndex = frames.Length - 1;
|
||||
else
|
||||
frameIndex = Animation.binarySearch(frames, time, 1) - 1;
|
||||
frameIndex = Animation.binarySearch(frames, time) - 1;
|
||||
|
||||
List<Slot> drawOrder = skeleton.drawOrder;
|
||||
List<Slot> slots = skeleton.slots;
|
||||
@ -583,7 +598,7 @@ namespace Spine {
|
||||
}
|
||||
|
||||
// Interpolate between the previous frame and the current frame.
|
||||
int frameIndex = Animation.binarySearch(frames, time, 1);
|
||||
int frameIndex = Animation.binarySearch(frames, time);
|
||||
float frameTime = frames[frameIndex];
|
||||
float percent = 1 - (time - frameTime) / (frames[frameIndex - 1] - frameTime);
|
||||
percent = GetCurvePercent(frameIndex - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
|
||||
@ -604,4 +619,57 @@ namespace Spine {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class IkConstraintTimeline : CurveTimeline {
|
||||
private const int PREV_FRAME_TIME = -3;
|
||||
private const int FRAME_MIX = 1;
|
||||
private const int FRAME_BEND_DIRECTION = 2;
|
||||
|
||||
internal int ikConstraintIndex;
|
||||
internal float[] frames;
|
||||
|
||||
public int IkConstraintIndex { get { return ikConstraintIndex; } set { ikConstraintIndex = value; } }
|
||||
public float[] Frames { get { return frames; } set { frames = value; } } // time, mix, bendDirection, ...
|
||||
|
||||
public IkConstraintTimeline (int frameCount)
|
||||
: base(frameCount) {
|
||||
frames = new float[frameCount * 3];
|
||||
}
|
||||
|
||||
public float[] getFrames () {
|
||||
return frames;
|
||||
}
|
||||
|
||||
/** Sets the time, mix and bend direction of the specified keyframe. */
|
||||
public void setFrame (int frameIndex, float time, float mix, int bendDirection) {
|
||||
frameIndex *= 3;
|
||||
frames[frameIndex] = time;
|
||||
frames[frameIndex + 1] = mix;
|
||||
frames[frameIndex + 2] = bendDirection;
|
||||
}
|
||||
|
||||
override public void Apply (Skeleton skeleton, float lastTime, float time, List<Event> firedEvents, float alpha) {
|
||||
float[] frames = this.frames;
|
||||
if (time < frames[0]) return; // Time is before first frame.
|
||||
|
||||
IkConstraint ikConstraint = skeleton.ikConstraints[ikConstraintIndex];
|
||||
|
||||
if (time >= frames[frames.Length - 3]) { // Time is after last frame.
|
||||
ikConstraint.mix += (frames[frames.Length - 2] - ikConstraint.mix) * alpha;
|
||||
ikConstraint.bendDirection = (int)frames[frames.Length - 1];
|
||||
return;
|
||||
}
|
||||
|
||||
// Interpolate between the previous frame and the current frame.
|
||||
int frameIndex = Animation.binarySearch(frames, time, 3);
|
||||
float prevFrameMix = frames[frameIndex - 2];
|
||||
float frameTime = frames[frameIndex];
|
||||
float percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime);
|
||||
percent = GetCurvePercent(frameIndex / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
|
||||
|
||||
float mix = prevFrameMix + (frames[frameIndex + FRAME_MIX] - prevFrameMix) * percent;
|
||||
ikConstraint.mix += (mix - ikConstraint.mix) * alpha;
|
||||
ikConstraint.bendDirection = (int)frames[frameIndex + FRAME_BEND_DIRECTION];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,15 +36,19 @@ namespace Spine {
|
||||
|
||||
internal BoneData data;
|
||||
internal Bone parent;
|
||||
internal float x, y, rotation, scaleX, scaleY;
|
||||
internal float x, y, rotation, rotationIK, scaleX, scaleY;
|
||||
internal float m00, m01, m10, m11;
|
||||
internal float worldX, worldY, worldRotation, worldScaleX, worldScaleY;
|
||||
internal bool flipX, flipY;
|
||||
|
||||
public BoneData Data { get { return data; } }
|
||||
public Bone Parent { get { return parent; } }
|
||||
public float X { get { return x; } set { x = value; } }
|
||||
public float Y { get { return y; } set { y = value; } }
|
||||
/// <summary>The forward kinetics rotation.</summary>
|
||||
public float Rotation { get { return rotation; } set { rotation = value; } }
|
||||
/// <summary>The inverse kinetics rotation, as calculated by any IK constraints.</summary>
|
||||
public float RotationIK { get { return rotationIK; } set { rotationIK = value; } }
|
||||
public float ScaleX { get { return scaleX; } set { scaleX = value; } }
|
||||
public float ScaleY { get { return scaleY; } set { scaleY = value; } }
|
||||
|
||||
@ -67,8 +71,9 @@ namespace Spine {
|
||||
}
|
||||
|
||||
/// <summary>Computes the world SRT using the parent bone and the local SRT.</summary>
|
||||
public void UpdateWorldTransform (bool flipX, bool flipY) {
|
||||
public void UpdateWorldTransform () {
|
||||
Bone parent = this.parent;
|
||||
float x = this.x, y = this.y;
|
||||
if (parent != null) {
|
||||
worldX = x * parent.m00 + y * parent.m01 + parent.worldX;
|
||||
worldY = x * parent.m10 + y * parent.m11 + parent.worldY;
|
||||
@ -79,28 +84,30 @@ namespace Spine {
|
||||
worldScaleX = scaleX;
|
||||
worldScaleY = scaleY;
|
||||
}
|
||||
worldRotation = data.inheritRotation ? parent.worldRotation + rotation : rotation;
|
||||
worldRotation = data.inheritRotation ? parent.worldRotation + rotationIK : rotationIK;
|
||||
} else {
|
||||
worldX = flipX ? -x : x;
|
||||
worldY = flipY != yDown ? -y : y;
|
||||
worldScaleX = scaleX;
|
||||
worldScaleY = scaleY;
|
||||
worldRotation = rotation;
|
||||
worldRotation = rotationIK;
|
||||
}
|
||||
float radians = worldRotation * (float)Math.PI / 180;
|
||||
float cos = (float)Math.Cos(radians);
|
||||
float sin = (float)Math.Sin(radians);
|
||||
m00 = cos * worldScaleX;
|
||||
m10 = sin * worldScaleX;
|
||||
m01 = -sin * worldScaleY;
|
||||
m11 = cos * worldScaleY;
|
||||
if (flipX) {
|
||||
m00 = -m00;
|
||||
m01 = -m01;
|
||||
m00 = -cos * worldScaleX;
|
||||
m01 = sin * worldScaleY;
|
||||
} else {
|
||||
m00 = cos * worldScaleX;
|
||||
m01 = -sin * worldScaleY;
|
||||
}
|
||||
if (flipY != yDown) {
|
||||
m10 = -m10;
|
||||
m11 = -m11;
|
||||
m10 = -sin * worldScaleX;
|
||||
m11 = -cos * worldScaleY;
|
||||
} else {
|
||||
m10 = sin * worldScaleX;
|
||||
m11 = cos * worldScaleY;
|
||||
}
|
||||
}
|
||||
|
||||
@ -109,10 +116,28 @@ namespace Spine {
|
||||
x = data.x;
|
||||
y = data.y;
|
||||
rotation = data.rotation;
|
||||
rotationIK = rotation;
|
||||
scaleX = data.scaleX;
|
||||
scaleY = data.scaleY;
|
||||
}
|
||||
|
||||
public void worldToLocal (float worldX, float worldY, out float localX, out float localY) {
|
||||
float dx = worldX - this.worldX, dy = worldY - this.worldY;
|
||||
float m00 = this.m00, m10 = this.m10, m01 = this.m01, m11 = this.m11;
|
||||
if (flipX != (flipY != yDown)) {
|
||||
m00 *= -1;
|
||||
m11 *= -1;
|
||||
}
|
||||
float invDet = 1 / (m00 * m11 - m01 * m10);
|
||||
localX = (dx * m00 * invDet - dy * m01 * invDet);
|
||||
localY = (dy * m11 * invDet - dx * m10 * invDet);
|
||||
}
|
||||
|
||||
public void localToWorld (float localX, float localY, out float worldX, out float worldY) {
|
||||
worldX = localX * m00 + localY * m01 + this.worldX;
|
||||
worldY = localX * m10 + localY * m11 + this.worldY;
|
||||
}
|
||||
|
||||
override public String ToString () {
|
||||
return data.name;
|
||||
}
|
||||
|
||||
@ -32,14 +32,16 @@ using System;
|
||||
|
||||
namespace Spine {
|
||||
public class EventData {
|
||||
public String Name { get; private set; }
|
||||
internal String name;
|
||||
|
||||
public String Name { get { return name; } }
|
||||
public int Int { get; set; }
|
||||
public float Float { get; set; }
|
||||
public String String { get; set; }
|
||||
|
||||
public EventData (String name) {
|
||||
if (name == null) throw new ArgumentNullException("name cannot be null.");
|
||||
Name = name;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
override public String ToString () {
|
||||
|
||||
148
spine-csharp/src/IkConstraint.cs
Normal file
148
spine-csharp/src/IkConstraint.cs
Normal file
@ -0,0 +1,148 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes Software License
|
||||
* Version 2.1
|
||||
*
|
||||
* Copyright (c) 2013, Esoteric Software
|
||||
* All rights reserved.
|
||||
*
|
||||
* You are granted a perpetual, non-exclusive, non-sublicensable and
|
||||
* non-transferable license to install, execute and perform the Spine Runtimes
|
||||
* Software (the "Software") solely for internal use. Without the written
|
||||
* permission of Esoteric Software (typically granted by licensing Spine), 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 SOFTARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Spine {
|
||||
public class IkConstraint {
|
||||
private const float radDeg = 180 / (float)Math.PI;
|
||||
|
||||
internal IkConstraintData data;
|
||||
internal List<Bone> bones = new List<Bone>();
|
||||
internal Bone target;
|
||||
internal int bendDirection = 1;
|
||||
internal float mix = 1;
|
||||
|
||||
public IkConstraintData Data { get { return data; } }
|
||||
public List<Bone> Bones { get { return bones; } }
|
||||
public Bone Target { get { return target; } set { target = value; } }
|
||||
public int BendDirection { get { return bendDirection; } set { bendDirection = value; } }
|
||||
public float Mix { get { return mix; } set { mix = value; } }
|
||||
|
||||
public IkConstraint (IkConstraintData data, Skeleton skeleton) {
|
||||
this.data = data;
|
||||
mix = data.mix;
|
||||
bendDirection = data.bendDirection;
|
||||
|
||||
bones = new List<Bone>(data.bones.Count);
|
||||
if (skeleton != null) {
|
||||
foreach (BoneData boneData in data.bones)
|
||||
bones.Add(skeleton.FindBone(boneData.name));
|
||||
target = skeleton.FindBone(data.target.name);
|
||||
}
|
||||
}
|
||||
|
||||
public void apply () {
|
||||
Bone target = this.target;
|
||||
List<Bone> bones = this.bones;
|
||||
switch (bones.Count) {
|
||||
case 1:
|
||||
apply(bones[0], target.worldX, target.worldY, mix);
|
||||
break;
|
||||
case 2:
|
||||
apply(bones[0], bones[1], target.worldX, target.worldY, bendDirection, mix);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
override public String ToString () {
|
||||
return data.name;
|
||||
}
|
||||
|
||||
/// <summary>Adjusts the bone rotation so the tip is as close to the target position as possible. The target is specified
|
||||
/// in the world coordinate system.</summary>
|
||||
static public void apply (Bone bone, float targetX, float targetY, float alpha) {
|
||||
float parentRotation = (!bone.data.inheritRotation || bone.parent == null) ? 0 : bone.parent.worldRotation;
|
||||
float rotation = bone.rotation;
|
||||
float rotationIK = (float)Math.Atan2(targetY - bone.worldY, targetX - bone.worldX) * radDeg - parentRotation;
|
||||
bone.rotationIK = rotation + (rotationIK - rotation) * alpha;
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="child">Any descendant bone of the parent.</param>
|
||||
static public void apply (Bone parent, Bone child, float targetX, float targetY, int bendDirection, float alpha) {
|
||||
float childRotation = child.rotation, parentRotation = parent.rotation;
|
||||
if (alpha == 0) {
|
||||
child.rotationIK = childRotation;
|
||||
parent.rotationIK = parentRotation;
|
||||
return;
|
||||
}
|
||||
float positionX, positionY;
|
||||
Bone parentParent = parent.parent;
|
||||
if (parentParent != null) {
|
||||
parentParent.worldToLocal(targetX, targetY, out positionX, out positionY);
|
||||
targetX = (positionX - parent.x) * parentParent.worldScaleX;
|
||||
targetY = (positionY - parent.y) * parentParent.worldScaleY;
|
||||
} else {
|
||||
targetX -= parent.x;
|
||||
targetY -= parent.y;
|
||||
}
|
||||
if (child.parent == parent) {
|
||||
positionX = child.x;
|
||||
positionY = child.y;
|
||||
} else {
|
||||
child.parent.localToWorld(child.x, child.y, out positionX, out positionY);
|
||||
parent.worldToLocal(positionX, positionY, out positionX, out positionY);
|
||||
}
|
||||
float childX = positionX * parent.worldScaleX, childY = positionY * parent.worldScaleY;
|
||||
float offset = (float)Math.Atan2(childY, childX);
|
||||
float len1 = (float)Math.Sqrt(childX * childX + childY * childY), len2 = child.data.length * child.worldScaleX;
|
||||
// Based on code by Ryan Juckett with permission: Copyright (c) 2008-2009 Ryan Juckett, http://www.ryanjuckett.com/
|
||||
float cosDenom = 2 * len1 * len2;
|
||||
if (cosDenom < 0.0001f) {
|
||||
child.rotationIK = childRotation + ((float)Math.Atan2(targetY, targetX) * radDeg - parentRotation - childRotation)
|
||||
* alpha;
|
||||
return;
|
||||
}
|
||||
float cos = (targetX * targetX + targetY * targetY - len1 * len1 - len2 * len2) / cosDenom;
|
||||
if (cos < -1)
|
||||
cos = -1;
|
||||
else if (cos > 1)
|
||||
cos = 1;
|
||||
float childAngle = (float)Math.Acos(cos) * bendDirection;
|
||||
float adjacent = len1 + len2 * cos, opposite = len2 * (float)Math.Sin(childAngle);
|
||||
float parentAngle = (float)Math.Atan2(targetY * adjacent - targetX * opposite, targetX * adjacent + targetY * opposite);
|
||||
float rotation = (parentAngle - offset) * radDeg - parentRotation;
|
||||
if (rotation > 180)
|
||||
rotation -= 360;
|
||||
else if (rotation < -180) //
|
||||
rotation += 360;
|
||||
parent.rotationIK = parentRotation + rotation * alpha;
|
||||
rotation = (childAngle + offset) * radDeg - childRotation;
|
||||
if (rotation > 180)
|
||||
rotation -= 360;
|
||||
else if (rotation < -180) //
|
||||
rotation += 360;
|
||||
child.rotationIK = childRotation + (rotation + parent.worldRotation - child.parent.worldRotation) * alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
57
spine-csharp/src/IkConstraintData.cs
Normal file
57
spine-csharp/src/IkConstraintData.cs
Normal file
@ -0,0 +1,57 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes Software License
|
||||
* Version 2.1
|
||||
*
|
||||
* Copyright (c) 2013, Esoteric Software
|
||||
* All rights reserved.
|
||||
*
|
||||
* You are granted a perpetual, non-exclusive, non-sublicensable and
|
||||
* non-transferable license to install, execute and perform the Spine Runtimes
|
||||
* Software (the "Software") solely for internal use. Without the written
|
||||
* permission of Esoteric Software (typically granted by licensing Spine), 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 SOFTARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Spine {
|
||||
public class IkConstraintData {
|
||||
internal String name;
|
||||
internal List<BoneData> bones = new List<BoneData>();
|
||||
internal BoneData target;
|
||||
internal int bendDirection = 1;
|
||||
internal float mix = 1;
|
||||
|
||||
public String Name { get { return name; } }
|
||||
public List<BoneData> Bones { get { return bones; } }
|
||||
public BoneData Target { get { return target; } set { target = value; } }
|
||||
public int BendDirection { get { return bendDirection; } set { bendDirection = value; } }
|
||||
public float Mix { get { return mix; } set { mix = value; } }
|
||||
|
||||
public IkConstraintData (String name) {
|
||||
if (name == null) throw new ArgumentNullException("name cannot be null.");
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
override public String ToString () {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -37,6 +37,8 @@ namespace Spine {
|
||||
internal List<Bone> bones;
|
||||
internal List<Slot> slots;
|
||||
internal List<Slot> drawOrder;
|
||||
internal List<IkConstraint> ikConstraints = new List<IkConstraint>();
|
||||
private List<List<Bone>> updateBonesCache = new List<List<Bone>>();
|
||||
internal Skin skin;
|
||||
internal float r = 1, g = 1, b = 1, a = 1;
|
||||
internal float time;
|
||||
@ -47,17 +49,38 @@ namespace Spine {
|
||||
public List<Bone> Bones { get { return bones; } }
|
||||
public List<Slot> Slots { get { return slots; } }
|
||||
public List<Slot> DrawOrder { get { return drawOrder; } }
|
||||
public List<IkConstraint> IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } }
|
||||
public Skin Skin { get { return skin; } set { skin = 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 float Time { get { return time; } set { time = value; } }
|
||||
public bool FlipX { get { return flipX; } set { flipX = value; } }
|
||||
public bool FlipY { get { return flipY; } set { flipY = value; } }
|
||||
public float X { get { return x; } set { x = value; } }
|
||||
public float Y { get { return y; } set { y = value; } }
|
||||
|
||||
public bool FlipX {
|
||||
get { return flipX; }
|
||||
set {
|
||||
if (flipX == value) return;
|
||||
flipX = value;
|
||||
List<Bone> bones = this.bones;
|
||||
for (int i = 0, n = bones.Count; i < n; i++)
|
||||
bones[i].flipX = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool FlipY {
|
||||
get { return flipY; }
|
||||
set {
|
||||
if (flipY == value) return;
|
||||
flipY = value;
|
||||
List<Bone> bones = this.bones;
|
||||
for (int i = 0, n = bones.Count; i < n; i++)
|
||||
bones[i].flipY = value;
|
||||
}
|
||||
}
|
||||
|
||||
public Bone RootBone {
|
||||
get {
|
||||
return bones.Count == 0 ? null : bones[0];
|
||||
@ -82,15 +105,73 @@ namespace Spine {
|
||||
slots.Add(slot);
|
||||
drawOrder.Add(slot);
|
||||
}
|
||||
|
||||
ikConstraints = new List<IkConstraint>(data.ikConstraints.Count);
|
||||
foreach (IkConstraintData ikConstraintData in data.ikConstraints)
|
||||
ikConstraints.Add(new IkConstraint(ikConstraintData, this));
|
||||
|
||||
UpdateCache();
|
||||
}
|
||||
|
||||
/// <summary>Updates the world transform for each bone.</summary>
|
||||
/// <summary>Caches information about bones and IK constraints. Must be called if bones or IK constraints are added or
|
||||
/// removed.</summary>
|
||||
public void UpdateCache () {
|
||||
List<List<Bone>> updateBonesCache = this.updateBonesCache;
|
||||
List<IkConstraint> ikConstraints = this.ikConstraints;
|
||||
int ikConstraintsCount = ikConstraints.Count;
|
||||
|
||||
int arrayCount = ikConstraintsCount + 1;
|
||||
if (updateBonesCache.Count > arrayCount) updateBonesCache.RemoveRange(arrayCount, updateBonesCache.Count - arrayCount);
|
||||
for (int i = 0, n = updateBonesCache.Count; i < n; i++)
|
||||
updateBonesCache[i].Clear();
|
||||
while (updateBonesCache.Count < arrayCount)
|
||||
updateBonesCache.Add(new List<Bone>());
|
||||
|
||||
List<Bone> nonIkBones = updateBonesCache[0];
|
||||
|
||||
for (int i = 0, n = bones.Count; i < n; i++) {
|
||||
Bone bone = bones[i];
|
||||
Bone current = bone;
|
||||
do {
|
||||
for (int ii = 0; ii < ikConstraintsCount; ii++) {
|
||||
IkConstraint ikConstraint = ikConstraints[ii];
|
||||
Bone parent = ikConstraint.bones[0];
|
||||
Bone child = ikConstraint.bones[ikConstraint.bones.Count - 1];
|
||||
while (true) {
|
||||
if (current == child) {
|
||||
updateBonesCache[ii].Add(bone);
|
||||
updateBonesCache[ii + 1].Add(bone);
|
||||
goto outer;
|
||||
}
|
||||
if (child == parent) break;
|
||||
child = child.parent;
|
||||
}
|
||||
}
|
||||
current = current.parent;
|
||||
} while (current != null);
|
||||
nonIkBones.Add(bone);
|
||||
outer: {}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Updates the world transform for each bone and applies IK constraints.</summary>
|
||||
public void UpdateWorldTransform () {
|
||||
bool flipX = this.flipX;
|
||||
bool flipY = this.flipY;
|
||||
List<Bone> bones = this.bones;
|
||||
for (int i = 0, n = bones.Count; i < n; i++)
|
||||
bones[i].UpdateWorldTransform(flipX, flipY);
|
||||
for (int ii = 0, nn = bones.Count; ii < nn; ii++) {
|
||||
Bone bone = bones[ii];
|
||||
bone.rotationIK = bone.rotation;
|
||||
}
|
||||
List<List<Bone>> updateBonesCache = this.updateBonesCache;
|
||||
List<IkConstraint> ikConstraints = this.ikConstraints;
|
||||
int i = 0, last = updateBonesCache.Count - 1;
|
||||
while (true) {
|
||||
List<Bone> updateBones = updateBonesCache[i];
|
||||
for (int ii = 0, nn = updateBones.Count; ii < nn; ii++)
|
||||
updateBones[ii].UpdateWorldTransform();
|
||||
if (i == last) break;
|
||||
ikConstraints[i].apply();
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Sets the bones and slots to their setup pose values.</summary>
|
||||
@ -103,6 +184,13 @@ namespace Spine {
|
||||
List<Bone> bones = this.bones;
|
||||
for (int i = 0, n = bones.Count; i < n; i++)
|
||||
bones[i].SetToSetupPose();
|
||||
|
||||
List<IkConstraint> ikConstraints = this.ikConstraints;
|
||||
for (int i = 0, n = ikConstraints.Count; i < n; i++) {
|
||||
IkConstraint ikConstraint = ikConstraints[i];
|
||||
ikConstraint.bendDirection = ikConstraint.data.bendDirection;
|
||||
ikConstraint.mix = ikConstraint.data.mix;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetSlotsToSetupPose () {
|
||||
@ -218,6 +306,17 @@ namespace Spine {
|
||||
throw new Exception("Slot not found: " + slotName);
|
||||
}
|
||||
|
||||
/** @return May be null. */
|
||||
public IkConstraint FindIkConstraint (String ikConstraintName) {
|
||||
if (ikConstraintName == null) throw new ArgumentNullException("ikConstraintName cannot be null.");
|
||||
List<IkConstraint> ikConstraints = this.ikConstraints;
|
||||
for (int i = 0, n = ikConstraints.Count; i < n; i++) {
|
||||
IkConstraint ikConstraint = ikConstraints[i];
|
||||
if (ikConstraint.data.name == ikConstraintName) return ikConstraint;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Update (float delta) {
|
||||
time += delta;
|
||||
}
|
||||
|
||||
@ -40,6 +40,9 @@ namespace Spine {
|
||||
internal Skin defaultSkin;
|
||||
internal List<EventData> events = new List<EventData>();
|
||||
internal List<Animation> animations = new List<Animation>();
|
||||
internal List<IkConstraintData> ikConstraints = new List<IkConstraintData>();
|
||||
internal float width, height;
|
||||
internal String version, hash;
|
||||
|
||||
public String Name { get { return name; } set { name = value; } }
|
||||
public List<BoneData> Bones { get { return bones; } } // Ordered parents first.
|
||||
@ -49,15 +52,15 @@ namespace Spine {
|
||||
public Skin DefaultSkin { get { return defaultSkin; } set { defaultSkin = value; } }
|
||||
public List<EventData> Events { get { return events; } set { events = value; } }
|
||||
public List<Animation> Animations { get { return animations; } set { animations = value; } }
|
||||
public List<IkConstraintData> IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } }
|
||||
public float Width { get { return width; } set { width = value; } }
|
||||
public float Height { get { return height; } set { height = value; } }
|
||||
/// <summary>The Spine version used to export this data.</summary>
|
||||
public String Version { get { return version; } set { version = value; } }
|
||||
public String Hash { get { return hash; } set { hash = value; } }
|
||||
|
||||
// --- Bones.
|
||||
|
||||
public void AddBone (BoneData bone) {
|
||||
if (bone == null) throw new ArgumentNullException("bone cannot be null.");
|
||||
bones.Add(bone);
|
||||
}
|
||||
|
||||
|
||||
/// <returns>May be null.</returns>
|
||||
public BoneData FindBone (String boneName) {
|
||||
if (boneName == null) throw new ArgumentNullException("boneName cannot be null.");
|
||||
@ -80,11 +83,6 @@ namespace Spine {
|
||||
|
||||
// --- Slots.
|
||||
|
||||
public void AddSlot (SlotData slot) {
|
||||
if (slot == null) throw new ArgumentNullException("slot cannot be null.");
|
||||
slots.Add(slot);
|
||||
}
|
||||
|
||||
/// <returns>May be null.</returns>
|
||||
public SlotData FindSlot (String slotName) {
|
||||
if (slotName == null) throw new ArgumentNullException("slotName cannot be null.");
|
||||
@ -106,12 +104,7 @@ namespace Spine {
|
||||
}
|
||||
|
||||
// --- Skins.
|
||||
|
||||
public void AddSkin (Skin skin) {
|
||||
if (skin == null) throw new ArgumentNullException("skin cannot be null.");
|
||||
skins.Add(skin);
|
||||
}
|
||||
|
||||
|
||||
/// <returns>May be null.</returns>
|
||||
public Skin FindSkin (String skinName) {
|
||||
if (skinName == null) throw new ArgumentNullException("skinName cannot be null.");
|
||||
@ -122,33 +115,36 @@ namespace Spine {
|
||||
|
||||
// --- Events.
|
||||
|
||||
public void AddEvent (EventData eventData) {
|
||||
if (eventData == null) throw new ArgumentNullException("eventData cannot be null.");
|
||||
events.Add(eventData);
|
||||
}
|
||||
|
||||
/// <returns>May be null.</returns>
|
||||
public EventData FindEvent (String eventDataName) {
|
||||
if (eventDataName == null) throw new ArgumentNullException("eventDataName cannot be null.");
|
||||
foreach (EventData eventData in events)
|
||||
if (eventData.Name == eventDataName) return eventData;
|
||||
if (eventData.name == eventDataName) return eventData;
|
||||
return null;
|
||||
}
|
||||
|
||||
// --- Animations.
|
||||
|
||||
public void AddAnimation (Animation animation) {
|
||||
if (animation == null) throw new ArgumentNullException("animation cannot be null.");
|
||||
animations.Add(animation);
|
||||
}
|
||||
|
||||
|
||||
/// <returns>May be null.</returns>
|
||||
public Animation FindAnimation (String animationName) {
|
||||
if (animationName == null) throw new ArgumentNullException("animationName cannot be null.");
|
||||
List<Animation> animations = this.animations;
|
||||
for (int i = 0, n = animations.Count; i < n; i++) {
|
||||
Animation animation = animations[i];
|
||||
if (animation.Name == animationName) return animation;
|
||||
if (animation.name == animationName) return animation;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// --- IK
|
||||
|
||||
/// <returns>May be null.</returns>
|
||||
public IkConstraintData FindIkConstraint (String ikConstraintName) {
|
||||
if (ikConstraintName == null) throw new ArgumentNullException("ikConstraintName cannot be null.");
|
||||
List<IkConstraintData> ikConstraints = this.ikConstraints;
|
||||
for (int i = 0, n = ikConstraints.Count; i < n; i++) {
|
||||
IkConstraintData ikConstraint = ikConstraints[i];
|
||||
if (ikConstraint.name == ikConstraintName) return ikConstraint;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -90,6 +90,15 @@ namespace Spine {
|
||||
var root = Json.Deserialize(reader) as Dictionary<String, Object>;
|
||||
if (root == null) throw new Exception("Invalid JSON.");
|
||||
|
||||
// Skeleton.
|
||||
var skeletonMap = (Dictionary<String, Object>)root["skeleton"];
|
||||
if (skeletonMap != null) {
|
||||
skeletonData.version = (String)skeletonMap["spine"];
|
||||
skeletonData.hash = (String)skeletonMap["hash"];
|
||||
skeletonData.width = GetFloat(skeletonMap, "width", 0);
|
||||
skeletonData.height = GetFloat(skeletonMap, "width", 1);
|
||||
}
|
||||
|
||||
// Bones.
|
||||
foreach (Dictionary<String, Object> boneMap in (List<Object>)root["bones"]) {
|
||||
BoneData parent = null;
|
||||
@ -107,7 +116,27 @@ namespace Spine {
|
||||
boneData.scaleY = GetFloat(boneMap, "scaleY", 1);
|
||||
boneData.inheritScale = GetBoolean(boneMap, "inheritScale", true);
|
||||
boneData.inheritRotation = GetBoolean(boneMap, "inheritRotation", true);
|
||||
skeletonData.AddBone(boneData);
|
||||
skeletonData.bones.Add(boneData);
|
||||
}
|
||||
|
||||
// IK constraints.
|
||||
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);
|
||||
}
|
||||
|
||||
// Slots.
|
||||
@ -134,7 +163,7 @@ namespace Spine {
|
||||
if (slotMap.ContainsKey("additive"))
|
||||
slotData.additiveBlending = (bool)slotMap["additive"];
|
||||
|
||||
skeletonData.AddSlot(slotData);
|
||||
skeletonData.slots.Add(slotData);
|
||||
}
|
||||
}
|
||||
|
||||
@ -149,7 +178,7 @@ namespace Spine {
|
||||
if (attachment != null) skin.AddAttachment(slotIndex, attachmentEntry.Key, attachment);
|
||||
}
|
||||
}
|
||||
skeletonData.AddSkin(skin);
|
||||
skeletonData.skins.Add(skin);
|
||||
if (skin.name == "default")
|
||||
skeletonData.defaultSkin = skin;
|
||||
}
|
||||
@ -163,7 +192,7 @@ namespace Spine {
|
||||
eventData.Int = GetInt(entryMap, "int", 0);
|
||||
eventData.Float = GetFloat(entryMap, "float", 0);
|
||||
eventData.String = GetString(entryMap, "string", null);
|
||||
skeletonData.AddEvent(eventData);
|
||||
skeletonData.events.Add(eventData);
|
||||
}
|
||||
}
|
||||
|
||||
@ -443,6 +472,26 @@ namespace Spine {
|
||||
}
|
||||
}
|
||||
|
||||
if (map.ContainsKey("ik")) {
|
||||
foreach (KeyValuePair<String, Object> ikMap in (Dictionary<String, Object>)map["ik"]) {
|
||||
IkConstraintData ikConstraint = skeletonData.FindIkConstraint(ikMap.Key);
|
||||
var values = (List<Object>)ikMap.Value;
|
||||
var timeline = new IkConstraintTimeline(values.Count);
|
||||
timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(ikConstraint);
|
||||
int frameIndex = 0;
|
||||
foreach (Dictionary<String, Object> valueMap in values) {
|
||||
float time = (float)valueMap["time"];
|
||||
float mix = valueMap.ContainsKey("mix") ? (float)valueMap["mix"] : 1;
|
||||
bool bendPositive = valueMap.ContainsKey("bendPositive") ? (bool)valueMap["bendPositive"] : true;
|
||||
timeline.setFrame(frameIndex, time, mix, bendPositive ? 1 : -1);
|
||||
ReadCurve(timeline, frameIndex, valueMap);
|
||||
frameIndex++;
|
||||
}
|
||||
timelines.Add(timeline);
|
||||
duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 3 - 3]);
|
||||
}
|
||||
}
|
||||
|
||||
if (map.ContainsKey("ffd")) {
|
||||
foreach (KeyValuePair<String, Object> ffdMap in (Dictionary<String, Object>)map["ffd"]) {
|
||||
Skin skin = skeletonData.FindSkin(ffdMap.Key);
|
||||
@ -553,7 +602,7 @@ namespace Spine {
|
||||
}
|
||||
|
||||
timelines.TrimExcess();
|
||||
skeletonData.AddAnimation(new Animation(name, timelines, duration));
|
||||
skeletonData.animations.Add(new Animation(name, timelines, duration));
|
||||
}
|
||||
|
||||
private void ReadCurve (CurveTimeline timeline, int frameIndex, Dictionary<String, Object> valueMap) {
|
||||
|
||||
104
spine-xna/example/data/raptor.atlas
Normal file
104
spine-xna/example/data/raptor.atlas
Normal file
@ -0,0 +1,104 @@
|
||||
|
||||
raptor.png
|
||||
size: 1014,480
|
||||
format: RGBA8888
|
||||
filter: Linear,Linear
|
||||
repeat: none
|
||||
stirrup
|
||||
rotate: false
|
||||
xy: 657, 57
|
||||
size: 50, 45
|
||||
orig: 50, 45
|
||||
offset: 0, 0
|
||||
index: -1
|
||||
velo_arm_back
|
||||
rotate: false
|
||||
xy: 881, 204
|
||||
size: 82, 86
|
||||
orig: 82, 86
|
||||
offset: 0, 0
|
||||
index: -1
|
||||
velo_body
|
||||
rotate: false
|
||||
xy: 2, 195
|
||||
size: 610, 285
|
||||
orig: 610, 285
|
||||
offset: 0, 0
|
||||
index: -1
|
||||
velo_front_arm
|
||||
rotate: false
|
||||
xy: 798, 188
|
||||
size: 81, 102
|
||||
orig: 81, 102
|
||||
offset: 0, 0
|
||||
index: -1
|
||||
velo_front_leg
|
||||
rotate: true
|
||||
xy: 2, 2
|
||||
size: 191, 257
|
||||
orig: 191, 257
|
||||
offset: 0, 0
|
||||
index: -1
|
||||
velo_hindleg_back
|
||||
rotate: false
|
||||
xy: 614, 265
|
||||
size: 169, 215
|
||||
orig: 169, 215
|
||||
offset: 0, 0
|
||||
index: -1
|
||||
velo_horn
|
||||
rotate: false
|
||||
xy: 614, 183
|
||||
size: 182, 80
|
||||
orig: 182, 80
|
||||
offset: 0, 0
|
||||
index: -1
|
||||
velo_horn_back
|
||||
rotate: false
|
||||
xy: 581, 104
|
||||
size: 176, 77
|
||||
orig: 176, 77
|
||||
offset: 0, 0
|
||||
index: -1
|
||||
velo_jaw
|
||||
rotate: false
|
||||
xy: 426, 50
|
||||
size: 153, 143
|
||||
orig: 153, 143
|
||||
offset: 0, 0
|
||||
index: -1
|
||||
velo_saddle_noshadow
|
||||
rotate: false
|
||||
xy: 261, 5
|
||||
size: 163, 188
|
||||
orig: 163, 188
|
||||
offset: 0, 0
|
||||
index: -1
|
||||
velo_saddle_strap_front
|
||||
rotate: false
|
||||
xy: 950, 385
|
||||
size: 57, 95
|
||||
orig: 57, 95
|
||||
offset: 0, 0
|
||||
index: -1
|
||||
velo_saddle_strap_rear
|
||||
rotate: true
|
||||
xy: 581, 48
|
||||
size: 54, 74
|
||||
orig: 54, 74
|
||||
offset: 0, 0
|
||||
index: -1
|
||||
velo_saddle_w_shadow
|
||||
rotate: false
|
||||
xy: 785, 292
|
||||
size: 163, 188
|
||||
orig: 163, 188
|
||||
offset: 0, 0
|
||||
index: -1
|
||||
velo_tongue
|
||||
rotate: true
|
||||
xy: 950, 297
|
||||
size: 86, 64
|
||||
orig: 86, 64
|
||||
offset: 0, 0
|
||||
index: -1
|
||||
1
spine-xna/example/data/raptor.json
Normal file
1
spine-xna/example/data/raptor.json
Normal file
File diff suppressed because one or more lines are too long
BIN
spine-xna/example/data/raptor.png
Normal file
BIN
spine-xna/example/data/raptor.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 380 KiB |
@ -117,6 +117,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="data\goblins-ffd.png" />
|
||||
<Content Include="data\raptor.png" />
|
||||
<Content Include="data\spineboy.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
@ -163,6 +164,8 @@
|
||||
<ItemGroup>
|
||||
<None Include="data\goblins-ffd.atlas" />
|
||||
<None Include="data\goblins-ffd.json" />
|
||||
<None Include="data\raptor.atlas" />
|
||||
<None Include="data\raptor.json" />
|
||||
<None Include="data\spineboy.atlas">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
|
||||
@ -50,9 +50,9 @@ namespace Spine {
|
||||
SkeletonBounds bounds = new SkeletonBounds();
|
||||
|
||||
#if WINDOWS_STOREAPP
|
||||
private string assetsFolder = @"Assets\";
|
||||
private string assetsFolder = @"Assets\";
|
||||
#else
|
||||
private string assetsFolder = "data/";
|
||||
private string assetsFolder = "data/";
|
||||
#endif
|
||||
|
||||
public Example () {
|
||||
@ -75,11 +75,13 @@ namespace Spine {
|
||||
skeletonRenderer.PremultipliedAlpha = true;
|
||||
|
||||
// String name = "spineboy";
|
||||
String name = "goblins-ffd";
|
||||
// String name = "goblins-ffd";
|
||||
String name = "raptor";
|
||||
|
||||
Atlas atlas = new Atlas(assetsFolder + name + ".atlas", new XnaTextureLoader(GraphicsDevice));
|
||||
Atlas atlas = new Atlas(assetsFolder + name + ".atlas", new XnaTextureLoader(GraphicsDevice));
|
||||
SkeletonJson json = new SkeletonJson(atlas);
|
||||
if (name == "spineboy") json.Scale = 0.6f;
|
||||
if (name == "raptor") json.Scale = 0.5f;
|
||||
skeleton = new Skeleton(json.ReadSkeletonData(assetsFolder + name + ".json"));
|
||||
if (name == "goblins-ffd") skeleton.SetSkin("goblin");
|
||||
|
||||
@ -138,13 +140,15 @@ namespace Spine {
|
||||
|
||||
bounds.Update(skeleton, true);
|
||||
MouseState mouse = Mouse.GetState();
|
||||
headSlot.G = 1;
|
||||
headSlot.B = 1;
|
||||
if (bounds.AabbContainsPoint(mouse.X, mouse.Y)) {
|
||||
BoundingBoxAttachment hit = bounds.ContainsPoint(mouse.X, mouse.Y);
|
||||
if (hit != null) {
|
||||
headSlot.G = 0;
|
||||
headSlot.B = 0;
|
||||
if (headSlot != null) {
|
||||
headSlot.G = 1;
|
||||
headSlot.B = 1;
|
||||
if (bounds.AabbContainsPoint(mouse.X, mouse.Y)) {
|
||||
BoundingBoxAttachment hit = bounds.ContainsPoint(mouse.X, mouse.Y);
|
||||
if (hit != null) {
|
||||
headSlot.G = 0;
|
||||
headSlot.B = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -152,25 +156,25 @@ namespace Spine {
|
||||
}
|
||||
|
||||
public void Start (AnimationState state, int trackIndex) {
|
||||
#if !WINDOWS_STOREAPP
|
||||
#if !WINDOWS_STOREAPP
|
||||
Console.WriteLine(trackIndex + " " + state.GetCurrent(trackIndex) + ": start");
|
||||
#endif
|
||||
}
|
||||
|
||||
public void End (AnimationState state, int trackIndex) {
|
||||
#if !WINDOWS_STOREAPP
|
||||
#if !WINDOWS_STOREAPP
|
||||
Console.WriteLine(trackIndex + " " + state.GetCurrent(trackIndex) + ": end");
|
||||
#endif
|
||||
}
|
||||
|
||||
public void Complete (AnimationState state, int trackIndex, int loopCount) {
|
||||
#if !WINDOWS_STOREAPP
|
||||
#if !WINDOWS_STOREAPP
|
||||
Console.WriteLine(trackIndex + " " + state.GetCurrent(trackIndex) + ": complete " + loopCount);
|
||||
#endif
|
||||
}
|
||||
|
||||
public void Event (AnimationState state, int trackIndex, Event e) {
|
||||
#if !WINDOWS_STOREAPP
|
||||
#if !WINDOWS_STOREAPP
|
||||
Console.WriteLine(trackIndex + " " + state.GetCurrent(trackIndex) + ": event " + e);
|
||||
#endif
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user