diff --git a/CHANGELOG.md b/CHANGELOG.md
index e5db595e46..8e694618fd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -34,6 +34,22 @@
* `SkeletonGraphic` now supports automatic scaling based on its `RectTransform` bounds. Automatic scaling can be enabled by setting the added `Layout Scale Mode` Inspector property to either `Width Controls Height`, `Height Controls Width`, `FitInParent` or `EnvelopeParent`. It is set to `None` by default to keep previous behaviour and avoid breaking existing projects. To modify the reference layout bounds, hit the additional `Edit Layout Bounds` toggle button to switch into edit mode, adjust the bounds or hit `Match RectTransform with Mesh`, and hit the button again when done adjusting. The skeleton will now be scaled accordingly to fit the reference layout bounds to the object's `RectTransform`.
* Added previously missing unlit URP 2D shader variant, available under `Universal Render Pipeline/2D/Spine/Skeleton`.
* Added support for light cookies at `Universal Render Pipeline/Spine/Sprite` shader.
+ * Timeline extension package: An additional Spine preferences parameter `Timeline` - `Default Mix Duration` has been added, setting newly added `SpineAnimationStateClip` clips accordingly, defaults to false. This Spine preferences parameter can be enabled to default to the previous behaviour before this update.
+ * Tint Black: Added support for [Tint Black](http://en.esotericsoftware.com/spine-slots#Tint-black) functionality at all Spine URP shaders (2D and 3D shaders) and at all standard pipeline `Spine/Sprite` shaders. This feature can be enabled via the `Tint Black` material parameter in the Inspector. Note: The URP Sprite shaders provided in the Spine URP Shaders extension UPM package require the latest version of the spine-unity runtime (package version 4.1.12, 2023-05-31 or newer) to display the added material parameters in the Inspector GUI.
+ * Added `SkeletonGraphic.MeshScale` property to allow access to calculated mesh scale. `MeshScale` is based on (1) Canvas pixels per unit, and (2) `RectTransform` bounds when using `Layout Scale Mode` other than `None` at `SkeletonGraphic` which scales the skeleton mesh to fit the parent `RectTransform` bounds accordingly.
+ * Added `updateSeparatorPartScale` property to `SkeletonGraphic` to let render separator parts follow the scale (lossy scale) of the `SkeletonGraphic` GameObject. Defaults to `false` to maintain existing behaviour.
+ * Added experimental `EditorSkeletonPlayer` component to allow Editor playback of the initial animation set at `SkeletonAnimation` or `SkeletonGraphic` components. Add this component to your skeleton GameObject to enable the in-editor animation preview. Allows configurations for continuous playback when selected, deselected, and alternative single-frame preview by setting `Fixed Track Time` to any value other than 0. Limitations: At skeletons with variable material count the Inspector preview may be too unresponsive. It is then recommended to disable the `EditorSkeletonPlayer` component (at the top of the Inspector) to make it responsive again, then you can disable `Play When Selected` and re-enable the component to preview playback only when deselected.
+ * Added example component `RenderCombinedMesh` to render a combined mesh of multiple meshes or submeshes. This is required by `OutlineOnly` shaders to render a combined outline when using `SkeletonRenderSeparator` or multiple atlas pages which would normally lead to outlines around individual parts. To add a combined outline to your SkeletenRenderer:
+ 1) Add a child GameObject and move it a bit back (e.g. position Z = 0.01).
+ 2) Add a `RenderCombinedMesh` component, provided in the `Spine Examples/Scripts/Sample Components` directory.
+ 3) Copy the original material, add *_Outline* to its name and set the shader to your outline-only shader like `Universal Render Pipeline/Spine/Outline/Skeleton-OutlineOnly` or `Spine/Outline/OutlineOnly-ZWrite`.
+ 4) Assign this *_Outline* material at the new child GameObject's `MeshRenderer` component.
+ If you are using `SkeletonRenderSeparator` and need to enable and disable the `SkeletonRenderSeparator` component at runtime, you can increase the `RenderCombinedMesh` `Reference Renderers` array by one and assign the `SkeletonRenderer` itself at the last entry after the parts renderers. Disabled `MeshRenderer` components will be skipped when combining the final mesh, so the combined mesh is automatically filled from the desired active renderers.
+ * Timeline extension package: Added static `EditorEvent` callback to allow editor scripts to react to animation events outside of play-mode. Register to the events via `Spine.Unity.Playables.SpineAnimationStateMixerBehaviour.EditorEvent += YourCallback;`.
+ * URP Shaders: Added `Depth Write` property to shaders `Universal Render Pipeline/Spine/Skeleton` and `Universal Render Pipeline/Spine/Skeleton Lit`. Defaults to false to maintain existing behaviour.
+ * Added `Animation Update` mode (called `UpdateTiming` in code) `In Late Update` for `SkeletonAnimation`, `SkeletonMecanim` and `SkeletonGraphic`. This allows you to update the `SkeletonMecanim` skeleton in the same frame that the Mecanim Animator updated its state, which happens between `Update` and `LateUpdate`.
+ * URP Shaders: Added URP "Blend Mode" shader variants for both URP 3D and URP 2D renderers. They are listed under shader name "Universal Render Pipeline/Spine/Blend Modes/" and "Universal Render Pipeline/2D/Spine/Blend Modes/" respectively.
+ * URP Shaders: Added support for [Tint Black](http://en.esotericsoftware.com/spine-slots#Tint-black) functionality at "Blend Modes" Spine URP shaders (2D and 3D shaders).
* **Breaking changes**
* Changed `SpineShaderWithOutlineGUI` outline related methods from `private` to `protected virtual` to allow for custom shader GUI subclasses to switch to different outline shaders.
diff --git a/spine-csharp/src/Animation.cs b/spine-csharp/src/Animation.cs
index 24bc1ad82c..7642be9f5c 100644
--- a/spine-csharp/src/Animation.cs
+++ b/spine-csharp/src/Animation.cs
@@ -175,13 +175,15 @@ public enum MixDirection {
Out
}
- internal enum Property {
+ public enum Property {
Rotate = 0, X, Y, ScaleX, ScaleY, ShearX, ShearY, //
RGB, Alpha, RGB2, //
Attachment, Deform, //
Event, DrawOrder, //
IkConstraint, TransformConstraint, //
PathConstraintPosition, PathConstraintSpacing, PathConstraintMix, //
+ PhysicsConstraintInertia, PhysicsConstraintStrength, PhysicsConstraintDamping, PhysicsConstraintMass, //
+ PhysicsConstraintWind, PhysicsConstraintGravity, PhysicsConstraintMix, PhysicsConstraintReset, //
Sequence
}
@@ -214,7 +216,7 @@ public virtual int FrameEntries {
}
/// The number of frames for this timeline.
- public int FrameCount {
+ public virtual int FrameCount {
get { return frames.Length / FrameEntries; }
}
@@ -433,6 +435,97 @@ public float GetCurveValue (float time) {
}
return GetBezierValue(time, i, VALUE, curveType - BEZIER);
}
+
+ public float GetRelativeValue (float time, float alpha, MixBlend blend, float current, float setup) {
+ if (time < frames[0]) {
+ switch (blend) {
+ case MixBlend.Setup:
+ return setup;
+ case MixBlend.First:
+ return current + (setup - current) * alpha;
+ }
+ return current;
+ }
+ float value = GetCurveValue(time);
+ switch (blend) {
+ case MixBlend.Setup:
+ return setup + value * alpha;
+ case MixBlend.First:
+ case MixBlend.Replace:
+ value += setup - current;
+ break;
+ }
+ return current + value * alpha;
+ }
+
+ public float GetAbsoluteValue (float time, float alpha, MixBlend blend, float current, float setup) {
+ if (time < frames[0]) {
+ switch (blend) {
+ case MixBlend.Setup:
+ return setup;
+ case MixBlend.First:
+ return current + (setup - current) * alpha;
+ }
+ return current;
+ }
+ float value = GetCurveValue(time);
+ if (blend == MixBlend.Setup) return setup + (value - setup) * alpha;
+ return current + (value - current) * alpha;
+ }
+
+ public float GetAbsoluteValue (float time, float alpha, MixBlend blend, float current, float setup, float value) {
+ if (time < frames[0]) {
+ switch (blend) {
+ case MixBlend.Setup:
+ return setup;
+ case MixBlend.First:
+ return current + (setup - current) * alpha;
+ }
+ return current;
+ }
+ if (blend == MixBlend.Setup) return setup + (value - setup) * alpha;
+ return current + (value - current) * alpha;
+ }
+
+ public float GetScaleValue (float time, float alpha, MixBlend blend, MixDirection direction, float current, float setup) {
+ float[] frames = this.frames;
+ if (time < frames[0]) {
+ switch (blend) {
+ case MixBlend.Setup:
+ return setup;
+ case MixBlend.First:
+ return current + (setup - current) * alpha;
+ }
+ return current;
+ }
+ float value = GetCurveValue(time) * setup;
+ if (alpha == 1) {
+ if (blend == MixBlend.Add) return current + value - setup;
+ return value;
+ }
+ // Mixing out uses sign of setup or current pose, else use sign of key.
+ if (direction == MixDirection.Out) {
+ switch (blend) {
+ case MixBlend.Setup:
+ return setup + (Math.Abs(value) * Math.Sign(setup) - setup) * alpha;
+ case MixBlend.First:
+ case MixBlend.Replace:
+ return current + (Math.Abs(value) * Math.Sign(current) - current) * alpha;
+ }
+ } else {
+ float s;
+ switch (blend) {
+ case MixBlend.Setup:
+ s = Math.Abs(setup) * Math.Sign(value);
+ return s + (value - s) * alpha;
+ case MixBlend.First:
+ case MixBlend.Replace:
+ s = Math.Abs(current) * Math.Sign(value);
+ return s + (value - s) * alpha;
+ }
+ }
+ return current + (value - setup) * alpha;
+ }
}
/// The base class for a which sets two properties.
@@ -479,33 +572,7 @@ public int BoneIndex {
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend,
MixDirection direction) {
Bone bone = skeleton.bones.Items[boneIndex];
- if (!bone.active) return;
-
- if (time < frames[0]) { // Time is before first frame.
- switch (blend) {
- case MixBlend.Setup:
- bone.rotation = bone.data.rotation;
- return;
- case MixBlend.First:
- bone.rotation += (bone.data.rotation - bone.rotation) * alpha;
- return;
- }
- return;
- }
-
- float r = GetCurveValue(time);
- switch (blend) {
- case MixBlend.Setup:
- bone.rotation = bone.data.rotation + r * alpha;
- break;
- case MixBlend.First:
- case MixBlend.Replace:
- r += bone.data.rotation - bone.rotation;
- goto case MixBlend.Add; // Fall through.
- case MixBlend.Add:
- bone.rotation += r * alpha;
- break;
- }
+ if (bone.active) bone.rotation = GetRelativeValue(time, alpha, blend, bone.rotation, bone.data.rotation);
}
}
@@ -532,7 +599,7 @@ override public void Apply (Skeleton skeleton, float lastTime, float time, Expos
if (!bone.active) return;
float[] frames = this.frames;
- if (time < frames[0]) { // Time is before first frame.
+ if (time < frames[0]) {
switch (blend) {
case MixBlend.Setup:
bone.x = bone.data.x;
@@ -607,34 +674,7 @@ public int BoneIndex {
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend,
MixDirection direction) {
Bone bone = skeleton.bones.Items[boneIndex];
- if (!bone.active) return;
-
- float[] frames = this.frames;
- if (time < frames[0]) { // Time is before first frame.
- switch (blend) {
- case MixBlend.Setup:
- bone.x = bone.data.x;
- return;
- case MixBlend.First:
- bone.x += (bone.data.x - bone.x) * alpha;
- return;
- }
- return;
- }
-
- float x = GetCurveValue(time);
- switch (blend) {
- case MixBlend.Setup:
- bone.x = bone.data.x + x * alpha;
- break;
- case MixBlend.First:
- case MixBlend.Replace:
- bone.x += (bone.data.x + x - bone.x) * alpha;
- break;
- case MixBlend.Add:
- bone.x += x * alpha;
- break;
- }
+ if (bone.active) bone.x = GetRelativeValue(time, alpha, blend, bone.x, bone.data.x);
}
}
@@ -656,34 +696,7 @@ public int BoneIndex {
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend,
MixDirection direction) {
Bone bone = skeleton.bones.Items[boneIndex];
- if (!bone.active) return;
-
- float[] frames = this.frames;
- if (time < frames[0]) { // Time is before first frame.
- switch (blend) {
- case MixBlend.Setup:
- bone.y = bone.data.y;
- return;
- case MixBlend.First:
- bone.y += (bone.data.y - bone.y) * alpha;
- return;
- }
- return;
- }
-
- float y = GetCurveValue(time);
- switch (blend) {
- case MixBlend.Setup:
- bone.y = bone.data.y + y * alpha;
- break;
- case MixBlend.First:
- case MixBlend.Replace:
- bone.y += (bone.data.y + y - bone.y) * alpha;
- break;
- case MixBlend.Add:
- bone.y += y * alpha;
- break;
- }
+ if (bone.active) bone.y = GetRelativeValue(time, alpha, blend, bone.y, bone.data.y);
}
}
@@ -710,7 +723,7 @@ override public void Apply (Skeleton skeleton, float lastTime, float time, Expos
if (!bone.active) return;
float[] frames = this.frames;
- if (time < frames[0]) { // Time is before first frame.
+ if (time < frames[0]) {
switch (blend) {
case MixBlend.Setup:
bone.scaleX = bone.data.scaleX;
@@ -821,62 +834,7 @@ public int BoneIndex {
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend,
MixDirection direction) {
Bone bone = skeleton.bones.Items[boneIndex];
- if (!bone.active) return;
-
- float[] frames = this.frames;
- if (time < frames[0]) { // Time is before first frame.
- switch (blend) {
- case MixBlend.Setup:
- bone.scaleX = bone.data.scaleX;
- return;
- case MixBlend.First:
- bone.scaleX += (bone.data.scaleX - bone.scaleX) * alpha;
- return;
- }
- return;
- }
-
- float x = GetCurveValue(time) * bone.data.scaleX;
- if (alpha == 1) {
- if (blend == MixBlend.Add)
- bone.scaleX += x - bone.data.scaleX;
- else
- bone.scaleX = x;
- } else {
- // Mixing out uses sign of setup or current pose, else use sign of key.
- float bx;
- if (direction == MixDirection.Out) {
- switch (blend) {
- case MixBlend.Setup:
- bx = bone.data.scaleX;
- bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha;
- break;
- case MixBlend.First:
- case MixBlend.Replace:
- bx = bone.scaleX;
- bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha;
- break;
- case MixBlend.Add:
- bone.scaleX += (x - bone.data.scaleX) * alpha;
- break;
- }
- } else {
- switch (blend) {
- case MixBlend.Setup:
- bx = Math.Abs(bone.data.scaleX) * Math.Sign(x);
- bone.scaleX = bx + (x - bx) * alpha;
- break;
- case MixBlend.First:
- case MixBlend.Replace:
- bx = Math.Abs(bone.scaleX) * Math.Sign(x);
- bone.scaleX = bx + (x - bx) * alpha;
- break;
- case MixBlend.Add:
- bone.scaleX += (x - bone.data.scaleX) * alpha;
- break;
- }
- }
- }
+ if (bone.active) bone.scaleX = GetScaleValue(time, alpha, blend, direction, bone.scaleX, bone.data.scaleX);
}
}
@@ -898,62 +856,7 @@ public int BoneIndex {
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend,
MixDirection direction) {
Bone bone = skeleton.bones.Items[boneIndex];
- if (!bone.active) return;
-
- float[] frames = this.frames;
- if (time < frames[0]) { // Time is before first frame.
- switch (blend) {
- case MixBlend.Setup:
- bone.scaleY = bone.data.scaleY;
- return;
- case MixBlend.First:
- bone.scaleY += (bone.data.scaleY - bone.scaleY) * alpha;
- return;
- }
- return;
- }
-
- float y = GetCurveValue(time) * bone.data.scaleY;
- if (alpha == 1) {
- if (blend == MixBlend.Add)
- bone.scaleY += y - bone.data.scaleY;
- else
- bone.scaleY = y;
- } else {
- // Mixing out uses sign of setup or current pose, else use sign of key.
- float by;
- if (direction == MixDirection.Out) {
- switch (blend) {
- case MixBlend.Setup:
- by = bone.data.scaleY;
- bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha;
- break;
- case MixBlend.First:
- case MixBlend.Replace:
- by = bone.scaleY;
- bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha;
- break;
- case MixBlend.Add:
- bone.scaleY += (y - bone.data.scaleY) * alpha;
- break;
- }
- } else {
- switch (blend) {
- case MixBlend.Setup:
- by = Math.Abs(bone.data.scaleY) * Math.Sign(y);
- bone.scaleY = by + (y - by) * alpha;
- break;
- case MixBlend.First:
- case MixBlend.Replace:
- by = Math.Abs(bone.scaleY) * Math.Sign(y);
- bone.scaleY = by + (y - by) * alpha;
- break;
- case MixBlend.Add:
- bone.scaleY += (y - bone.data.scaleY) * alpha;
- break;
- }
- }
- }
+ if (bone.active) bone.scaleY = GetScaleValue(time, alpha, blend, direction, bone.scaleX, bone.data.scaleY);
}
}
@@ -1051,34 +954,7 @@ public int BoneIndex {
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend,
MixDirection direction) {
Bone bone = skeleton.bones.Items[boneIndex];
- if (!bone.active) return;
-
- float[] frames = this.frames;
- if (time < frames[0]) { // Time is before first frame.
- switch (blend) {
- case MixBlend.Setup:
- bone.shearX = bone.data.shearX;
- return;
- case MixBlend.First:
- bone.shearX += (bone.data.shearX - bone.shearX) * alpha;
- return;
- }
- return;
- }
-
- float x = GetCurveValue(time);
- switch (blend) {
- case MixBlend.Setup:
- bone.shearX = bone.data.shearX + x * alpha;
- break;
- case MixBlend.First:
- case MixBlend.Replace:
- bone.shearX += (bone.data.shearX + x - bone.shearX) * alpha;
- break;
- case MixBlend.Add:
- bone.shearX += x * alpha;
- break;
- }
+ if (bone.active) bone.shearX = GetRelativeValue(time, alpha, blend, bone.shearX, bone.data.shearX);
}
}
@@ -1100,34 +976,7 @@ public int BoneIndex {
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend,
MixDirection direction) {
Bone bone = skeleton.bones.Items[boneIndex];
- if (!bone.active) return;
-
- float[] frames = this.frames;
- if (time < frames[0]) { // Time is before first frame.
- switch (blend) {
- case MixBlend.Setup:
- bone.shearY = bone.data.shearY;
- return;
- case MixBlend.First:
- bone.shearY += (bone.data.shearY - bone.shearY) * alpha;
- return;
- }
- return;
- }
-
- float y = GetCurveValue(time);
- switch (blend) {
- case MixBlend.Setup:
- bone.shearY = bone.data.shearY + y * alpha;
- break;
- case MixBlend.First:
- case MixBlend.Replace:
- bone.shearY += (bone.data.shearY + y - bone.shearY) * alpha;
- break;
- case MixBlend.Add:
- bone.shearY += y * alpha;
- break;
- }
+ if (bone.active) bone.shearY = GetRelativeValue(time, alpha, blend, bone.shearX, bone.data.shearY);
}
}
@@ -1172,7 +1021,7 @@ override public void Apply (Skeleton skeleton, float lastTime, float time, Expos
if (!slot.bone.active) return;
float[] frames = this.frames;
- if (time < frames[0]) { // Time is before first frame.
+ if (time < frames[0]) {
SlotData setup = slot.data;
switch (blend) {
case MixBlend.Setup:
@@ -1288,7 +1137,7 @@ override public void Apply (Skeleton skeleton, float lastTime, float time, Expos
if (!slot.bone.active) return;
float[] frames = this.frames;
- if (time < frames[0]) { // Time is before first frame.
+ if (time < frames[0]) {
SlotData setup = slot.data;
switch (blend) {
case MixBlend.Setup:
@@ -1376,7 +1225,7 @@ override public void Apply (Skeleton skeleton, float lastTime, float time, Expos
if (!slot.bone.active) return;
float[] frames = this.frames;
- if (time < frames[0]) { // Time is before first frame.
+ if (time < frames[0]) {
SlotData setup = slot.data;
switch (blend) {
case MixBlend.Setup:
@@ -1452,7 +1301,7 @@ override public void Apply (Skeleton skeleton, float lastTime, float time, Expos
if (!slot.bone.active) return;
float[] frames = this.frames;
- if (time < frames[0]) { // Time is before first frame.
+ if (time < frames[0]) {
SlotData setup = slot.data;
switch (blend) {
case MixBlend.Setup:
@@ -1611,7 +1460,7 @@ override public void Apply (Skeleton skeleton, float lastTime, float time, Expos
if (!slot.bone.active) return;
float[] frames = this.frames;
- if (time < frames[0]) { // Time is before first frame.
+ if (time < frames[0]) {
SlotData setup = slot.data;
switch (blend) {
case MixBlend.Setup:
@@ -1755,7 +1604,7 @@ public override void Apply (Skeleton skeleton, float lastTime, float time, Expos
}
float[] frames = this.frames;
- if (time < frames[0]) { // Time is before first frame.
+ if (time < frames[0]) {
if (blend == MixBlend.Setup || blend == MixBlend.First) SetAttachment(skeleton, slot, slot.data.attachmentName);
return;
}
@@ -1881,7 +1730,7 @@ override public void Apply (Skeleton skeleton, float lastTime, float time, Expos
float[] deform;
float[] frames = this.frames;
- if (time < frames[0]) { // Time is before first frame.
+ if (time < frames[0]) {
switch (blend) {
case MixBlend.Setup:
deformArray.Clear();
@@ -2086,12 +1935,12 @@ public override void Apply (Skeleton skeleton, float lastTime, float time, Expos
float[] frames = this.frames;
int frameCount = frames.Length;
- if (lastTime > time) { // Fire events after last time for looped animations.
+ if (lastTime > time) { // Apply after lastTime for looped animations.
Apply(skeleton, lastTime, int.MaxValue, firedEvents, alpha, blend, direction);
lastTime = -1f;
} else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame.
return;
- if (time < frames[0]) return; // Time is before first frame.
+ if (time < frames[0]) return;
int i;
if (lastTime < frames[0])
@@ -2147,7 +1996,7 @@ public override void Apply (Skeleton skeleton, float lastTime, float time, Expos
}
float[] frames = this.frames;
- if (time < frames[0]) { // Time is before first frame.
+ if (time < frames[0]) {
if (blend == MixBlend.Setup || blend == MixBlend.First) Array.Copy(skeleton.slots.Items, 0, skeleton.drawOrder.Items, 0, skeleton.slots.Count);
return;
}
@@ -2170,11 +2019,11 @@ public class IkConstraintTimeline : CurveTimeline {
public const int ENTRIES = 6;
private const int MIX = 1, SOFTNESS = 2, BEND_DIRECTION = 3, COMPRESS = 4, STRETCH = 5;
- readonly int ikConstraintIndex;
+ readonly int constraintIndex;
public IkConstraintTimeline (int frameCount, int bezierCount, int ikConstraintIndex)
: base(frameCount, bezierCount, (int)Property.IkConstraint + "|" + ikConstraintIndex) {
- this.ikConstraintIndex = ikConstraintIndex;
+ this.constraintIndex = ikConstraintIndex;
}
public override int FrameEntries {
@@ -2183,11 +2032,11 @@ public override int FrameEntries {
}
}
- /// The index of the IK constraint slot in that will be changed when this timeline is
+ /// The index of the IK constraint in that will be changed when this timeline is
/// applied.
public int IkConstraintIndex {
get {
- return ikConstraintIndex;
+ return constraintIndex;
}
}
@@ -2208,11 +2057,11 @@ public void SetFrame (int frame, float time, float mix, float softness, int bend
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend,
MixDirection direction) {
- IkConstraint constraint = skeleton.ikConstraints.Items[ikConstraintIndex];
+ IkConstraint constraint = skeleton.ikConstraints.Items[constraintIndex];
if (!constraint.active) return;
float[] frames = this.frames;
- if (time < frames[0]) { // Time is before first frame.
+ if (time < frames[0]) {
switch (blend) {
case MixBlend.Setup:
constraint.mix = constraint.data.mix;
@@ -2282,11 +2131,11 @@ public class TransformConstraintTimeline : CurveTimeline {
public const int ENTRIES = 7;
private const int ROTATE = 1, X = 2, Y = 3, SCALEX = 4, SCALEY = 5, SHEARY = 6;
- readonly int transformConstraintIndex;
+ readonly int constraintIndex;
public TransformConstraintTimeline (int frameCount, int bezierCount, int transformConstraintIndex)
: base(frameCount, bezierCount, (int)Property.TransformConstraint + "|" + transformConstraintIndex) {
- this.transformConstraintIndex = transformConstraintIndex;
+ constraintIndex = transformConstraintIndex;
}
public override int FrameEntries {
@@ -2295,11 +2144,11 @@ public override int FrameEntries {
}
}
- /// The index of the transform constraint slot in that will be changed when this
+ /// The index of the transform constraint in that will be changed when this
/// timeline is applied.
public int TransformConstraintIndex {
get {
- return transformConstraintIndex;
+ return constraintIndex;
}
}
@@ -2320,11 +2169,11 @@ public void SetFrame (int frame, float time, float mixRotate, float mixX, float
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend,
MixDirection direction) {
- TransformConstraint constraint = skeleton.transformConstraints.Items[transformConstraintIndex];
+ TransformConstraint constraint = skeleton.transformConstraints.Items[constraintIndex];
if (!constraint.active) return;
float[] frames = this.frames;
- if (time < frames[0]) { // Time is before first frame.
+ if (time < frames[0]) {
TransformConstraintData data = constraint.data;
switch (blend) {
case MixBlend.Setup:
@@ -2412,101 +2261,66 @@ public void GetCurveValue (out float rotate, out float x, out float y,
/// Changes a path constraint's .
public class PathConstraintPositionTimeline : CurveTimeline1 {
- readonly int pathConstraintIndex;
+ readonly int constraintIndex;
public PathConstraintPositionTimeline (int frameCount, int bezierCount, int pathConstraintIndex)
: base(frameCount, bezierCount, (int)Property.PathConstraintPosition + "|" + pathConstraintIndex) {
- this.pathConstraintIndex = pathConstraintIndex;
+ this.constraintIndex = pathConstraintIndex;
}
/// The index of the path constraint slot in that will be changed when this timeline
/// is applied.
public int PathConstraintIndex {
get {
- return pathConstraintIndex;
+ return constraintIndex;
}
}
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend,
MixDirection direction) {
- PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex];
- if (!constraint.active) return;
-
- if (time < frames[0]) { // Time is before first frame.
- switch (blend) {
- case MixBlend.Setup:
- constraint.position = constraint.data.position;
- return;
- case MixBlend.First:
- constraint.position += (constraint.data.position - constraint.position) * alpha;
- return;
- }
- return;
- }
-
- float position = GetCurveValue(time);
- if (blend == MixBlend.Setup)
- constraint.position = constraint.data.position + (position - constraint.data.position) * alpha;
- else
- constraint.position += (position - constraint.position) * alpha;
+ PathConstraint constraint = skeleton.pathConstraints.Items[constraintIndex];
+ if (constraint.active)
+ constraint.position = GetAbsoluteValue(time, alpha, blend, constraint.position, constraint.data.position);
}
}
/// Changes a path constraint's .
public class PathConstraintSpacingTimeline : CurveTimeline1 {
- readonly int pathConstraintIndex;
+ readonly int constraintIndex;
public PathConstraintSpacingTimeline (int frameCount, int bezierCount, int pathConstraintIndex)
: base(frameCount, bezierCount, (int)Property.PathConstraintSpacing + "|" + pathConstraintIndex) {
- this.pathConstraintIndex = pathConstraintIndex;
+ constraintIndex = pathConstraintIndex;
}
- /// The index of the path constraint slot in that will be changed when this timeline
+ /// The index of the path constraint in that will be changed when this timeline
/// is applied.
public int PathConstraintIndex {
get {
- return pathConstraintIndex;
+ return constraintIndex;
}
}
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, MixBlend blend,
MixDirection direction) {
- PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex];
- if (!constraint.active) return;
-
- float[] frames = this.frames;
- if (time < frames[0]) { // Time is before first frame.
- switch (blend) {
- case MixBlend.Setup:
- constraint.spacing = constraint.data.spacing;
- return;
- case MixBlend.First:
- constraint.spacing += (constraint.data.spacing - constraint.spacing) * alpha;
- return;
- }
- return;
- }
-
- float spacing = GetCurveValue(time);
- if (blend == MixBlend.Setup)
- constraint.spacing = constraint.data.spacing + (spacing - constraint.data.spacing) * alpha;
- else
- constraint.spacing += (spacing - constraint.spacing) * alpha;
+ PathConstraint constraint = skeleton.pathConstraints.Items[constraintIndex];
+ if (constraint.active)
+ constraint.spacing = GetAbsoluteValue(time, alpha, blend, constraint.spacing, constraint.data.spacing);
}
}
- /// Changes a transform constraint's , , and
+ /// Changes a path constraint's , , and
/// .
public class PathConstraintMixTimeline : CurveTimeline {
public const int ENTRIES = 4;
private const int ROTATE = 1, X = 2, Y = 3;
- readonly int pathConstraintIndex;
+ readonly int constraintIndex;
public PathConstraintMixTimeline (int frameCount, int bezierCount, int pathConstraintIndex)
: base(frameCount, bezierCount, (int)Property.PathConstraintMix + "|" + pathConstraintIndex) {
- this.pathConstraintIndex = pathConstraintIndex;
+ constraintIndex = pathConstraintIndex;
}
public override int FrameEntries {
@@ -2517,7 +2331,7 @@ public override int FrameEntries {
/// is applied.
public int PathConstraintIndex {
get {
- return pathConstraintIndex;
+ return constraintIndex;
}
}
@@ -2534,11 +2348,11 @@ public void SetFrame (int frame, float time, float mixRotate, float mixX, float
override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend,
MixDirection direction) {
- PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex];
+ PathConstraint constraint = skeleton.pathConstraints.Items[constraintIndex];
if (!constraint.active) return;
float[] frames = this.frames;
- if (time < frames[0]) { // Time is before first frame.
+ if (time < frames[0]) {
switch (blend) {
case MixBlend.Setup:
constraint.mixRotate = constraint.data.mixRotate;
@@ -2592,6 +2406,277 @@ override public void Apply (Skeleton skeleton, float lastTime, float time, Expos
}
}
+ /// The base class for most timelines.
+ public abstract class PhysicsConstraintTimeline : CurveTimeline1 {
+ readonly int constraintIndex;
+
+ /// -1 for all physics constraints in the skeleton.
+ public PhysicsConstraintTimeline (int frameCount, int bezierCount, int physicsConstraintIndex, Property property)
+ : base(frameCount, bezierCount, (int)property + "|" + physicsConstraintIndex) {
+
+ constraintIndex = physicsConstraintIndex;
+ }
+
+ /// The index of the physics constraint in that will be changed when this timeline
+ /// is applied, or -1 if all physics constraints in the skeleton will be changed.
+ public int PhysicsConstraintIndex {
+ get {
+ return constraintIndex;
+ }
+ }
+
+ override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend,
+ MixDirection direction) {
+ PhysicsConstraint constraint;
+ if (constraintIndex == -1) {
+ float value = time >= frames[0] ? GetCurveValue(time) : 0;
+
+ PhysicsConstraint[] constraints = skeleton.physicsConstraints.Items;
+ for (int i = 0, n = skeleton.physicsConstraints.Count; i < n; i++) {
+ constraint = (PhysicsConstraint)constraints[i];
+ if (constraint.active && Global(constraint.data))
+ Set(constraint, GetAbsoluteValue(time, alpha, blend, Get(constraint), Setup(constraint), value));
+ }
+ } else {
+ constraint = skeleton.physicsConstraints.Items[constraintIndex];
+ if (constraint.active) Set(constraint, GetAbsoluteValue(time, alpha, blend, Get(constraint), Setup(constraint)));
+ }
+ }
+
+ abstract protected float Setup (PhysicsConstraint constraint);
+
+ abstract protected float Get (PhysicsConstraint constraint);
+
+ abstract protected void Set (PhysicsConstraint constraint, float value);
+
+ abstract protected bool Global (PhysicsConstraintData constraint);
+ }
+
+ /// Changes a physics constraint's .
+ public class PhysicsConstraintInertiaTimeline : PhysicsConstraintTimeline {
+ public PhysicsConstraintInertiaTimeline (int frameCount, int bezierCount, int physicsConstraintIndex)
+ : base(frameCount, bezierCount, physicsConstraintIndex, Property.PhysicsConstraintInertia) {
+ }
+
+ override protected float Setup (PhysicsConstraint constraint) {
+ return constraint.data.inertia;
+ }
+
+ override protected float Get (PhysicsConstraint constraint) {
+ return constraint.inertia;
+ }
+
+ override protected void Set (PhysicsConstraint constraint, float value) {
+ constraint.inertia = value;
+ }
+
+ override protected bool Global (PhysicsConstraintData constraint) {
+ return constraint.inertiaGlobal;
+ }
+ }
+
+ /// Changes a physics constraint's .
+ public class PhysicsConstraintStrengthTimeline : PhysicsConstraintTimeline {
+ public PhysicsConstraintStrengthTimeline (int frameCount, int bezierCount, int physicsConstraintIndex)
+ : base(frameCount, bezierCount, physicsConstraintIndex, Property.PhysicsConstraintStrength) {
+ }
+
+ override protected float Setup (PhysicsConstraint constraint) {
+ return constraint.data.strength;
+ }
+
+ override protected float Get (PhysicsConstraint constraint) {
+ return constraint.strength;
+ }
+
+ override protected void Set (PhysicsConstraint constraint, float value) {
+ constraint.strength = value;
+ }
+
+ override protected bool Global (PhysicsConstraintData constraint) {
+ return constraint.strengthGlobal;
+ }
+ }
+
+ /// Changes a physics constraint's .
+ public class PhysicsConstraintDampingTimeline : PhysicsConstraintTimeline {
+ public PhysicsConstraintDampingTimeline (int frameCount, int bezierCount, int physicsConstraintIndex)
+ : base(frameCount, bezierCount, physicsConstraintIndex, Property.PhysicsConstraintDamping) {
+ }
+
+ override protected float Setup (PhysicsConstraint constraint) {
+ return constraint.data.damping;
+ }
+
+ override protected float Get (PhysicsConstraint constraint) {
+ return constraint.damping;
+ }
+
+ override protected void Set (PhysicsConstraint constraint, float value) {
+ constraint.damping = value;
+ }
+
+ override protected bool Global (PhysicsConstraintData constraint) {
+ return constraint.dampingGlobal;
+ }
+ }
+
+ /// Changes a physics constraint's . The timeline values are not inverted.
+ public class PhysicsConstraintMassTimeline : PhysicsConstraintTimeline {
+ public PhysicsConstraintMassTimeline (int frameCount, int bezierCount, int physicsConstraintIndex)
+ : base(frameCount, bezierCount, physicsConstraintIndex, Property.PhysicsConstraintMass) {
+ }
+
+ override protected float Setup (PhysicsConstraint constraint) {
+ return 1 / constraint.data.massInverse;
+ }
+
+ override protected float Get (PhysicsConstraint constraint) {
+ return 1 / constraint.massInverse;
+ }
+
+ override protected void Set (PhysicsConstraint constraint, float value) {
+ constraint.massInverse = 1 / value;
+ }
+
+ override protected bool Global (PhysicsConstraintData constraint) {
+ return constraint.massGlobal;
+ }
+ }
+
+ /// Changes a physics constraint's .
+ public class PhysicsConstraintWindTimeline : PhysicsConstraintTimeline {
+ public PhysicsConstraintWindTimeline (int frameCount, int bezierCount, int physicsConstraintIndex)
+ : base(frameCount, bezierCount, physicsConstraintIndex, Property.PhysicsConstraintWind) {
+ }
+
+ override protected float Setup (PhysicsConstraint constraint) {
+ return constraint.data.wind;
+ }
+
+ override protected float Get (PhysicsConstraint constraint) {
+ return constraint.wind;
+ }
+
+ override protected void Set (PhysicsConstraint constraint, float value) {
+ constraint.wind = value;
+ }
+
+ override protected bool Global (PhysicsConstraintData constraint) {
+ return constraint.windGlobal;
+ }
+ }
+
+ /// Changes a physics constraint's .
+ public class PhysicsConstraintGravityTimeline : PhysicsConstraintTimeline {
+ public PhysicsConstraintGravityTimeline (int frameCount, int bezierCount, int physicsConstraintIndex)
+ : base(frameCount, bezierCount, physicsConstraintIndex, Property.PhysicsConstraintGravity) {
+ }
+
+ override protected float Setup (PhysicsConstraint constraint) {
+ return constraint.data.gravity;
+ }
+
+ override protected float Get (PhysicsConstraint constraint) {
+ return constraint.gravity;
+ }
+
+ override protected void Set (PhysicsConstraint constraint, float value) {
+ constraint.gravity = value;
+ }
+
+ override protected bool Global (PhysicsConstraintData constraint) {
+ return constraint.gravityGlobal;
+ }
+ }
+
+ /// Changes a physics constraint's .
+ public class PhysicsConstraintMixTimeline : PhysicsConstraintTimeline {
+ public PhysicsConstraintMixTimeline (int frameCount, int bezierCount, int physicsConstraintIndex)
+ : base(frameCount, bezierCount, physicsConstraintIndex, Property.PhysicsConstraintMix) {
+ }
+
+ override protected float Setup (PhysicsConstraint constraint) {
+ return constraint.data.mix;
+ }
+
+ override protected float Get (PhysicsConstraint constraint) {
+ return constraint.mix;
+ }
+
+ override protected void Set (PhysicsConstraint constraint, float value) {
+ constraint.mix = value;
+ }
+
+ override protected bool Global (PhysicsConstraintData constraint) {
+ return constraint.mixGlobal;
+ }
+ }
+
+ /// Resets a physics constraint when specific animation times are reached.
+ public class PhysicsConstraintResetTimeline : Timeline {
+ static readonly string[] propertyIds = { ((int)Property.PhysicsConstraintReset).ToString() };
+
+ readonly int constraintIndex;
+
+ /// -1 for all physics constraints in the skeleton.
+ public PhysicsConstraintResetTimeline (int frameCount, int physicsConstraintIndex)
+ : base(frameCount, propertyIds) {
+ constraintIndex = physicsConstraintIndex;
+ }
+
+ /// The index of the physics constraint in that will be reset when this timeline is
+ /// applied, or -1 if all physics constraints in the skeleton will be reset.
+ public int PhysicsConstraintIndex {
+ get {
+ return constraintIndex;
+ }
+ }
+
+ override public int FrameCount {
+ get { return frames.Length; }
+ }
+
+ /// Sets the time for the specified frame.
+ /// Between 0 and frameCount
, inclusive.
+ public void SetFrame (int frame, float time) {
+ frames[frame] = time;
+ }
+
+ /// Resets the physics constraint when frames > lastTime
and <= time
.
+ override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend,
+ MixDirection direction) {
+
+ PhysicsConstraint constraint = null;
+ if (constraintIndex != -1) {
+ constraint = skeleton.physicsConstraints.Items[constraintIndex];
+ if (!constraint.active) return;
+ }
+
+ float[] frames = this.frames;
+
+ if (lastTime > time) { // Apply after lastTime for looped animations.
+ Apply(skeleton, lastTime, int.MaxValue, null, alpha, blend, direction);
+ lastTime = -1f;
+ } else if (lastTime >= frames[frames.Length - 1]) // Last time is after last frame.
+ return;
+ if (time < frames[0]) return;
+
+ if (lastTime < frames[0] || time >= frames[Search(frames, lastTime) + 1]) {
+ if (constraint != null)
+ constraint.Reset();
+ else {
+ PhysicsConstraint[] constraints = skeleton.physicsConstraints.Items;
+ for (int i = 0, n = skeleton.physicsConstraints.Count; i < n; i++) {
+ constraint = (PhysicsConstraint)constraints[i];
+ if (constraint.active) constraint.Reset();
+ }
+ }
+ }
+ }
+ }
+
+
/// Changes a slot's for an attachment's .
public class SequenceTimeline : Timeline, ISlotTimeline {
public const int ENTRIES = 3;
@@ -2646,7 +2731,7 @@ override public void Apply (Skeleton skeleton, float lastTime, float time, Expos
if (sequence == null) return;
float[] frames = this.frames;
- if (time < frames[0]) { // Time is before first frame.
+ if (time < frames[0]) {
if (blend == MixBlend.Setup || blend == MixBlend.First) slot.SequenceIndex = -1;
return;
}
@@ -2659,7 +2744,7 @@ override public void Apply (Skeleton skeleton, float lastTime, float time, Expos
int index = modeAndIndex >> 4, count = sequence.Regions.Length;
SequenceMode mode = (SequenceMode)(modeAndIndex & 0xf);
if (mode != SequenceMode.Hold) {
- index += (int)((time - before) / delay + 0.00001f);
+ index += (int)((time - before) / delay + 0.0001f);
switch (mode) {
case SequenceMode.Once:
index = Math.Min(count - 1, index);
diff --git a/spine-csharp/src/AnimationState.cs b/spine-csharp/src/AnimationState.cs
index 3b5354091d..337d00f2a1 100644
--- a/spine-csharp/src/AnimationState.cs
+++ b/spine-csharp/src/AnimationState.cs
@@ -246,6 +246,7 @@ public bool Apply (Skeleton skeleton) {
mix *= ApplyMixingFrom(current, skeleton, blend);
else if (current.trackTime >= current.trackEnd && current.next == null) //
mix = 0; // Set to setup pose the last time the entry will be applied.
+ bool attachments = mix < current.attachmentThreshold;
// Apply current entry.
float animationLast = current.animationLast, animationTime = current.AnimationTime, applyTime = animationTime;
@@ -258,10 +259,11 @@ public bool Apply (Skeleton skeleton) {
int timelineCount = current.animation.timelines.Count;
Timeline[] timelines = current.animation.timelines.Items;
if ((i == 0 && mix == 1) || blend == MixBlend.Add) {
+ if (i == 0) attachments = true;
for (int ii = 0; ii < timelineCount; ii++) {
Timeline timeline = timelines[ii];
if (timeline is AttachmentTimeline)
- ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, applyTime, blend, true);
+ ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, applyTime, blend, attachments);
else
timeline.Apply(skeleton, animationLast, applyTime, applyEvents, mix, blend, MixDirection.In);
}
@@ -281,7 +283,7 @@ public bool Apply (Skeleton skeleton) {
ApplyRotateTimeline(rotateTimeline, skeleton, applyTime, mix, timelineBlend, timelinesRotation,
ii << 1, firstFrame);
else if (timeline is AttachmentTimeline)
- ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, applyTime, blend, true);
+ ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, applyTime, blend, attachments);
else
timeline.Apply(skeleton, animationLast, applyTime, applyEvents, mix, timelineBlend, MixDirection.In);
}
@@ -544,7 +546,7 @@ static private void ApplyRotateTimeline (RotateTimeline timeline, Skeleton skele
// Mix between rotations using the direction of the shortest route on the first frame.
float total, diff = r2 - r1;
- diff -= (16384 - (int)(16384.499999999996 - diff / 360)) * 360;
+ diff -= (float)Math.Ceiling(diff / 360 - 0.5f) * 360;
if (diff == 0) {
total = timelinesRotation[i];
} else {
@@ -942,7 +944,7 @@ public void ClearListenerNotifications () {
///
public float TimeScale { get { return timeScale; } set { timeScale = value; } }
- /// The AnimationStateData to look up mix durations.
+ /// The to look up mix durations.
public AnimationStateData Data {
get {
return data;
@@ -1037,7 +1039,7 @@ public void Reset () {
/// duration.
public bool Loop { get { return loop; } set { loop = value; } }
- ///
+ ///
///
/// Seconds to postpone playing the animation. When this track entry is the current track entry, Delay
/// postpones incrementing the . When this track entry is queued, Delay
is the time from
@@ -1096,7 +1098,7 @@ public float TrackComplete {
///
/// Seconds for the last frame of this animation. Non-looping animations won't play past this time. Looping animations will
/// loop back to at this time. Defaults to the animation .
- ///
+ ///
public float AnimationEnd { get { return animationEnd; } set { animationEnd = value; } }
///
@@ -1173,7 +1175,7 @@ public float AnimationTime {
/// When the mix percentage ( / ) is less than the
/// AttachmentThreshold
, attachment timelines are applied while this animation is being mixed out. Defaults to
/// 0, so attachment timelines are not applied while this animation is being mixed out.
- ///
+ ///
public float AttachmentThreshold { get { return attachmentThreshold; } set { attachmentThreshold = value; } }
///
@@ -1194,6 +1196,12 @@ public float AnimationTime {
/// The animation queued to play before this animation, or null. previous
makes up a doubly linked list.
public TrackEntry Previous { get { return previous; } }
+ /// Returns true if this track entry has been applied at least once.
+ ///
+ public bool WasApplied {
+ get { return nextTrackLast != -1; }
+ }
+
///
/// Returns true if at least one loop has been completed.
///
@@ -1222,6 +1230,17 @@ public bool IsComplete {
///
public float MixDuration { get { return mixDuration; } set { mixDuration = value; } }
+ /// Sets both and .
+ /// If > 0, sets . If <= 0, the delay set is the duration of the previous track
+ /// entry minus the specified mix duration plus the specified delay
(ie the mix ends at
+ /// (delay
= 0) or before (delay
< 0) the previous track entry duration). If the previous
+ /// entry is looping, its next loop completion is used instead of its duration.
+ public void SetMixDuration (float mixDuration, float delay) {
+ this.mixDuration = mixDuration;
+ if (previous != null && delay <= 0) delay += previous.TrackComplete - mixDuration;
+ this.delay = delay;
+ }
+
///
///
/// Controls how properties keyed in the animation are mixed with lower tracks. Defaults to .
@@ -1235,12 +1254,12 @@ public bool IsComplete {
///
/// The track entry for the previous animation when mixing from the previous animation to this animation, or null if no
- /// mixing is currently occuring. When mixing from multiple animations, MixingFrom
makes up a linked list.
+ /// mixing is currently occurring. When mixing from multiple animations, MixingFrom
makes up a linked list.
public TrackEntry MixingFrom { get { return mixingFrom; } }
///
/// The track entry for the next animation when mixing from this animation to the next animation, or null if no mixing is
- /// currently occuring. When mixing to multiple animations, MixingTo
makes up a linked list.
+ /// currently occurring. When mixing to multiple animations, MixingTo
makes up a linked list.
public TrackEntry MixingTo { get { return mixingTo; } }
///
diff --git a/spine-csharp/src/Attachments/Attachment.cs b/spine-csharp/src/Attachments/Attachment.cs
index af87ce5fe5..0b9b10859f 100644
--- a/spine-csharp/src/Attachments/Attachment.cs
+++ b/spine-csharp/src/Attachments/Attachment.cs
@@ -50,7 +50,7 @@ override public string ToString () {
return Name;
}
- ///Returns a copy of the attachment.
+ /// Returns a copy of the attachment.
public abstract Attachment Copy ();
}
}
diff --git a/spine-csharp/src/Attachments/MeshAttachment.cs b/spine-csharp/src/Attachments/MeshAttachment.cs
index 9558010bea..430eb1867a 100644
--- a/spine-csharp/src/Attachments/MeshAttachment.cs
+++ b/spine-csharp/src/Attachments/MeshAttachment.cs
@@ -197,7 +197,7 @@ override public void ComputeWorldVertices (Slot slot, int start, int count, floa
base.ComputeWorldVertices(slot, start, count, worldVertices, offset, stride);
}
- ///Returns a new mesh with this mesh set as the .
+ /// Returns a new mesh with this mesh set as the .
public MeshAttachment NewLinkedMesh () {
MeshAttachment mesh = new MeshAttachment(Name);
diff --git a/spine-csharp/src/Attachments/PointAttachment.cs b/spine-csharp/src/Attachments/PointAttachment.cs
index f2db057783..ee902df54d 100644
--- a/spine-csharp/src/Attachments/PointAttachment.cs
+++ b/spine-csharp/src/Attachments/PointAttachment.cs
@@ -27,6 +27,8 @@
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
+using System;
+
namespace Spine {
///
/// An attachment which is a single point and a rotation. This can be used to spawn projectiles, particles, etc. A bone can be
@@ -45,7 +47,7 @@ public PointAttachment (string name)
: base(name) {
}
- /** Copy constructor. */
+ /// Copy constructor.
protected PointAttachment (PointAttachment other)
: base(other) {
x = other.x;
@@ -58,10 +60,10 @@ public void ComputeWorldPosition (Bone bone, out float ox, out float oy) {
}
public float ComputeWorldRotation (Bone bone) {
- float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation);
- float ix = cos * bone.a + sin * bone.b;
- float iy = cos * bone.c + sin * bone.d;
- return MathUtils.Atan2(iy, ix) * MathUtils.RadDeg;
+ float r = rotation * MathUtils.DegRad, cos = (float)Math.Cos(r), sin = (float)Math.Sin(r);
+ float x = cos * bone.a + sin * bone.b;
+ float y = cos * bone.c + sin * bone.d;
+ return MathUtils.Atan2Deg(y, x);
}
public override Attachment Copy () {
diff --git a/spine-csharp/src/Attachments/RegionAttachment.cs b/spine-csharp/src/Attachments/RegionAttachment.cs
index e8e8999338..37bb12fc17 100644
--- a/spine-csharp/src/Attachments/RegionAttachment.cs
+++ b/spine-csharp/src/Attachments/RegionAttachment.cs
@@ -106,8 +106,7 @@ public void UpdateRegion () {
return;
}
- float width = Width;
- float height = Height;
+ float width = Width, height = Height;
float localX2 = width / 2;
float localY2 = height / 2;
float localX = -localX2;
@@ -126,17 +125,13 @@ public void UpdateRegion () {
localY2 -= (region.originalHeight - region.offsetY - region.packedHeight) / region.originalHeight * height;
}
}
- float scaleX = ScaleX;
- float scaleY = ScaleY;
+ float scaleX = ScaleX, scaleY = ScaleY;
localX *= scaleX;
localY *= scaleY;
localX2 *= scaleX;
localY2 *= scaleY;
- float rotation = Rotation;
- float cos = MathUtils.CosDeg(this.rotation);
- float sin = MathUtils.SinDeg(this.rotation);
- float x = X;
- float y = Y;
+ float r = Rotation * MathUtils.DegRad, cos = (float)Math.Cos(r), sin = (float)Math.Sin(r);
+ float x = X, y = Y;
float localXCos = localX * cos + x;
float localXSin = localX * sin;
float localYCos = localY * cos + y;
diff --git a/spine-csharp/src/Attachments/VertexAttachment.cs b/spine-csharp/src/Attachments/VertexAttachment.cs
index 3b294bb161..1da942b04b 100644
--- a/spine-csharp/src/Attachments/VertexAttachment.cs
+++ b/spine-csharp/src/Attachments/VertexAttachment.cs
@@ -47,7 +47,7 @@ public abstract class VertexAttachment : Attachment {
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; } }
- ///Timelines for the timeline attachment are also applied to this attachment.
+ /// Timelines for the timeline attachment are also applied to this attachment.
/// May be null if no attachment-specific timelines should be applied.
public VertexAttachment TimelineAttachment { get { return timelineAttachment; } set { timelineAttachment = value; } }
diff --git a/spine-csharp/src/Bone.cs b/spine-csharp/src/Bone.cs
index 69002dc8ff..f35fb0cd22 100644
--- a/spine-csharp/src/Bone.cs
+++ b/spine-csharp/src/Bone.cs
@@ -30,6 +30,8 @@
using System;
namespace Spine {
+ using Physics = Skeleton.Physics;
+
///
/// Stores a bone's current pose.
///
@@ -57,8 +59,6 @@ public class Bone : IUpdatable {
public Skeleton Skeleton { get { return skeleton; } }
public Bone Parent { get { return parent; } }
public ExposedList Children { get { return children; } }
- /// Returns false when the bone has not been computed because is true and the
- /// active skin does not contain this bone.
public bool Active { get { return active; } }
/// The local X translation.
public float X { get { return x; } set { x = value; } }
@@ -113,8 +113,10 @@ public class Bone : IUpdatable {
public float WorldX { get { return worldX; } set { worldX = value; } }
/// The world Y position. If changed, should be called.
public float WorldY { get { return worldY; } set { worldY = value; } }
- public float WorldRotationX { get { return MathUtils.Atan2(c, a) * MathUtils.RadDeg; } }
- public float WorldRotationY { get { return MathUtils.Atan2(d, b) * MathUtils.RadDeg; } }
+ /// The world rotation for the X axis, calculated using and .
+ public float WorldRotationX { get { return MathUtils.Atan2Deg(c, a); } }
+ /// The world rotation for the Y axis, calculated using and .
+ public float WorldRotationY { get { return MathUtils.Atan2Deg(d, b); } }
/// Returns the magnitide (always positive) of the world scale X.
public float WorldScaleX { get { return (float)Math.Sqrt(a * a + c * c); } }
@@ -148,7 +150,7 @@ public Bone (Bone bone, Skeleton skeleton, Bone parent) {
}
/// Computes the world transform using the parent bone and this bone's local applied transform.
- public void Update () {
+ public void Update (Physics physics) {
UpdateWorldTransform(ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY);
}
@@ -173,11 +175,14 @@ public void UpdateWorldTransform (float x, float y, float rotation, float scaleX
Bone parent = this.parent;
if (parent == null) { // Root bone.
- float rotationY = rotation + 90 + shearY, sx = skeleton.scaleX, sy = skeleton.scaleY;
- a = MathUtils.CosDeg(rotation + shearX) * scaleX * sx;
- b = MathUtils.CosDeg(rotationY) * scaleY * sx;
- c = MathUtils.SinDeg(rotation + shearX) * scaleX * sy;
- d = MathUtils.SinDeg(rotationY) * scaleY * sy;
+ Skeleton skeleton = this.skeleton;
+ float sx = skeleton.scaleX, sy = skeleton.scaleY;
+ float rx = (rotation + shearX) * MathUtils.DegRad;
+ float ry = (rotation + 90 + shearY) * MathUtils.DegRad;
+ a = (float)Math.Cos(rx) * scaleX * sx;
+ b = (float)Math.Cos(ry) * scaleY * sx;
+ c = (float)Math.Sin(rx) * scaleX * sy;
+ d = (float)Math.Sin(ry) * scaleY * sy;
worldX = x * sx + skeleton.x;
worldY = y * sy + skeleton.y;
return;
@@ -189,11 +194,12 @@ public void UpdateWorldTransform (float x, float y, float rotation, float scaleX
switch (data.transformMode) {
case TransformMode.Normal: {
- float rotationY = rotation + 90 + shearY;
- float la = MathUtils.CosDeg(rotation + shearX) * scaleX;
- float lb = MathUtils.CosDeg(rotationY) * scaleY;
- float lc = MathUtils.SinDeg(rotation + shearX) * scaleX;
- float ld = MathUtils.SinDeg(rotationY) * scaleY;
+ float rx = (rotation + shearX) * MathUtils.DegRad;
+ float ry = (rotation + 90 + shearY) * MathUtils.DegRad;
+ float la = (float)Math.Cos(rx) * scaleX;
+ float lb = (float)Math.Cos(ry) * scaleY;
+ float lc = (float)Math.Sin(rx) * scaleX;
+ float ld = (float)Math.Sin(ry) * scaleY;
a = pa * la + pb * lc;
b = pa * lb + pb * ld;
c = pc * la + pd * lc;
@@ -201,11 +207,12 @@ public void UpdateWorldTransform (float x, float y, float rotation, float scaleX
return;
}
case TransformMode.OnlyTranslation: {
- float rotationY = rotation + 90 + shearY;
- a = MathUtils.CosDeg(rotation + shearX) * scaleX;
- b = MathUtils.CosDeg(rotationY) * scaleY;
- c = MathUtils.SinDeg(rotation + shearX) * scaleX;
- d = MathUtils.SinDeg(rotationY) * scaleY;
+ float rx = (rotation + shearX) * MathUtils.DegRad;
+ float ry = (rotation + 90 + shearY) * MathUtils.DegRad;
+ a = (float)Math.Cos(rx) * scaleX;
+ b = (float)Math.Cos(ry) * scaleY;
+ c = (float)Math.Sin(rx) * scaleX;
+ d = (float)Math.Sin(ry) * scaleY;
break;
}
case TransformMode.NoRotationOrReflection: {
@@ -216,18 +223,18 @@ public void UpdateWorldTransform (float x, float y, float rotation, float scaleX
pc /= skeleton.scaleY;
pb = pc * s;
pd = pa * s;
- prx = MathUtils.Atan2(pc, pa) * MathUtils.RadDeg;
+ prx = MathUtils.Atan2Deg(pc, pa);
} else {
pa = 0;
pc = 0;
- prx = 90 - MathUtils.Atan2(pd, pb) * MathUtils.RadDeg;
+ prx = 90 - MathUtils.Atan2Deg(pd, pb);
}
- float rx = rotation + shearX - prx;
- float ry = rotation + shearY - prx + 90;
- float la = MathUtils.CosDeg(rx) * scaleX;
- float lb = MathUtils.CosDeg(ry) * scaleY;
- float lc = MathUtils.SinDeg(rx) * scaleX;
- float ld = MathUtils.SinDeg(ry) * scaleY;
+ float rx = (rotation + shearX - prx) * MathUtils.DegRad;
+ float ry = (rotation + shearY - prx + 90) * MathUtils.DegRad;
+ float la = (float)Math.Cos(rx) * scaleX;
+ float lb = (float)Math.Cos(ry) * scaleY;
+ float lc = (float)Math.Sin(rx) * scaleX;
+ float ld = (float)Math.Sin(ry) * scaleY;
a = pa * la - pb * lc;
b = pa * lb - pb * ld;
c = pc * la + pd * lc;
@@ -236,7 +243,8 @@ public void UpdateWorldTransform (float x, float y, float rotation, float scaleX
}
case TransformMode.NoScale:
case TransformMode.NoScaleOrReflection: {
- float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation);
+ rotation *= MathUtils.DegRad;
+ float cos = (float)Math.Cos(rotation), sin = (float)Math.Sin(rotation);
float za = (pa * cos + pb * sin) / skeleton.scaleX;
float zc = (pc * cos + pd * sin) / skeleton.scaleY;
float s = (float)Math.Sqrt(za * za + zc * zc);
@@ -246,14 +254,15 @@ public void UpdateWorldTransform (float x, float y, float rotation, float scaleX
s = (float)Math.Sqrt(za * za + zc * zc);
if (data.transformMode == TransformMode.NoScale
&& (pa * pd - pb * pc < 0) != (skeleton.scaleX < 0 != skeleton.scaleY < 0)) s = -s;
-
- float r = MathUtils.PI / 2 + MathUtils.Atan2(zc, za);
- float zb = MathUtils.Cos(r) * s;
- float zd = MathUtils.Sin(r) * s;
- float la = MathUtils.CosDeg(shearX) * scaleX;
- float lb = MathUtils.CosDeg(90 + shearY) * scaleY;
- float lc = MathUtils.SinDeg(shearX) * scaleX;
- float ld = MathUtils.SinDeg(90 + shearY) * scaleY;
+ rotation = MathUtils.PI / 2 + MathUtils.Atan2(zc, za);
+ float zb = (float)Math.Cos(rotation) * s;
+ float zd = (float)Math.Sin(rotation) * s;
+ shearX *= MathUtils.DegRad;
+ shearY = (90 + shearY) * MathUtils.DegRad;
+ float la = (float)Math.Cos(shearX) * scaleX;
+ float lb = (float)Math.Cos(shearY) * scaleY;
+ float lc = (float)Math.Sin(shearX) * scaleX;
+ float ld = (float)Math.Sin(shearY) * scaleY;
a = za * la + zb * lc;
b = za * lb + zb * ld;
c = zc * la + zd * lc;
@@ -261,13 +270,13 @@ public void UpdateWorldTransform (float x, float y, float rotation, float scaleX
break;
}
}
-
a *= skeleton.scaleX;
b *= skeleton.scaleX;
c *= skeleton.scaleY;
d *= skeleton.scaleY;
}
+ /// Sets this bone's local transform to the setup pose.
public void SetToSetupPose () {
BoneData data = this.data;
x = data.x;
@@ -294,18 +303,19 @@ public void UpdateAppliedTransform () {
if (parent == null) {
ax = worldX - skeleton.x;
ay = worldY - skeleton.y;
- arotation = MathUtils.Atan2(c, a) * MathUtils.RadDeg;
+ float a = this.a, b = this.b, c = this.c, d = this.d;
+ arotation = MathUtils.Atan2Deg(c, a);
ascaleX = (float)Math.Sqrt(a * a + c * c);
ascaleY = (float)Math.Sqrt(b * b + d * d);
ashearX = 0;
- ashearY = MathUtils.Atan2(a * b + c * d, a * d - b * c) * MathUtils.RadDeg;
+ ashearY = MathUtils.Atan2Deg(a * b + c * d, a * d - b * c);
return;
}
+
float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
float pid = 1 / (pa * pd - pb * pc);
float ia = pd * pid, ib = pb * pid, ic = pc * pid, id = pa * pid;
float dx = worldX - parent.worldX, dy = worldY - parent.worldY;
-
ax = (dx * ia - dy * ib);
ay = (dy * id - dx * ic);
@@ -330,7 +340,7 @@ public void UpdateAppliedTransform () {
}
case TransformMode.NoScale:
case TransformMode.NoScaleOrReflection: {
- float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation);
+ float r = rotation * MathUtils.DegRad, cos = (float)Math.Cos(r), sin = (float)Math.Sin(r);
pa = (pa * cos + pb * sin) / skeleton.scaleX;
pc = (pc * cos + pd * sin) / skeleton.scaleY;
float s = (float)Math.Sqrt(pa * pa + pc * pc);
@@ -339,9 +349,9 @@ public void UpdateAppliedTransform () {
pc *= s;
s = (float)Math.Sqrt(pa * pa + pc * pc);
if (data.transformMode == TransformMode.NoScale && pid < 0 != (skeleton.scaleX < 0 != skeleton.scaleY < 0)) s = -s;
- float r = MathUtils.PI / 2 + MathUtils.Atan2(pc, pa);
- pb = MathUtils.Cos(r) * s;
- pd = MathUtils.Sin(r) * s;
+ r = MathUtils.PI / 2 + MathUtils.Atan2(pc, pa);
+ pb = (float)Math.Cos(r) * s;
+ pd = (float)Math.Sin(r) * s;
pid = 1 / (pa * pd - pb * pc);
ia = pd * pid;
ib = pb * pid;
@@ -361,16 +371,17 @@ public void UpdateAppliedTransform () {
if (ascaleX > 0.0001f) {
float det = ra * rd - rb * rc;
ascaleY = det / ascaleX;
- ashearY = -MathUtils.Atan2(ra * rb + rc * rd, det) * MathUtils.RadDeg;
- arotation = MathUtils.Atan2(rc, ra) * MathUtils.RadDeg;
+ ashearY = -MathUtils.Atan2Deg(ra * rb + rc * rd, det);
+ arotation = MathUtils.Atan2Deg(rc, ra);
} else {
ascaleX = 0;
ascaleY = (float)Math.Sqrt(rb * rb + rd * rd);
ashearY = 0;
- arotation = 90 - MathUtils.Atan2(rd, rb) * MathUtils.RadDeg;
+ arotation = 90 - MathUtils.Atan2Deg(rd, rb);
}
}
+ /// Transforms a point from world coordinates to the bone's local coordinates.
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 det = a * d - b * c;
@@ -379,53 +390,60 @@ public void WorldToLocal (float worldX, float worldY, out float localX, out floa
localY = (y * a - x * c) / det;
}
+ /// Transforms a point from the bone's local coordinates to world coordinates.
public void LocalToWorld (float localX, float localY, out float worldX, out float worldY) {
worldX = localX * a + localY * b + this.worldX;
worldY = localX * c + localY * d + this.worldY;
}
- public float WorldToLocalRotationX {
- get {
- Bone parent = this.parent;
- if (parent == null) return arotation;
- 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;
+ /// Transforms a point from world coordinates to the parent bone's local coordinates.
+ public void WorldToParent (float worldX, float worldY, out float parentX, out float parentY) {
+ if (parent == null) {
+ parentX = worldX;
+ parentY = worldY;
+ } else {
+ parent.WorldToLocal(worldX, worldY, out parentX, out parentY);
}
}
- public float WorldToLocalRotationY {
- get {
- Bone parent = this.parent;
- if (parent == null) return arotation;
- 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;
+ /// Transforms a point from the parent bone's coordinates to world coordinates.
+ public void ParentToWorld (float parentX, float parentY, out float worldX, out float worldY) {
+ if (parent == null) {
+ worldX = parentX;
+ worldY = parentY;
+ } else {
+ parent.LocalToWorld(parentX, parentY, out worldX, out worldY);
}
}
+ /// Transforms a world rotation to a local rotation.
public float WorldToLocalRotation (float worldRotation) {
- float sin = MathUtils.SinDeg(worldRotation), cos = MathUtils.CosDeg(worldRotation);
- return MathUtils.Atan2(a * sin - c * cos, d * cos - b * sin) * MathUtils.RadDeg + rotation - shearX;
+ worldRotation *= MathUtils.DegRad;
+ float sin = (float)Math.Sin(worldRotation), cos = (float)Math.Cos(worldRotation);
+ return MathUtils.Atan2Deg(a * sin - c * cos, d * cos - b * sin) + rotation - shearX;
}
+ /// Transforms a local rotation to a world rotation.
public float LocalToWorldRotation (float localRotation) {
- localRotation -= rotation - shearX;
- float sin = MathUtils.SinDeg(localRotation), cos = MathUtils.CosDeg(localRotation);
- return MathUtils.Atan2(cos * c + sin * d, cos * a + sin * b) * MathUtils.RadDeg;
+ localRotation = (localRotation - rotation - shearX) * MathUtils.DegRad;
+ float sin = (float)Math.Sin(localRotation), cos = (float)Math.Cos(localRotation);
+ return MathUtils.Atan2Deg(cos * c + sin * d, cos * a + sin * b);
}
///
/// Rotates the world transform the specified amount.
///
- /// After changes are made to the world transform, should be called and will
- /// need to be called on any child bones, recursively.
+ /// After changes are made to the world transform, should be called and
+ /// will need to be called on any child bones, recursively.
///
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;
+ degrees *= MathUtils.DegRad;
+ float sin = (float)Math.Sin(degrees), cos = (float)Math.Cos(degrees);
+ float ra = a, rb = b;
+ a = cos * ra - sin * c;
+ b = cos * rb - sin * d;
+ c = sin * ra + cos * c;
+ d = sin * rb + cos * d;
}
override public string ToString () {
diff --git a/spine-csharp/src/BoneData.cs b/spine-csharp/src/BoneData.cs
index 594f27163e..203643c0b3 100644
--- a/spine-csharp/src/BoneData.cs
+++ b/spine-csharp/src/BoneData.cs
@@ -56,7 +56,7 @@ public class BoneData {
/// Local Y translation.
public float Y { get { return y; } set { y = value; } }
- /// Local rotation.
+ /// Local rotation in degrees, counter clockwise.
public float Rotation { get { return rotation; } set { rotation = value; } }
/// Local scaleX.
@@ -74,8 +74,8 @@ public class BoneData {
/// The transform mode for how parent world transforms affect this bone.
public TransformMode TransformMode { get { return transformMode; } set { transformMode = value; } }
- ///When true, only updates this bone if the contains this
- /// bone.
+ /// When true, only updates this bone if the contains
+ /// this bone.
///
public bool SkinRequired { get { return skinRequired; } set { skinRequired = value; } }
diff --git a/spine-csharp/src/ConstraintData.cs b/spine-csharp/src/ConstraintData.cs
index 374ba67307..9242358f5d 100644
--- a/spine-csharp/src/ConstraintData.cs
+++ b/spine-csharp/src/ConstraintData.cs
@@ -45,13 +45,13 @@ public ConstraintData (string name) {
/// The constraint's name, which is unique across all constraints in the skeleton of the same type.
public string Name { get { return name; } }
- ///The ordinal of this constraint for the order a skeleton's constraints will be applied by
- /// .
+ /// The ordinal of this constraint for the order a skeleton's constraints will be applied by
+ /// .
public int Order { get { return order; } set { order = value; } }
- ///When true, only updates this constraint if the contains
- /// this constraint.
- ///
+ /// When true, only updates this constraint if the
+ /// contains this constraint.
+ ///
public bool SkinRequired { get { return skinRequired; } set { skinRequired = value; } }
override public string ToString () {
diff --git a/spine-csharp/src/IUpdatable.cs b/spine-csharp/src/IUpdatable.cs
index 4242fb4fa9..80164db6f2 100644
--- a/spine-csharp/src/IUpdatable.cs
+++ b/spine-csharp/src/IUpdatable.cs
@@ -28,15 +28,20 @@
*****************************************************************************/
namespace Spine {
+ using Physics = Skeleton.Physics;
- ///The interface for items updated by .
+ /// The interface for items updated by .
public interface IUpdatable {
- void Update ();
+ /// Determines how physics and other non-deterministic updates are applied.
+ void Update (Physics physics);
- ///Returns false when this item has not been updated because a skin is required and the active
- /// skin does not contain this item.
+ /// Returns false when this item won't be updated by
+ /// because a skin is required and the
+ /// active skin does not contain this item.
///
///
+ ///
+ ///
bool Active { get; }
}
}
diff --git a/spine-csharp/src/IkConstraint.cs b/spine-csharp/src/IkConstraint.cs
index d9df545f86..151db3c904 100644
--- a/spine-csharp/src/IkConstraint.cs
+++ b/spine-csharp/src/IkConstraint.cs
@@ -30,6 +30,8 @@
using System;
namespace Spine {
+ using Physics = Skeleton.Physics;
+
///
///
/// Stores the current pose for an IK constraint. An IK constraint adjusts the rotation of 1 or 2 constrained bones so the tip of
@@ -49,7 +51,6 @@ public class IkConstraint : IUpdatable {
public IkConstraint (IkConstraintData 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;
mix = data.mix;
softness = data.softness;
@@ -60,18 +61,17 @@ public IkConstraint (IkConstraintData data, Skeleton skeleton) {
bones = new ExposedList(data.bones.Count);
foreach (BoneData boneData in data.bones)
bones.Add(skeleton.bones.Items[boneData.index]);
+
target = skeleton.bones.Items[data.target.index];
}
/// Copy constructor.
- public IkConstraint (IkConstraint constraint, Skeleton skeleton) {
- if (constraint == null) throw new ArgumentNullException("constraint cannot be null.");
- if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null.");
+ public IkConstraint (IkConstraint constraint) {
+ if (constraint == null) throw new ArgumentNullException("constraint", "constraint cannot be null.");
data = constraint.data;
bones = new ExposedList(constraint.Bones.Count);
- foreach (Bone bone in constraint.Bones)
- bones.Add(skeleton.Bones.Items[bone.data.index]);
- target = skeleton.Bones.Items[constraint.target.data.index];
+ bones.AddRange(constraint.Bones);
+ target = constraint.target;
mix = constraint.mix;
softness = constraint.softness;
bendDirection = constraint.bendDirection;
@@ -79,7 +79,16 @@ public IkConstraint (IkConstraint constraint, Skeleton skeleton) {
stretch = constraint.stretch;
}
- public void Update () {
+ public void SetToSetupPose () {
+ IkConstraintData data = this.data;
+ mix = data.mix;
+ softness = data.softness;
+ bendDirection = data.bendDirection;
+ compress = data.compress;
+ stretch = data.stretch;
+ }
+
+ public void Update (Physics physics) {
if (mix == 0) return;
Bone target = this.target;
Bone[] bones = this.bones.Items;
@@ -177,7 +186,7 @@ static public void Apply (Bone bone, float targetX, float targetY, bool compress
float sc = pc / bone.skeleton.scaleY;
pb = -sc * s * bone.skeleton.scaleX;
pd = sa * s * bone.skeleton.scaleY;
- rotationIK += (float)Math.Atan2(sc, sa) * MathUtils.RadDeg;
+ rotationIK += MathUtils.Atan2Deg(sc, sa);
goto default; // Fall through.
}
default: {
@@ -194,7 +203,7 @@ static public void Apply (Bone bone, float targetX, float targetY, bool compress
}
}
- rotationIK += (float)Math.Atan2(ty, tx) * MathUtils.RadDeg;
+ rotationIK += MathUtils.Atan2Deg(ty, tx);
if (bone.ascaleX < 0) rotationIK += 180;
if (rotationIK > 180)
rotationIK -= 360;
@@ -210,11 +219,14 @@ static public void Apply (Bone bone, float targetX, float targetY, bool compress
ty = targetY - bone.worldY;
break;
}
- float b = bone.data.length * sx, dd = (float)Math.Sqrt(tx * tx + ty * ty);
- if ((compress && dd < b) || (stretch && dd > b) && b > 0.0001f) {
- float s = (dd / b - 1) * alpha + 1;
- sx *= s;
- if (uniform) sy *= s;
+ float b = bone.data.length * sx;
+ if (b > 0.0001f) {
+ float dd = tx * tx + ty * ty;
+ if ((compress && dd < b * b) || (stretch && dd > b * b)) {
+ float s = ((float)Math.Sqrt(dd) / b - 1) * alpha + 1;
+ sx *= s;
+ if (uniform) sy *= s;
+ }
}
}
bone.UpdateWorldTransform(bone.ax, bone.ay, bone.arotation + rotationIK * alpha, sx, sy, bone.ashearX, bone.ashearY);
diff --git a/spine-csharp/src/MathUtils.cs b/spine-csharp/src/MathUtils.cs
index 016045ede7..1ccc947b27 100644
--- a/spine-csharp/src/MathUtils.cs
+++ b/spine-csharp/src/MathUtils.cs
@@ -35,6 +35,7 @@ namespace Spine {
public static class MathUtils {
public const float PI = 3.1415927f;
public const float PI2 = PI * 2;
+ public const float InvPI2 = 1 / PI2;
public const float RadDeg = 180f / PI;
public const float DegRad = PI / 180;
@@ -115,6 +116,12 @@ static public float CosDeg (float degrees) {
return (float)Math.Cos(degrees * DegRad);
}
+
+ static public float Atan2Deg (float y, float x) {
+ return (float)Math.Atan2(y, x) * RadDeg;
+ }
+
+
/// Returns the atan2 using Math.Atan2.
static public float Atan2 (float y, float x) {
return (float)Math.Atan2(y, x);
diff --git a/spine-csharp/src/PathConstraint.cs b/spine-csharp/src/PathConstraint.cs
index e126676ef9..6a8c9b775b 100644
--- a/spine-csharp/src/PathConstraint.cs
+++ b/spine-csharp/src/PathConstraint.cs
@@ -30,6 +30,7 @@
using System;
namespace Spine {
+ using Physics = Skeleton.Physics;
///
///
@@ -57,9 +58,11 @@ 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(data.Bones.Count);
foreach (BoneData boneData in data.bones)
bones.Add(skeleton.bones.Items[boneData.index]);
+
target = skeleton.slots.Items[data.target.index];
position = data.position;
spacing = data.spacing;
@@ -69,14 +72,12 @@ public PathConstraint (PathConstraintData data, Skeleton skeleton) {
}
/// Copy constructor.
- public PathConstraint (PathConstraint constraint, Skeleton skeleton) {
+ public PathConstraint (PathConstraint constraint) {
if (constraint == null) throw new ArgumentNullException("constraint cannot be null.");
- if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null.");
data = constraint.data;
- bones = new ExposedList(constraint.bones.Count);
- foreach (Bone bone in constraint.bones)
- bones.Add(skeleton.bones.Items[bone.data.index]);
- target = skeleton.slots.Items[constraint.target.data.index];
+ bones = new ExposedList(constraint.Bones.Count);
+ bones.AddRange(constraint.Bones);
+ target = constraint.target;
position = constraint.position;
spacing = constraint.spacing;
mixRotate = constraint.mixRotate;
@@ -89,7 +90,16 @@ public static void ArraysFill (float[] a, int fromIndex, int toIndex, float val)
a[i] = val;
}
- public void Update () {
+ public void SetToSetupPose () {
+ PathConstraintData data = this.data;
+ position = data.position;
+ spacing = data.spacing;
+ mixRotate = data.mixRotate;
+ mixX = data.mixX;
+ mixY = data.mixY;
+ }
+
+ public void Update (Physics physics) {
PathAttachment attachment = target.Attachment as PathAttachment;
if (attachment == null) return;
@@ -108,12 +118,8 @@ public void Update () {
for (int i = 0, n = spacesCount - 1; i < n; i++) {
Bone bone = bonesItems[i];
float setupLength = bone.data.length;
- if (setupLength < PathConstraint.Epsilon)
- lengths[i] = 0;
- else {
- float x = setupLength * bone.a, y = setupLength * bone.c;
- lengths[i] = (float)Math.Sqrt(x * x + y * y);
- }
+ float x = setupLength * bone.a, y = setupLength * bone.c;
+ lengths[i] = (float)Math.Sqrt(x * x + y * y);
}
}
ArraysFill(spaces, 1, spacesCount, spacing);
diff --git a/spine-csharp/src/PhysicsConstraint.cs b/spine-csharp/src/PhysicsConstraint.cs
index 28704f8130..a8de40b327 100644
--- a/spine-csharp/src/PhysicsConstraint.cs
+++ b/spine-csharp/src/PhysicsConstraint.cs
@@ -1,16 +1,17 @@
+
/******************************************************************************
* Spine Runtimes License Agreement
- * Last updated September 24, 2021. Replaces all prior versions.
+ * Last updated July 28, 2023. Replaces all prior versions.
*
- * Copyright (c) 2013-2021, Esoteric Software LLC
+ * Copyright (c) 2013-2023, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software or
+ * otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
@@ -23,13 +24,15 @@
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
+ * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
namespace Spine {
+ using Physics = Skeleton.Physics;
+
///
/// Stores the current pose for a physics constraint. A physics constraint applies physics to bones.
///
@@ -37,66 +40,244 @@ namespace Spine {
///
public class PhysicsConstraint : IUpdatable {
internal readonly PhysicsConstraintData data;
- internal readonly ExposedList bones;
- // BOZO! - stiffness -> strength. stiffness, damping, rope, stretch -> move to spring.
- internal float mix, friction, gravity, wind, stiffness, damping;
- internal bool rope, stretch;
+ public Bone bone;
+ internal float inertia, strength, damping, massInverse, wind, gravity, mix;
+
+ bool reset = true;
+ float ux, uy, cx, cy, tx, ty;
+ float xOffset, xVelocity;
+ float yOffset, yVelocity;
+ float rotateOffset, rotateVelocity;
+ float scaleOffset, scaleVelocity;
internal bool active;
+ readonly Skeleton skeleton;
+ float remaining, lastTime;
+
public PhysicsConstraint (PhysicsConstraintData 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;
- mix = data.mix;
- friction = data.friction;
- gravity = data.gravity;
- wind = data.wind;
- stiffness = data.stiffness;
+ this.skeleton = skeleton;
+ bone = skeleton.bones.Items[data.bone.index];
+ inertia = data.inertia;
+ strength = data.strength;
damping = data.damping;
- rope = data.rope;
- stretch = data.stretch;
-
- bones = new ExposedList(data.Bones.Count);
- foreach (BoneData boneData in data.bones)
- bones.Add(skeleton.bones.Items[boneData.index]);
+ massInverse = data.massInverse;
+ wind = data.wind;
+ gravity = data.gravity;
+ mix = data.mix;
}
- /// Copy constructor.
- public PhysicsConstraint (PhysicsConstraint constraint, Skeleton skeleton) {
+ /** Copy constructor. */
+ public PhysicsConstraint (PhysicsConstraint constraint) {
if (constraint == null) throw new ArgumentNullException("constraint", "constraint cannot be null.");
- if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null.");
data = constraint.data;
- bones = new ExposedList(constraint.bones.Count);
- foreach (Bone bone in constraint.bones)
- bones.Add(skeleton.bones.Items[bone.data.index]);
- mix = constraint.mix;
- friction = constraint.friction;
- gravity = constraint.gravity;
- wind = constraint.wind;
- stiffness = constraint.stiffness;
+ skeleton = constraint.skeleton;
+ bone = constraint.bone;
+ inertia = constraint.inertia;
+ strength = constraint.strength;
damping = constraint.damping;
- rope = constraint.rope;
- stretch = constraint.stretch;
+ massInverse = constraint.massInverse;
+ wind = constraint.wind;
+ gravity = constraint.gravity;
+ mix = constraint.mix;
+ }
+
+ public void Reset () {
+ remaining = 0;
+ lastTime = skeleton.time;
+ reset = true;
+ xOffset = 0;
+ xVelocity = 0;
+ yOffset = 0;
+ yVelocity = 0;
+ rotateOffset = 0;
+ rotateVelocity = 0;
+ scaleOffset = 0;
+ scaleVelocity = 0;
+ }
+
+ public void SetToSetupPose () {
+ PhysicsConstraintData data = this.data;
+ inertia = data.inertia;
+ strength = data.strength;
+ damping = data.damping;
+ massInverse = data.massInverse;
+ wind = data.wind;
+ gravity = data.gravity;
+ mix = data.mix;
}
/// Applies the constraint to the constrained bones.
- public void Update () {
+ public void Update (Physics physics) {
+ float mix = this.mix;
+ if (mix == 0) return;
+
+ bool x = data.x > 0, y = data.y > 0, rotateOrShearX = data.rotate > 0 || data.shearX > 0, scaleX = data.scaleX > 0;
+ Bone bone = this.bone;
+ float l = bone.data.length;
+
+ switch (physics) {
+ case Physics.None:
+ return;
+ case Physics.Reset:
+ Reset();
+ goto case Physics.Update; // Fall through.
+ case Physics.Update:
+ remaining += Math.Max(skeleton.time - lastTime, 0);
+ lastTime = skeleton.time;
+ float bx = bone.worldX, by = bone.worldY;
+ if (reset) {
+ reset = false;
+ ux = bx;
+ uy = by;
+ } else {
+ float remaining = this.remaining, i = inertia, step = data.step;
+ if (x || y) {
+ if (x) {
+ xOffset += (ux - bx) * i;
+ ux = bx;
+ }
+ if (y) {
+ yOffset += (uy - by) * i;
+ uy = by;
+ }
+ if (remaining >= step) {
+ float m = massInverse * step, e = strength, w = wind * 100, g = gravity * -100;
+ float d = (float)Math.Pow(damping, 60 * step);
+ do {
+ if (x) {
+ xVelocity += (w - xOffset * e) * m;
+ xOffset += xVelocity * step;
+ xVelocity *= d;
+ }
+ if (y) {
+ yVelocity += (g - yOffset * e) * m;
+ yOffset += yVelocity * step;
+ yVelocity *= d;
+ }
+ remaining -= step;
+ } while (remaining >= step);
+ }
+ if (x) bone.worldX += xOffset * mix * data.x;
+ if (y) bone.worldY += yOffset * mix * data.y;
+ }
+ if (rotateOrShearX || scaleX) {
+ float ca = (float)Math.Atan2(bone.c, bone.a), c, s;
+ if (rotateOrShearX) {
+ float dx = cx - bone.worldX, dy = cy - bone.worldY, r = (float)Math.Atan2(dy + ty, dx + tx) - ca - rotateOffset * mix;
+ rotateOffset += (r - (float)Math.Ceiling(r * MathUtils.InvPI2 - 0.5f) * MathUtils.PI2) * i;
+ r = rotateOffset * mix + ca;
+ c = (float)Math.Cos(r);
+ s = (float)Math.Sin(r);
+ if (scaleX) {
+ r = l * bone.WorldScaleX;
+ if (r > 0) scaleOffset += (dx * c + dy * s) * i / r;
+ }
+ } else {
+ c = (float)Math.Cos(ca);
+ s = (float)Math.Sin(ca);
+ float r = l * bone.WorldScaleX;
+ if (r > 0) scaleOffset += ((cx - bone.worldX) * c + (cy - bone.worldY) * s) * i / r;
+ }
+ remaining = this.remaining;
+ if (remaining >= step) {
+ float m = massInverse * step, e = strength, w = wind, g = gravity;
+ float d = (float)Math.Pow(damping, 60 * step);
+ while (true) {
+ remaining -= step;
+ if (scaleX) {
+ scaleVelocity += (w * c - g * s - scaleOffset * e) * m;
+ scaleOffset += scaleVelocity * step;
+ scaleVelocity *= d;
+ }
+ if (rotateOrShearX) {
+ rotateVelocity += (-0.01f * l * (w * s + g * c) - rotateOffset * e) * m;
+ rotateOffset += rotateVelocity * step;
+ rotateVelocity *= d;
+ if (remaining < step) break;
+ float r = rotateOffset * mix + ca;
+ c = (float)Math.Cos(r);
+ s = (float)Math.Sin(r);
+ } else if (remaining < step) //
+ break;
+ }
+ }
+ }
+ this.remaining = remaining;
+ }
+ cx = bone.worldX;
+ cy = bone.worldY;
+ break;
+ case Physics.Pose:
+ if (x) bone.worldX += xOffset * mix * data.x;
+ if (y) bone.worldY += yOffset * mix * data.y;
+ break;
+ }
+
+ if (rotateOrShearX) {
+ float o = rotateOffset * mix, s, c, a;
+ if (data.shearX > 0) {
+ float r = 0;
+ if (data.rotate > 0) {
+ r = o * data.rotate;
+ s = (float)Math.Sin(r);
+ c = (float)Math.Cos(r);
+ a = bone.b;
+ bone.b = c * a - s * bone.d;
+ bone.d = s * a + c * bone.d;
+ }
+ r += o * data.shearX;
+ s = (float)Math.Sin(r);
+ c = (float)Math.Cos(r);
+ a = bone.a;
+ bone.a = c * a - s * bone.c;
+ bone.c = s * a + c * bone.c;
+ } else {
+ o *= data.rotate;
+ s = (float)Math.Sin(o);
+ c = (float)Math.Cos(o);
+ a = bone.a;
+ bone.a = c * a - s * bone.c;
+ bone.c = s * a + c * bone.c;
+ a = bone.b;
+ bone.b = c * a - s * bone.d;
+ bone.d = s * a + c * bone.d;
+ }
+ }
+ if (scaleX) {
+ float s = 1 + scaleOffset * mix * data.scaleX;
+ bone.a *= s;
+ bone.c *= s;
+ }
+ if (physics != Physics.Pose) {
+ tx = l * bone.a;
+ ty = l * bone.c;
+ }
+ bone.UpdateAppliedTransform();
}
- /// The bones that will be modified by this physics constraint.
- public ExposedList Bones { get { return bones; } }
+ /// The bone constrained by this physics constraint.
+ public Bone Bone { get {return bone;} set { bone = value; } }
+ public float Inertia { get { return inertia; } set { inertia = value; } }
+ public float Strength { get { return strength; } set { strength = value; } }
+ public float Damping { get { return damping; } set { damping = value; } }
+ public float MassInverse { get { return massInverse; } set { massInverse = value; } }
+ public float Wind { get { return wind; } set { wind = value; } }
+ public float Gravity { get { return gravity; } set { gravity = value; } }
/// A percentage (0-1) that controls the mix between the constrained and unconstrained poses.
public float Mix { get { return mix; } set { mix = value; } }
- public float Friction { get { return friction; } set { friction = value; } }
- public float Gravity { get { return gravity; } set { gravity = value; } }
- public float Wind { get { return wind; } set { wind = value; } }
- public float Stiffness { get { return stiffness; } set { stiffness = value; } }
- public float Damping { get { return damping; } set { damping = value; } }
- public bool Rope { get { return rope; } set { rope = value; } }
- public bool Stretch { get { return stretch; } set { stretch = value; } }
public bool Active { get { return active; } }
+
+
+ /** The physics constraint's setup pose data. */
+ public PhysicsConstraintData getData () {
+ return data;
+ }
+
/// The physics constraint's setup pose data.
public PhysicsConstraintData Data { get { return data; } }
diff --git a/spine-csharp/src/PhysicsConstraintData.cs b/spine-csharp/src/PhysicsConstraintData.cs
index 59df824586..3ff48c754d 100644
--- a/spine-csharp/src/PhysicsConstraintData.cs
+++ b/spine-csharp/src/PhysicsConstraintData.cs
@@ -1,16 +1,16 @@
/******************************************************************************
* Spine Runtimes License Agreement
- * Last updated September 24, 2021. Replaces all prior versions.
+ * Last updated July 28, 2023. Replaces all prior versions.
*
- * Copyright (c) 2013-2021, Esoteric Software LLC
+ * Copyright (c) 2013-2023, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
- * Otherwise, it is permitted to integrate the Spine Runtimes into software
- * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software or
+ * otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
@@ -23,12 +23,10 @@
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) 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
- * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
+ * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
-using System;
-
namespace Spine {
///
/// Stores the setup pose for a .
@@ -36,24 +34,37 @@ namespace Spine {
/// See Physics constraints in the Spine User Guide.
///
public class PhysicsConstraintData : ConstraintData {
- internal ExposedList bones = new ExposedList();
- internal float mix, friction, gravity, wind, stiffness, damping;
- internal bool rope, stretch;
+ internal BoneData bone;
+ internal float x, y, rotate, scaleX, shearX;
+ internal float step, inertia, strength, damping, massInverse, wind, gravity, mix;
+ internal bool inertiaGlobal, strengthGlobal, dampingGlobal, massGlobal, windGlobal, gravityGlobal, mixGlobal;
public PhysicsConstraintData (string name) : base(name) {
}
- /// The bones that are constrained by this physics constraint.
- public ExposedList Bones { get { return bones; } }
+ /// The bone constrained by this physics constraint.
+ public BoneData Bone { get { return bone; } }
+ public float Step { get { return step; } set { step = value; } }
+ public float X { get { return x; } set { x = value; } }
+ public float Y { get { return y; } set { y = value; } }
+ public float Rotate { get { return rotate; } set { rotate = value; } }
+ public float ScaleX { get { return scaleX; } set { scaleX = value; } }
+ public float ShearX { get { return shearX; } set { shearX = value; } }
+ public float Inertia { get { return inertia; } set { inertia = value; } }
+ public float Strength { get { return strength; } set { strength = value; } }
+ public float Damping { get { return damping; } set { damping = value; } }
+ public float MassInverse { get { return massInverse; } set { massInverse = value; } }
+ public float Wind { get { return wind; } set { wind = value; } }
+ public float Gravity { get { return gravity; } set { gravity = value; } }
/// A percentage (0-1) that controls the mix between the constrained and unconstrained poses.
public float Mix { get { return mix; } set { mix = value; } }
- public float Friction { get { return friction; } set { friction = value; } }
- public float Gravity { get { return gravity; } set { gravity = value; } }
- public float Wind { get { return wind; } set { wind = value; } }
- public float Stiffness { get { return stiffness; } set { stiffness = value; } }
- public float Damping { get { return damping; } set { damping = value; } }
- public bool Rope { get { return rope; } set { rope = value; } }
- public bool Stretch { get { return stretch; } set { stretch = value; } }
+ public bool InertiaGlobal { get { return inertiaGlobal; } set { inertiaGlobal = value; } }
+ public bool StrengthGlobal { get { return strengthGlobal; } set { strengthGlobal = value; } }
+ public bool DampingGlobal { get { return dampingGlobal; } set { dampingGlobal = value; } }
+ public bool MassGlobal { get { return massGlobal; } set { massGlobal = value; } }
+ public bool WindGlobal { get { return windGlobal; } set { windGlobal = value; } }
+ public bool GravityGlobal { get { return gravityGlobal; } set { gravityGlobal = value; } }
+ public bool MixGlobal { get { return mixGlobal; } set { mixGlobal = value; } }
}
}
diff --git a/spine-csharp/src/Skeleton.cs b/spine-csharp/src/Skeleton.cs
index 8ccf68f84d..56375f9206 100644
--- a/spine-csharp/src/Skeleton.cs
+++ b/spine-csharp/src/Skeleton.cs
@@ -42,8 +42,9 @@ public class Skeleton {
internal ExposedList updateCache = new ExposedList();
internal Skin skin;
internal float r = 1, g = 1, b = 1, a = 1;
- internal float scaleX = 1, scaleY = 1;
internal float x, y;
+ internal float scaleX = 1, scaleY = 1;
+ internal float time;
/// The skeleton's setup pose data.
public SkeletonData Data { get { return data; } }
@@ -99,6 +100,9 @@ public Skin Skin {
[Obsolete("Use ScaleY instead. FlipY is when ScaleY is negative.")]
public bool FlipY { get { return scaleY < 0; } set { scaleY = value ? -1f : 1f; } }
+ /// Returns the skeleton's time. This is used for time-based manipulations, such as .
+ ///
+ public float Time { get { return time; } set { time = value; } }
/// Returns the root bone, or null if the skeleton has no bones.
public Bone RootBone {
@@ -183,27 +187,30 @@ public Skeleton (Skeleton skeleton) {
ikConstraints = new ExposedList(skeleton.ikConstraints.Count);
foreach (IkConstraint ikConstraint in skeleton.ikConstraints)
- ikConstraints.Add(new IkConstraint(ikConstraint, this));
+ ikConstraints.Add(new IkConstraint(ikConstraint));
transformConstraints = new ExposedList(skeleton.transformConstraints.Count);
foreach (TransformConstraint transformConstraint in skeleton.transformConstraints)
- transformConstraints.Add(new TransformConstraint(transformConstraint, this));
+ transformConstraints.Add(new TransformConstraint(transformConstraint));
pathConstraints = new ExposedList(skeleton.pathConstraints.Count);
foreach (PathConstraint pathConstraint in skeleton.pathConstraints)
- pathConstraints.Add(new PathConstraint(pathConstraint, this));
+ pathConstraints.Add(new PathConstraint(pathConstraint));
physicsConstraints = new ExposedList(skeleton.physicsConstraints.Count);
foreach (PhysicsConstraint physicsConstraint in skeleton.physicsConstraints)
- physicsConstraints.Add(new PhysicsConstraint(physicsConstraint, this));
+ physicsConstraints.Add(new PhysicsConstraint(physicsConstraint));
skin = skeleton.skin;
r = skeleton.r;
g = skeleton.g;
b = skeleton.b;
a = skeleton.a;
+ x = skeleton.x;
+ y = skeleton.y;
scaleX = skeleton.scaleX;
scaleY = skeleton.scaleY;
+ time = skeleton.time;
UpdateCache();
}
@@ -383,17 +390,16 @@ private void SortPhysicsConstraint (PhysicsConstraint constraint) {
constraint.active = !constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data));
if (!constraint.active) return;
- Object[] constrained = constraint.bones.Items;
- int boneCount = constraint.bones.Count;
- for (int i = 0; i < boneCount; i++)
- SortBone((Bone)constrained[i]);
+ Bone bone = constraint.bone;
+ constraint.active = bone.active;
+ if (!constraint.active) return;
+
+ SortBone(bone);
updateCache.Add(constraint);
- for (int i = 0; i < boneCount; i++)
- SortReset(((Bone)constrained[i]).children);
- for (int i = 0; i < boneCount; i++)
- ((Bone)constrained[i]).sorted = true;
+ SortReset(bone.children);
+ bone.sorted = true;
}
private void SortBone (Bone bone) {
@@ -420,7 +426,7 @@ private static void SortReset (ExposedList bones) {
/// See World transforms in the Spine
/// Runtimes Guide.
///
- public void UpdateWorldTransform () {
+ public void UpdateWorldTransform (Physics physics) {
Bone[] bones = this.bones.Items;
for (int i = 0, n = this.bones.Count; i < n; i++) {
Bone bone = bones[i];
@@ -435,14 +441,14 @@ public void UpdateWorldTransform () {
IUpdatable[] updateCache = this.updateCache.Items;
for (int i = 0, n = this.updateCache.Count; i < n; i++)
- updateCache[i].Update();
+ updateCache[i].Update(physics);
}
///
/// Temporarily sets the root bone as a child of the specified bone, then updates the world transform for each bone and applies
/// all constraints.
///
- public void UpdateWorldTransform (Bone parent) {
+ public void UpdateWorldTransform (Physics physics, Bone parent) {
if (parent == null) throw new ArgumentNullException("parent", "parent cannot be null.");
// Apply the parent bone transform to the root bone. The root bone always inherits scale, rotation and reflection.
@@ -451,11 +457,12 @@ public void UpdateWorldTransform (Bone parent) {
rootBone.worldX = pa * x + pb * y + parent.worldX;
rootBone.worldY = pc * x + pd * y + parent.worldY;
- float rotationY = rootBone.rotation + 90 + rootBone.shearY;
- float la = MathUtils.CosDeg(rootBone.rotation + rootBone.shearX) * rootBone.scaleX;
- float lb = MathUtils.CosDeg(rotationY) * rootBone.scaleY;
- float lc = MathUtils.SinDeg(rootBone.rotation + rootBone.shearX) * rootBone.scaleX;
- float ld = MathUtils.SinDeg(rotationY) * rootBone.scaleY;
+ float rx = (rootBone.rotation + rootBone.shearX) * MathUtils.DegRad;
+ float ry = (rootBone.rotation + 90 + rootBone.shearY) * MathUtils.DegRad;
+ float la = (float)Math.Cos(rx) * rootBone.scaleX;
+ float lb = (float)Math.Cos(ry) * rootBone.scaleY;
+ float lc = (float)Math.Sin(rx) * rootBone.scaleX;
+ float ld = (float)Math.Sin(ry) * rootBone.scaleY;
rootBone.a = (pa * la + pb * lc) * scaleX;
rootBone.b = (pa * lb + pb * ld) * scaleX;
rootBone.c = (pc * la + pd * lc) * scaleY;
@@ -465,10 +472,15 @@ public void UpdateWorldTransform (Bone parent) {
IUpdatable[] updateCache = this.updateCache.Items;
for (int i = 0, n = this.updateCache.Count; i < n; i++) {
IUpdatable updatable = updateCache[i];
- if (updatable != rootBone) updatable.Update();
+ if (updatable != rootBone) updatable.Update(physics);
}
}
+ /// Increments the skeleton's .
+ public void Update (float delta) {
+ time += delta;
+ }
+
/// Sets the bones, constraints, and slots to their setup pose values.
public void SetToSetupPose () {
SetBonesToSetupPose();
@@ -482,52 +494,20 @@ public void SetBonesToSetupPose () {
bones[i].SetToSetupPose();
IkConstraint[] ikConstraints = this.ikConstraints.Items;
- for (int i = 0, n = this.ikConstraints.Count; i < n; i++) {
- IkConstraint constraint = ikConstraints[i];
- IkConstraintData data = constraint.data;
- constraint.mix = data.mix;
- constraint.softness = data.softness;
- constraint.bendDirection = data.bendDirection;
- constraint.compress = data.compress;
- constraint.stretch = data.stretch;
- }
+ for (int i = 0, n = this.ikConstraints.Count; i < n; i++)
+ ikConstraints[i].SetToSetupPose();
TransformConstraint[] transformConstraints = this.transformConstraints.Items;
- for (int i = 0, n = this.transformConstraints.Count; i < n; i++) {
- TransformConstraint constraint = transformConstraints[i];
- TransformConstraintData data = constraint.data;
- constraint.mixRotate = data.mixRotate;
- constraint.mixX = data.mixX;
- constraint.mixY = data.mixY;
- constraint.mixScaleX = data.mixScaleX;
- constraint.mixScaleY = data.mixScaleY;
- constraint.mixShearY = data.mixShearY;
- }
+ for (int i = 0, n = this.transformConstraints.Count; i < n; i++)
+ transformConstraints[i].SetToSetupPose();
PathConstraint[] pathConstraints = this.pathConstraints.Items;
- for (int i = 0, n = this.pathConstraints.Count; i < n; i++) {
- PathConstraint constraint = pathConstraints[i];
- PathConstraintData data = constraint.data;
- constraint.position = data.position;
- constraint.spacing = data.spacing;
- constraint.mixRotate = data.mixRotate;
- constraint.mixX = data.mixX;
- constraint.mixY = data.mixY;
- }
+ for (int i = 0, n = this.pathConstraints.Count; i < n; i++)
+ pathConstraints[i].SetToSetupPose();
PhysicsConstraint[] physicsConstraints = this.physicsConstraints.Items;
- for (int i = 0, n = this.physicsConstraints.Count; i < n; i++) {
- PhysicsConstraint constraint = physicsConstraints[i];
- PhysicsConstraintData data = constraint.data;
- constraint.mix = data.mix;
- constraint.friction = data.friction;
- constraint.gravity = data.gravity;
- constraint.wind = data.wind;
- constraint.stiffness = data.stiffness;
- constraint.damping = data.damping;
- constraint.rope = data.rope;
- constraint.stretch = data.stretch;
- }
+ for (int i = 0, n = this.physicsConstraints.Count; i < n; i++)
+ physicsConstraints[i].SetToSetupPose();
}
public void SetSlotsToSetupPose () {
@@ -743,5 +723,24 @@ public void GetBounds (out float x, out float y, out float width, out float heig
height = maxY - minY;
vertexBuffer = temp;
}
+
+ override public string ToString () {
+ return data.name;
+ }
+
+ /// Determines how physics and other non-deterministic updates are applied.
+ public enum Physics {
+ /// Physics are not updated or applied.
+ None,
+
+ /// Physics are reset to the current pose.
+ Reset,
+
+ /// Physics are updated and the pose from physics is applied.
+ Update,
+
+ /// Physics are not updated but the pose from physics is applied.
+ Pose
+ }
}
}
diff --git a/spine-csharp/src/SkeletonBinary.cs b/spine-csharp/src/SkeletonBinary.cs
index 9ccdb84f05..3068d1267b 100644
--- a/spine-csharp/src/SkeletonBinary.cs
+++ b/spine-csharp/src/SkeletonBinary.cs
@@ -68,10 +68,21 @@ public class SkeletonBinary : SkeletonLoader {
public const int PATH_SPACING = 1;
public const int PATH_MIX = 2;
+ public const int PHYSICS_INERTIA = 0;
+ public const int PHYSICS_STRENGTH = 1;
+ public const int PHYSICS_DAMPING = 2;
+ public const int PHYSICS_MASS = 4;
+ public const int PHYSICS_WIND = 5;
+ public const int PHYSICS_GRAVITY = 6;
+ public const int PHYSICS_MIX = 7;
+ public const int PHYSICS_RESET = 8;
+
public const int CURVE_LINEAR = 0;
public const int CURVE_STEPPED = 1;
public const int CURVE_BEZIER = 2;
+ private readonly List linkedMeshes = new List();
+
public SkeletonBinary (AttachmentLoader attachmentLoader)
: base(attachmentLoader) {
}
@@ -177,7 +188,11 @@ public SkeletonData ReadSkeletonData (Stream file) {
data.Length = input.ReadFloat() * scale;
data.transformMode = TransformModeValues[input.ReadInt(true)];
data.skinRequired = input.ReadBoolean();
- if (nonessential) input.ReadInt(); // Skip bone color.
+ if (nonessential) { // discard non-essential data
+ input.ReadInt(); // Color.rgba8888ToColor(data.color, input.readInt());
+ input.ReadString(); // data.icon = input.readString();
+ input.ReadBoolean(); // data.visible = input.readBoolean();
+ }
bones[i] = data;
}
@@ -203,6 +218,7 @@ public SkeletonData ReadSkeletonData (Stream file) {
slotData.attachmentName = input.ReadStringRef();
slotData.blendMode = (BlendMode)input.ReadInt(true);
+ if (nonessential) input.ReadBoolean(); // if (nonessential) data.visible = input.readBoolean();
slots[i] = slotData;
}
@@ -211,17 +227,18 @@ public SkeletonData ReadSkeletonData (Stream file) {
for (int i = 0, nn; i < n; i++) {
IkConstraintData data = new IkConstraintData(input.ReadString());
data.order = input.ReadInt(true);
- data.skinRequired = input.ReadBoolean();
BoneData[] constraintBones = data.bones.Resize(nn = input.ReadInt(true)).Items;
for (int ii = 0; ii < nn; ii++)
constraintBones[ii] = bones[input.ReadInt(true)];
data.target = bones[input.ReadInt(true)];
data.mix = input.ReadFloat();
data.softness = input.ReadFloat() * scale;
- data.bendDirection = input.ReadSByte();
- data.compress = input.ReadBoolean();
- data.stretch = input.ReadBoolean();
- data.uniform = input.ReadBoolean();
+ int flags = input.Read();
+ data.skinRequired = (flags & 1) != 0;
+ data.bendDirection = (flags & 2) != 0 ? 1 : -1;
+ data.compress = (flags & 4) != 0;
+ data.stretch = (flags & 8) != 0;
+ data.uniform = (flags & 16) != 0;
o[i] = data;
}
@@ -230,13 +247,14 @@ public SkeletonData ReadSkeletonData (Stream file) {
for (int i = 0, nn; i < n; i++) {
TransformConstraintData data = new TransformConstraintData(input.ReadString());
data.order = input.ReadInt(true);
- data.skinRequired = input.ReadBoolean();
BoneData[] constraintBones = data.bones.Resize(nn = input.ReadInt(true)).Items;
for (int ii = 0; ii < nn; ii++)
constraintBones[ii] = bones[input.ReadInt(true)];
data.target = bones[input.ReadInt(true)];
- data.local = input.ReadBoolean();
- data.relative = input.ReadBoolean();
+ int flags = input.Read();
+ data.skinRequired = (flags & 1) != 0;
+ data.local = (flags & 2) != 0;
+ data.relative = (flags & 4) != 0;
data.offsetRotation = input.ReadFloat();
data.offsetX = input.ReadFloat() * scale;
data.offsetY = input.ReadFloat() * scale;
@@ -258,7 +276,7 @@ public SkeletonData ReadSkeletonData (Stream file) {
PathConstraintData data = new PathConstraintData(input.ReadString());
data.order = input.ReadInt(true);
data.skinRequired = input.ReadBoolean();
- Object[] constraintBones = data.bones.Resize(nn = input.ReadInt(true)).Items;
+ BoneData[] constraintBones = data.bones.Resize(nn = input.ReadInt(true)).Items;
for (int ii = 0; ii < nn; ii++)
constraintBones[ii] = bones[input.ReadInt(true)];
data.target = slots[input.ReadInt(true)];
@@ -276,6 +294,38 @@ public SkeletonData ReadSkeletonData (Stream file) {
o[i] = data;
}
+ // Physics constraints.
+ o = skeletonData.physicsConstraints.Resize(n = input.ReadInt(true)).Items;
+ for (int i = 0; i < n; i++) {
+ PhysicsConstraintData data = new PhysicsConstraintData(input.ReadString());
+ data.order = input.ReadInt(true);
+ data.bone = bones[input.ReadInt(true)];
+ int flags = input.Read();
+ data.skinRequired = (flags & 1) != 0;
+ if ((flags & 2) != 0) data.x = input.ReadFloat();
+ if ((flags & 4) != 0) data.y = input.ReadFloat();
+ if ((flags & 8) != 0) data.rotate = input.ReadFloat();
+ if ((flags & 16) != 0) data.scaleX = input.ReadFloat();
+ if ((flags & 32) != 0) data.shearX = input.ReadFloat();
+ data.step = 1f / input.ReadByte();
+ data.inertia = input.ReadFloat();
+ data.strength = input.ReadFloat();
+ data.damping = input.ReadFloat();
+ data.massInverse = input.ReadFloat();
+ data.wind = input.ReadFloat();
+ data.gravity = input.ReadFloat();
+ data.mix = input.ReadFloat();
+ flags = input.Read();
+ if ((flags & 1) != 0) data.inertiaGlobal = true;
+ if ((flags & 2) != 0) data.strengthGlobal = true;
+ if ((flags & 4) != 0) data.dampingGlobal = true;
+ if ((flags & 8) != 0) data.massGlobal = true;
+ if ((flags & 16) != 0) data.windGlobal = true;
+ if ((flags & 32) != 0) data.gravityGlobal = true;
+ if ((flags & 64) != 0) data.mixGlobal = true;
+ o[i] = data;
+ }
+
// Default skin.
Skin defaultSkin = ReadSkin(input, skeletonData, true, nonessential);
if (defaultSkin != null) {
@@ -295,8 +345,7 @@ public SkeletonData ReadSkeletonData (Stream file) {
n = linkedMeshes.Count;
for (int i = 0; i < n; i++) {
LinkedMesh linkedMesh = linkedMeshes[i];
- Skin skin = linkedMesh.skin == null ? skeletonData.DefaultSkin : skeletonData.FindSkin(linkedMesh.skin);
- if (skin == null) throw new Exception("Skin not found: " + linkedMesh.skin);
+ Skin skin = skeletonData.skins.Items[linkedMesh.skinIndex];
Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent);
if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent);
linkedMesh.mesh.TimelineAttachment = linkedMesh.inheritTimelines ? (VertexAttachment)parent : linkedMesh.mesh;
@@ -308,7 +357,7 @@ public SkeletonData ReadSkeletonData (Stream file) {
// Events.
o = skeletonData.events.Resize(n = input.ReadInt(true)).Items;
for (int i = 0; i < n; i++) {
- EventData data = new EventData(input.ReadStringRef());
+ EventData data = new EventData(input.ReadString());
data.Int = input.ReadInt(false);
data.Float = input.ReadFloat();
data.String = input.ReadString();
@@ -339,7 +388,10 @@ private Skin ReadSkin (SkeletonInput input, SkeletonData skeletonData, bool defa
if (slotCount == 0) return null;
skin = new Skin("default");
} else {
- skin = new Skin(input.ReadStringRef());
+ skin = new Skin(input.ReadString());
+
+ if (nonessential) input.ReadInt(); // discard, Color.rgba8888ToColor(skin.color, input.readInt());
+
Object[] bones = skin.bones.Resize(input.ReadInt(true)).Items;
BoneData[] bonesItems = skeletonData.bones.Items;
for (int i = 0, n = skin.bones.Count; i < n; i++)
@@ -354,6 +406,9 @@ private Skin ReadSkin (SkeletonInput input, SkeletonData skeletonData, bool defa
PathConstraintData[] pathConstraintsItems = skeletonData.pathConstraints.Items;
for (int i = 0, n = input.ReadInt(true); i < n; i++)
skin.constraints.Add(pathConstraintsItems[input.ReadInt(true)]);
+ PhysicsConstraintData[] physicsConstraintsItems = skeletonData.physicsConstraints.Items;
+ for (int i = 0, n = input.ReadInt(true); i < n; i++)
+ skin.constraints.Add(physicsConstraintsItems[input.ReadInt(true)]);
skin.constraints.TrimExcess();
slotCount = input.ReadInt(true);
@@ -373,12 +428,14 @@ private Attachment ReadAttachment (SkeletonInput input, SkeletonData skeletonDat
String attachmentName, bool nonessential) {
float scale = this.scale;
- String name = input.ReadStringRef();
- if (name == null) name = attachmentName;
+ int flags = input.ReadByte();
+ string name = (flags & 8) != 0 ? input.ReadStringRef() : attachmentName;
- switch ((AttachmentType)input.ReadByte()) {
+ switch ((AttachmentType)(flags & 0b111)) {
case AttachmentType.Region: {
- String path = input.ReadStringRef();
+ string path = (flags & 16) != 0 ? input.ReadStringRef() : null;
+ uint color = (flags & 32) != 0 ? (uint)input.ReadInt() : 0xffffffff;
+ Sequence sequence = (flags & 64) != 0 ? ReadSequence(input) : null;
float rotation = input.ReadFloat();
float x = input.ReadFloat();
float y = input.ReadFloat();
@@ -386,8 +443,6 @@ private Attachment ReadAttachment (SkeletonInput input, SkeletonData skeletonDat
float scaleY = input.ReadFloat();
float width = input.ReadFloat();
float height = input.ReadFloat();
- int color = input.ReadInt();
- Sequence sequence = ReadSequence(input);
if (path == null) path = name;
RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path, sequence);
@@ -409,36 +464,34 @@ private Attachment ReadAttachment (SkeletonInput input, SkeletonData skeletonDat
return region;
}
case AttachmentType.Boundingbox: {
- int vertexCount = input.ReadInt(true);
- Vertices vertices = ReadVertices(input, vertexCount);
- if (nonessential) input.ReadInt(); //int color = nonessential ? input.ReadInt() : 0; // Avoid unused local warning.
+ Vertices vertices = ReadVertices(input, (flags & 16) != 0);
+ if (nonessential) input.ReadInt(); // discard, int color = nonessential ? input.readInt() : 0;
BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name);
if (box == null) return null;
- box.worldVerticesLength = vertexCount << 1;
+ box.worldVerticesLength = vertices.length;
box.vertices = vertices.vertices;
box.bones = vertices.bones;
// skipped porting: if (nonessential) Color.rgba8888ToColor(box.getColor(), color);
return box;
}
case AttachmentType.Mesh: {
- String path = input.ReadStringRef();
- int color = input.ReadInt();
- int vertexCount = input.ReadInt(true);
- float[] uvs = ReadFloatArray(input, vertexCount << 1, 1);
- int[] triangles = ReadShortArray(input);
- Vertices vertices = ReadVertices(input, vertexCount);
+ string path = (flags & 16) != 0 ? input.ReadStringRef() : name;
+ uint color = (flags & 32) != 0 ? (uint)input.ReadInt() : 0xffffffff;
+ Sequence sequence = (flags & 64) != 0 ? ReadSequence(input) : null;
int hullLength = input.ReadInt(true);
- Sequence sequence = ReadSequence(input);
+ Vertices vertices = ReadVertices(input, (flags & 128) != 0);
+ float[] uvs = ReadFloatArray(input, vertices.length, 1);
+ int[] triangles = ReadShortArray(input, (vertices.length - hullLength - 2) * 3);
+
int[] edges = null;
float width = 0, height = 0;
if (nonessential) {
- edges = ReadShortArray(input);
+ edges = ReadShortArray(input, input.ReadInt(true));
width = input.ReadFloat();
height = input.ReadFloat();
}
- if (path == null) path = name;
MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path, sequence);
if (mesh == null) return null;
mesh.Path = path;
@@ -448,7 +501,7 @@ private Attachment ReadAttachment (SkeletonInput input, SkeletonData skeletonDat
mesh.a = ((color & 0x000000ff)) / 255f;
mesh.bones = vertices.bones;
mesh.vertices = vertices.vertices;
- mesh.WorldVerticesLength = vertexCount << 1;
+ mesh.WorldVerticesLength = vertices.length;
mesh.triangles = triangles;
mesh.regionUVs = uvs;
if (sequence == null) mesh.UpdateRegion();
@@ -462,19 +515,18 @@ private Attachment ReadAttachment (SkeletonInput input, SkeletonData skeletonDat
return mesh;
}
case AttachmentType.Linkedmesh: {
- String path = input.ReadStringRef();
- int color = input.ReadInt();
- String skinName = input.ReadStringRef();
- String parent = input.ReadStringRef();
- bool inheritTimelines = input.ReadBoolean();
- Sequence sequence = ReadSequence(input);
+ String path = (flags & 16) != 0 ? input.ReadStringRef() : name;
+ uint color = (flags & 32) != 0 ? (uint)input.ReadInt() : 0xffffffff;
+ Sequence sequence = (flags & 64) != 0 ? ReadSequence(input) : null;
+ bool inheritTimelines = (flags & 128) != 0;
+ int skinIndex = input.ReadInt(true);
+ string parent = input.ReadStringRef();
float width = 0, height = 0;
if (nonessential) {
width = input.ReadFloat();
height = input.ReadFloat();
}
- if (path == null) path = name;
MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path, sequence);
if (mesh == null) return null;
mesh.Path = path;
@@ -487,15 +539,14 @@ private Attachment ReadAttachment (SkeletonInput input, SkeletonData skeletonDat
mesh.Width = width * scale;
mesh.Height = height * scale;
}
- linkedMeshes.Add(new SkeletonJson.LinkedMesh(mesh, skinName, slotIndex, parent, inheritTimelines));
+ linkedMeshes.Add(new LinkedMesh(mesh, skinIndex, slotIndex, parent, inheritTimelines));
return mesh;
}
case AttachmentType.Path: {
- bool closed = input.ReadBoolean();
- bool constantSpeed = input.ReadBoolean();
- int vertexCount = input.ReadInt(true);
- Vertices vertices = ReadVertices(input, vertexCount);
- float[] lengths = new float[vertexCount / 3];
+ bool closed = (flags & 16) != 0;
+ bool constantSpeed = (flags & 32) != 0;
+ Vertices vertices = ReadVertices(input, (flags & 64) != 0);
+ float[] lengths = new float[vertices.length / 6];
for (int i = 0, n = lengths.Length; i < n; i++)
lengths[i] = input.ReadFloat() * scale;
if (nonessential) input.ReadInt(); //int color = nonessential ? input.ReadInt() : 0;
@@ -504,7 +555,7 @@ private Attachment ReadAttachment (SkeletonInput input, SkeletonData skeletonDat
if (path == null) return null;
path.closed = closed;
path.constantSpeed = constantSpeed;
- path.worldVerticesLength = vertexCount << 1;
+ path.worldVerticesLength = vertices.length;
path.vertices = vertices.vertices;
path.bones = vertices.bones;
path.lengths = lengths;
@@ -527,14 +578,13 @@ private Attachment ReadAttachment (SkeletonInput input, SkeletonData skeletonDat
}
case AttachmentType.Clipping: {
int endSlotIndex = input.ReadInt(true);
- int vertexCount = input.ReadInt(true);
- Vertices vertices = ReadVertices(input, vertexCount);
+ Vertices vertices = ReadVertices(input, (flags & 16) != 0);
if (nonessential) input.ReadInt();
ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name);
if (clip == null) return null;
clip.EndSlot = skeletonData.slots.Items[endSlotIndex];
- clip.worldVerticesLength = vertexCount << 1;
+ clip.worldVerticesLength = vertices.length;
clip.vertices = vertices.vertices;
clip.bones = vertices.bones;
// skipped porting: if (nonessential) Color.rgba8888ToColor(clip.getColor(), color);
@@ -545,7 +595,6 @@ private Attachment ReadAttachment (SkeletonInput input, SkeletonData skeletonDat
}
private Sequence ReadSequence (SkeletonInput input) {
- if (!input.ReadBoolean()) return null;
Sequence sequence = new Sequence(input.ReadInt(true));
sequence.Start = input.ReadInt(true);
sequence.Digits = input.ReadInt(true);
@@ -553,16 +602,17 @@ private Sequence ReadSequence (SkeletonInput input) {
return sequence;
}
- private Vertices ReadVertices (SkeletonInput input, int vertexCount) {
+ private Vertices ReadVertices (SkeletonInput input, bool weighted) {
float scale = this.scale;
- int verticesLength = vertexCount << 1;
+ int vertexCount = input.ReadInt(true);
Vertices vertices = new Vertices();
- if (!input.ReadBoolean()) {
- vertices.vertices = ReadFloatArray(input, verticesLength, scale);
+ vertices.length = vertexCount << 1;
+ if (!weighted) {
+ vertices.vertices = ReadFloatArray(input, vertices.length, scale);
return vertices;
}
- ExposedList weights = new ExposedList(verticesLength * 3 * 3);
- ExposedList bonesArray = new ExposedList(verticesLength * 3);
+ ExposedList weights = new ExposedList(vertices.length * 3 * 3);
+ ExposedList bonesArray = new ExposedList(vertices.length * 3);
for (int i = 0; i < vertexCount; i++) {
int boneCount = input.ReadInt(true);
bonesArray.Add(boneCount);
@@ -591,11 +641,10 @@ private float[] ReadFloatArray (SkeletonInput input, int n, float scale) {
return array;
}
- private int[] ReadShortArray (SkeletonInput input) {
- int n = input.ReadInt(true);
+ private int[] ReadShortArray (SkeletonInput input, int n) {
int[] array = new int[n];
for (int i = 0; i < n; i++)
- array[i] = (input.ReadByte() << 8) | input.ReadByte();
+ array[i] = input.ReadInt(true);
return array;
}
@@ -783,34 +832,34 @@ private Animation ReadAnimation (String name, SkeletonInput input, SkeletonData
int type = input.ReadByte(), frameCount = input.ReadInt(true), bezierCount = input.ReadInt(true);
switch (type) {
case BONE_ROTATE:
- timelines.Add(ReadTimeline(input, new RotateTimeline(frameCount, bezierCount, boneIndex), 1));
+ ReadTimeline(input, timelines, new RotateTimeline(frameCount, bezierCount, boneIndex), 1);
break;
case BONE_TRANSLATE:
- timelines.Add(ReadTimeline(input, new TranslateTimeline(frameCount, bezierCount, boneIndex), scale));
+ ReadTimeline(input, timelines, new TranslateTimeline(frameCount, bezierCount, boneIndex), scale);
break;
case BONE_TRANSLATEX:
- timelines.Add(ReadTimeline(input, new TranslateXTimeline(frameCount, bezierCount, boneIndex), scale));
+ ReadTimeline(input, timelines, new TranslateXTimeline(frameCount, bezierCount, boneIndex), scale);
break;
case BONE_TRANSLATEY:
- timelines.Add(ReadTimeline(input, new TranslateYTimeline(frameCount, bezierCount, boneIndex), scale));
+ ReadTimeline(input, timelines, new TranslateYTimeline(frameCount, bezierCount, boneIndex), scale);
break;
case BONE_SCALE:
- timelines.Add(ReadTimeline(input, new ScaleTimeline(frameCount, bezierCount, boneIndex), 1));
+ ReadTimeline(input, timelines, new ScaleTimeline(frameCount, bezierCount, boneIndex), 1);
break;
case BONE_SCALEX:
- timelines.Add(ReadTimeline(input, new ScaleXTimeline(frameCount, bezierCount, boneIndex), 1));
+ ReadTimeline(input, timelines, new ScaleXTimeline(frameCount, bezierCount, boneIndex), 1);
break;
case BONE_SCALEY:
- timelines.Add(ReadTimeline(input, new ScaleYTimeline(frameCount, bezierCount, boneIndex), 1));
+ ReadTimeline(input, timelines, new ScaleYTimeline(frameCount, bezierCount, boneIndex), 1);
break;
case BONE_SHEAR:
- timelines.Add(ReadTimeline(input, new ShearTimeline(frameCount, bezierCount, boneIndex), 1));
+ ReadTimeline(input, timelines, new ShearTimeline(frameCount, bezierCount, boneIndex), 1);
break;
case BONE_SHEARX:
- timelines.Add(ReadTimeline(input, new ShearXTimeline(frameCount, bezierCount, boneIndex), 1));
+ ReadTimeline(input, timelines, new ShearXTimeline(frameCount, bezierCount, boneIndex), 1);
break;
case BONE_SHEARY:
- timelines.Add(ReadTimeline(input, new ShearYTimeline(frameCount, bezierCount, boneIndex), 1));
+ ReadTimeline(input, timelines, new ShearYTimeline(frameCount, bezierCount, boneIndex), 1);
break;
}
}
@@ -822,7 +871,8 @@ private Animation ReadAnimation (String name, SkeletonInput input, SkeletonData
IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount, input.ReadInt(true), index);
float time = input.ReadFloat(), mix = input.ReadFloat(), softness = input.ReadFloat() * scale;
for (int frame = 0, bezier = 0; ; frame++) {
- timeline.SetFrame(frame, time, mix, softness, input.ReadSByte(), input.ReadBoolean(), input.ReadBoolean());
+ int flags = input.Read();
+ timeline.SetFrame(frame, time, mix, softness, input.ReadByte(), (flags & 1) != 0, (flags & 2) != 0);
if (frame == frameLast) break;
float time2 = input.ReadFloat(), mix2 = input.ReadFloat(), softness2 = input.ReadFloat() * scale;
switch (input.ReadByte()) {
@@ -881,20 +931,18 @@ private Animation ReadAnimation (String name, SkeletonInput input, SkeletonData
int index = input.ReadInt(true);
PathConstraintData data = skeletonData.pathConstraints.Items[index];
for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) {
- switch (input.ReadByte()) {
+ int type = input.ReadByte(), frameCount = input.ReadInt(true), bezierCount = input.ReadInt(true);
+ switch (type) {
case PATH_POSITION:
- timelines
- .Add(ReadTimeline(input, new PathConstraintPositionTimeline(input.ReadInt(true), input.ReadInt(true), index),
- data.positionMode == PositionMode.Fixed ? scale : 1));
+ ReadTimeline(input, timelines, new PathConstraintPositionTimeline(frameCount, bezierCount, index),
+ data.positionMode == PositionMode.Fixed ? scale : 1);
break;
case PATH_SPACING:
- timelines
- .Add(ReadTimeline(input, new PathConstraintSpacingTimeline(input.ReadInt(true), input.ReadInt(true), index),
- data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed ? scale : 1));
+ ReadTimeline(input, timelines, new PathConstraintSpacingTimeline(frameCount, bezierCount, index),
+ data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed ? scale : 1);
break;
case PATH_MIX:
- PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(input.ReadInt(true), input.ReadInt(true),
- index);
+ PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(frameCount, bezierCount, index);
float time = input.ReadFloat(), mixRotate = input.ReadFloat(), mixX = input.ReadFloat(), mixY = input.ReadFloat();
for (int frame = 0, bezier = 0, frameLast = timeline.FrameCount - 1; ; frame++) {
timeline.SetFrame(frame, time, mixRotate, mixX, mixY);
@@ -922,6 +970,45 @@ private Animation ReadAnimation (String name, SkeletonInput input, SkeletonData
}
}
+ // Physics timelines.
+ for (int i = 0, n = input.ReadInt(true); i < n; i++) {
+ int index = input.ReadInt(true) - 1;
+ for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) {
+ int type = input.ReadByte(), frameCount = input.ReadInt(true);
+ if (type == PHYSICS_RESET) {
+ PhysicsConstraintResetTimeline timeline = new PhysicsConstraintResetTimeline(frameCount, index);
+ for (int frame = 0; frame < frameCount; frame++)
+ timeline.SetFrame(frame, input.ReadFloat());
+ timelines.Add(timeline);
+ continue;
+ }
+ int bezierCount = input.ReadInt(true);
+ switch (type) {
+ case PHYSICS_INERTIA:
+ ReadTimeline(input, timelines, new PhysicsConstraintInertiaTimeline(frameCount, bezierCount, index), 1);
+ break;
+ case PHYSICS_STRENGTH:
+ ReadTimeline(input, timelines, new PhysicsConstraintStrengthTimeline(frameCount, bezierCount, index), 1);
+ break;
+ case PHYSICS_DAMPING:
+ ReadTimeline(input, timelines, new PhysicsConstraintDampingTimeline(frameCount, bezierCount, index), 1);
+ break;
+ case PHYSICS_MASS:
+ ReadTimeline(input, timelines, new PhysicsConstraintMassTimeline(frameCount, bezierCount, index), 1);
+ break;
+ case PHYSICS_WIND:
+ ReadTimeline(input, timelines, new PhysicsConstraintWindTimeline(frameCount, bezierCount, index), 1);
+ break;
+ case PHYSICS_GRAVITY:
+ ReadTimeline(input, timelines, new PhysicsConstraintGravityTimeline(frameCount, bezierCount, index), 1);
+ break;
+ case PHYSICS_MIX:
+ ReadTimeline(input, timelines, new PhysicsConstraintMixTimeline(frameCount, bezierCount, index), 1);
+ break;
+ }
+ }
+ }
+
// Attachment timelines.
for (int i = 0, n = input.ReadInt(true); i < n; i++) {
Skin skin = skeletonData.skins.Items[input.ReadInt(true)];
@@ -1038,7 +1125,8 @@ private Animation ReadAnimation (String name, SkeletonInput input, SkeletonData
Event e = new Event(time, eventData);
e.intValue = input.ReadInt(false);
e.floatValue = input.ReadFloat();
- e.stringValue = input.ReadBoolean() ? input.ReadString() : eventData.String;
+ e.stringValue = input.ReadString();
+ if (e.stringValue == null) e.stringValue = eventData.String;
if (e.Data.AudioPath != null) {
e.volume = input.ReadFloat();
e.balance = input.ReadFloat();
@@ -1056,7 +1144,7 @@ private Animation ReadAnimation (String name, SkeletonInput input, SkeletonData
}
/// Throws IOException when a read operation fails.
- private Timeline ReadTimeline (SkeletonInput input, CurveTimeline1 timeline, float scale) {
+ private void ReadTimeline (SkeletonInput input, ExposedList timelines, CurveTimeline1 timeline, float scale) {
float time = input.ReadFloat(), value = input.ReadFloat() * scale;
for (int frame = 0, bezier = 0, frameLast = timeline.FrameCount - 1; ; frame++) {
timeline.SetFrame(frame, time, value);
@@ -1073,11 +1161,11 @@ private Timeline ReadTimeline (SkeletonInput input, CurveTimeline1 timeline, flo
time = time2;
value = value2;
}
- return timeline;
+ timelines.Add(timeline);
}
/// Throws IOException when a read operation fails.
- private Timeline ReadTimeline (SkeletonInput input, CurveTimeline2 timeline, float scale) {
+ private void ReadTimeline (SkeletonInput input, ExposedList timelines, CurveTimeline2 timeline, float scale) {
float time = input.ReadFloat(), value1 = input.ReadFloat() * scale, value2 = input.ReadFloat() * scale;
for (int frame = 0, bezier = 0, frameLast = timeline.FrameCount - 1; ; frame++) {
timeline.SetFrame(frame, time, value1, value2);
@@ -1096,7 +1184,7 @@ private Timeline ReadTimeline (SkeletonInput input, CurveTimeline2 timeline, flo
value1 = nvalue1;
value2 = nvalue2;
}
- return timeline;
+ timelines.Add(timeline);
}
/// Throws IOException when a read operation fails.
@@ -1107,6 +1195,7 @@ void SetBezier (SkeletonInput input, CurveTimeline timeline, int bezier, int fra
}
internal class Vertices {
+ public int length;
public int[] bones;
public float[] vertices;
}
@@ -1202,7 +1291,7 @@ public string ReadString () {
return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount);
}
- ///May be null.
+ /// May be null.
public String ReadStringRef () {
int index = ReadInt(true);
return index == 0 ? null : strings[index - 1];
@@ -1257,5 +1346,20 @@ public string GetVersionStringOld3X () {
throw new ArgumentException("Stream does not contain valid binary Skeleton Data.");
}
}
+
+ private class LinkedMesh {
+ internal string parent;
+ internal int skinIndex, slotIndex;
+ internal MeshAttachment mesh;
+ internal bool inheritTimelines;
+
+ public LinkedMesh (MeshAttachment mesh, int skinIndex, int slotIndex, string parent, bool inheritTimelines) {
+ this.mesh = mesh;
+ this.skinIndex = skinIndex;
+ this.slotIndex = slotIndex;
+ this.parent = parent;
+ this.inheritTimelines = inheritTimelines;
+ }
+ }
}
}
diff --git a/spine-csharp/src/SkeletonData.cs b/spine-csharp/src/SkeletonData.cs
index fd1df91133..774b9ae41e 100644
--- a/spine-csharp/src/SkeletonData.cs
+++ b/spine-csharp/src/SkeletonData.cs
@@ -51,8 +51,8 @@ public class SkeletonData {
internal float fps;
internal string imagesPath, audioPath;
- ///The skeleton's name, which by default is the name of the skeleton data file when possible, or null when a name hasn't been
- ///set.
+ /// The skeleton's name, which by default is the name of the skeleton data file when possible, or null when a name hasn't been
+ /// set.
public string Name { get { return name; } set { name = value; } }
/// The skeleton's bones, sorted parent first. The root bone is always the first bone.
@@ -90,8 +90,8 @@ public class SkeletonData {
/// The Spine version used to export this data, or null.
public string Version { get { return version; } set { version = value; } }
- ///The skeleton data hash. This value will change if any of the skeleton data has changed.
- ///May be null.
+ /// The skeleton data hash. This value will change if any of the skeleton data has changed.
+ /// May be null.
public string Hash { get { return hash; } set { hash = value; } }
public string ImagesPath { get { return imagesPath; } set { imagesPath = value; } }
@@ -217,7 +217,7 @@ public PathConstraintData FindPathConstraint (string constraintName) {
/// May be null.
public PhysicsConstraintData FindPhysicsConstraint (String constraintName) {
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
- Object[] physicsConstraints = this.physicsConstraints.Items;
+ PhysicsConstraintData[] physicsConstraints = this.physicsConstraints.Items;
for (int i = 0, n = this.physicsConstraints.Count; i < n; i++) {
PhysicsConstraintData constraint = (PhysicsConstraintData)physicsConstraints[i];
if (constraint.name.Equals(constraintName)) return constraint;
diff --git a/spine-csharp/src/SkeletonJson.cs b/spine-csharp/src/SkeletonJson.cs
index 7f65c7b568..d91f6b94fe 100644
--- a/spine-csharp/src/SkeletonJson.cs
+++ b/spine-csharp/src/SkeletonJson.cs
@@ -52,6 +52,7 @@ namespace Spine {
/// Runtimes Guide.
///
public class SkeletonJson : SkeletonLoader {
+ private readonly List linkedMeshes = new List();
public SkeletonJson (AttachmentLoader attachmentLoader)
: base(attachmentLoader) {
@@ -277,6 +278,42 @@ public SkeletonData ReadSkeletonData (TextReader reader) {
}
}
+ // Physics constraints.
+ if (root.ContainsKey("physics")) {
+ foreach (Dictionary constraintMap in (List