From fe53638f6947a7626bbf35bbb441b77e2aae17b9 Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Fri, 17 Nov 2023 16:41:44 +0100 Subject: [PATCH] [csharp] Ported 4.2-beta physics. --- CHANGELOG.md | 16 + spine-csharp/src/Animation.cs | 751 ++++++++++-------- spine-csharp/src/AnimationState.cs | 37 +- spine-csharp/src/Attachments/Attachment.cs | 2 +- .../src/Attachments/MeshAttachment.cs | 2 +- .../src/Attachments/PointAttachment.cs | 12 +- .../src/Attachments/RegionAttachment.cs | 13 +- .../src/Attachments/VertexAttachment.cs | 2 +- spine-csharp/src/Bone.cs | 164 ++-- spine-csharp/src/BoneData.cs | 6 +- spine-csharp/src/ConstraintData.cs | 10 +- spine-csharp/src/IUpdatable.cs | 13 +- spine-csharp/src/IkConstraint.cs | 42 +- spine-csharp/src/MathUtils.cs | 7 + spine-csharp/src/PathConstraint.cs | 32 +- spine-csharp/src/PhysicsConstraint.cs | 269 ++++++- spine-csharp/src/PhysicsConstraintData.cs | 51 +- spine-csharp/src/Skeleton.cs | 123 ++- spine-csharp/src/SkeletonBinary.cs | 278 +++++-- spine-csharp/src/SkeletonData.cs | 10 +- spine-csharp/src/SkeletonJson.cs | 134 +++- spine-csharp/src/SkeletonLoader.cs | 17 - spine-csharp/src/Skin.cs | 8 +- spine-csharp/src/TransformConstraint.cs | 27 +- spine-csharp/src/package.json | 2 +- .../TwoByTwoTransformEffectExample.cs | 4 + 26 files changed, 1299 insertions(+), 733 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5db595e4..8e694618f 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 24bc1ad82..7642be9f5 100644 --- a/spine-csharp/src/Animation.cs +++ b/spine-csharp/src/Animation.cs @@ -175,13 +175,15 @@ namespace Spine { 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 @@ namespace Spine { } /// The number of frames for this timeline. - public int FrameCount { + public virtual int FrameCount { get { return frames.Length / FrameEntries; } } @@ -433,6 +435,97 @@ namespace Spine { } 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { } 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { } 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 @@ namespace Spine { 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 @@ namespace Spine { } } - /// 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { } } - /// 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 @@ namespace Spine { 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 @@ namespace Spine { /// 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 @@ namespace Spine { /// is applied. public int PathConstraintIndex { get { - return pathConstraintIndex; + return constraintIndex; } } @@ -2534,11 +2348,11 @@ namespace Spine { 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 @@ namespace Spine { } } + /// 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 @@ namespace Spine { 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 @@ namespace Spine { 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 3b5354091..337d00f2a 100644 --- a/spine-csharp/src/AnimationState.cs +++ b/spine-csharp/src/AnimationState.cs @@ -246,6 +246,7 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { // 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 @@ namespace Spine { /// 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 @@ namespace Spine { /// 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 @@ namespace Spine { /// /// 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 @@ namespace Spine { /// 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 @@ namespace Spine { /// 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 @@ namespace Spine { /// 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 @@ namespace Spine { /// /// 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 af87ce5fe..0b9b10859 100644 --- a/spine-csharp/src/Attachments/Attachment.cs +++ b/spine-csharp/src/Attachments/Attachment.cs @@ -50,7 +50,7 @@ namespace Spine { 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 9558010be..430eb1867 100644 --- a/spine-csharp/src/Attachments/MeshAttachment.cs +++ b/spine-csharp/src/Attachments/MeshAttachment.cs @@ -197,7 +197,7 @@ namespace Spine { 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 f2db05778..ee902df54 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 @@ namespace Spine { : base(name) { } - /** Copy constructor. */ + /// Copy constructor. protected PointAttachment (PointAttachment other) : base(other) { x = other.x; @@ -58,10 +60,10 @@ namespace Spine { } 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 e8e899933..37bb12fc1 100644 --- a/spine-csharp/src/Attachments/RegionAttachment.cs +++ b/spine-csharp/src/Attachments/RegionAttachment.cs @@ -106,8 +106,7 @@ namespace Spine { 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 @@ namespace Spine { 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 3b294bb16..1da942b04 100644 --- a/spine-csharp/src/Attachments/VertexAttachment.cs +++ b/spine-csharp/src/Attachments/VertexAttachment.cs @@ -47,7 +47,7 @@ namespace Spine { 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 69002dc8f..f35fb0cd2 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { } /// 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { } 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { } 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { 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 594f27163..203643c0b 100644 --- a/spine-csharp/src/BoneData.cs +++ b/spine-csharp/src/BoneData.cs @@ -56,7 +56,7 @@ namespace Spine { /// 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 @@ namespace Spine { /// 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 374ba6730..9242358f5 100644 --- a/spine-csharp/src/ConstraintData.cs +++ b/spine-csharp/src/ConstraintData.cs @@ -45,13 +45,13 @@ namespace Spine { /// 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 4242fb4fa..80164db6f 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 d9df545f8..151db3c90 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { } } - 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 @@ namespace Spine { 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 016045ede..1ccc947b2 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 @@ namespace Spine { 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 e126676ef..6a8c9b775 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 @@ namespace Spine { 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 @@ namespace Spine { } /// 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 @@ namespace Spine { 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 @@ namespace Spine { 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 28704f813..a8de40b32 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 59df82458..3ff48c754 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 8ccf68f84..56375f920 100644 --- a/spine-csharp/src/Skeleton.cs +++ b/spine-csharp/src/Skeleton.cs @@ -42,8 +42,9 @@ namespace Spine { 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 @@ namespace Spine { [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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { /// 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { 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 9ccdb84f0..3068d1267 100644 --- a/spine-csharp/src/SkeletonBinary.cs +++ b/spine-csharp/src/SkeletonBinary.cs @@ -68,10 +68,21 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { // 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { } 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 @@ namespace Spine { } 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { 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 @@ namespace Spine { } } + // 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 @@ namespace Spine { 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 @@ namespace Spine { } /// 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 @@ namespace Spine { 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 @@ namespace Spine { value1 = nvalue1; value2 = nvalue2; } - return timeline; + timelines.Add(timeline); } /// Throws IOException when a read operation fails. @@ -1107,6 +1195,7 @@ namespace Spine { } internal class Vertices { + public int length; public int[] bones; public float[] vertices; } @@ -1202,7 +1291,7 @@ namespace Spine { 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 @@ namespace Spine { 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 fd1df9113..774b9ae41 100644 --- a/spine-csharp/src/SkeletonData.cs +++ b/spine-csharp/src/SkeletonData.cs @@ -51,8 +51,8 @@ namespace Spine { 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 @@ namespace Spine { /// 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 @@ namespace Spine { /// 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 7f65c7b56..d91f6b94f 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 @@ namespace Spine { } } + // Physics constraints. + if (root.ContainsKey("physics")) { + foreach (Dictionary constraintMap in (List)root["physics"]) { + PhysicsConstraintData data = new PhysicsConstraintData((string)constraintMap["name"]); + data.order = GetInt(constraintMap, "order", 0); + data.skinRequired = GetBoolean(constraintMap, "skin", false); + + string boneName = (string)constraintMap["bone"]; + data.bone = skeletonData.FindBone(boneName); + if (data.bone == null) throw new Exception("Physics bone not found: " + boneName); + + data.x = GetFloat(constraintMap, "x", 0); + data.y = GetFloat(constraintMap, "y", 0); + data.rotate = GetFloat(constraintMap, "rotate", 0); + data.scaleX = GetFloat(constraintMap, "scaleX", 0); + data.shearX = GetFloat(constraintMap, "shearX", 0); + data.step = 1f / GetInt(constraintMap, "fps", 60); + data.inertia = GetFloat(constraintMap, "inertia", 1); + data.strength = GetFloat(constraintMap, "strength", 100); + data.damping = GetFloat(constraintMap, "damping", 1); + data.massInverse = 1f / GetFloat(constraintMap, "mass", 1); + data.wind = GetFloat(constraintMap, "wind", 0); + data.gravity = GetFloat(constraintMap, "gravity", 0); + data.mix = GetFloat(constraintMap, "mix", 1); + data.inertiaGlobal = GetBoolean(constraintMap, "inertiaGlobal", false); + data.strengthGlobal = GetBoolean(constraintMap, "strengthGlobal", false); + data.dampingGlobal = GetBoolean(constraintMap, "dampingGlobal", false); + data.massGlobal = GetBoolean(constraintMap, "massGlobal", false); + data.windGlobal = GetBoolean(constraintMap, "windGlobal", false); + data.gravityGlobal = GetBoolean(constraintMap, "gravityGlobal", false); + data.mixGlobal = GetBoolean(constraintMap, "mixGlobal", false); + + skeletonData.physicsConstraints.Add(data); + } + } + // Skins. if (root.ContainsKey("skins")) { foreach (Dictionary skinMap in (List)root["skins"]) { @@ -310,6 +347,13 @@ namespace Spine { skin.constraints.Add(constraint); } } + if (skinMap.ContainsKey("physics")) { + foreach (string entryName in (List)skinMap["physics"]) { + PhysicsConstraintData constraint = skeletonData.FindPhysicsConstraint(entryName); + if (constraint == null) throw new Exception("Skin physics constraint not found: " + entryName); + skin.constraints.Add(constraint); + } + } skin.constraints.TrimExcess(); if (skinMap.ContainsKey("attachments")) { foreach (KeyValuePair slotEntry in (Dictionary)skinMap["attachments"]) { @@ -964,6 +1008,55 @@ namespace Spine { } } + // Physics constraint timelines. + if (map.ContainsKey("physics")) { + foreach (KeyValuePair constraintMap in (Dictionary)map["physics"]) { + int index = -1; + if (!string.IsNullOrEmpty(constraintMap.Key)) { + PhysicsConstraintData constraint = skeletonData.FindPhysicsConstraint(constraintMap.Key); + if (constraint == null) throw new Exception("Physics constraint not found: " + constraintMap.Key); + index = skeletonData.physicsConstraints.IndexOf(constraint); + } + Dictionary timelineMap = (Dictionary)constraintMap.Value; + foreach (KeyValuePair timelineEntry in timelineMap) { + List values = (List)timelineEntry.Value; + List.Enumerator keyMapEnumerator = values.GetEnumerator(); + if (!keyMapEnumerator.MoveNext()) continue; + + int frames = values.Count; + string timelineName = (string)timelineEntry.Key; + if (timelineName == "reset") { + PhysicsConstraintResetTimeline timeline1 = new PhysicsConstraintResetTimeline(frames, index); + int frame = 0; + foreach (Dictionary keyMap in values) { + timeline1.SetFrame(frame++, GetFloat(keyMap, "time", 0)); + } + timelines.Add(timeline1); + continue; + } + + CurveTimeline1 timeline; + if (timelineName == "inertia") + timeline = new PhysicsConstraintInertiaTimeline(frames, frames, index); + else if (timelineName == "strength") + timeline = new PhysicsConstraintStrengthTimeline(frames, frames, index); + else if (timelineName == "damping") + timeline = new PhysicsConstraintDampingTimeline(frames, frames, index); + else if (timelineName == "mass") + timeline = new PhysicsConstraintMassTimeline(frames, frames, index); + else if (timelineName == "wind") + timeline = new PhysicsConstraintWindTimeline(frames, frames, index); + else if (timelineName == "gravity") + timeline = new PhysicsConstraintGravityTimeline(frames, frames, index); + else if (timelineName == "mix") // + timeline = new PhysicsConstraintMixTimeline(frames, frames, index); + else + continue; + timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, 0, 1)); + } + } + } + // Attachment timelines. if (map.ContainsKey("attachments")) { foreach (KeyValuePair attachmentsMap in (Dictionary)map["attachments"]) { @@ -1051,13 +1144,13 @@ namespace Spine { DrawOrderTimeline timeline = new DrawOrderTimeline(values.Count); int slotCount = skeletonData.slots.Count; int frame = 0; - foreach (Dictionary drawOrderMap in values) { + foreach (Dictionary keyMap in values) { int[] drawOrder = null; - if (drawOrderMap.ContainsKey("offsets")) { + if (keyMap.ContainsKey("offsets")) { drawOrder = new int[slotCount]; for (int i = slotCount - 1; i >= 0; i--) drawOrder[i] = -1; - List offsets = (List)drawOrderMap["offsets"]; + List offsets = (List)keyMap["offsets"]; int[] unchanged = new int[slotCount - offsets.Count]; int originalIndex = 0, unchangedIndex = 0; foreach (Dictionary offsetMap in offsets) { @@ -1076,7 +1169,7 @@ namespace Spine { for (int i = slotCount - 1; i >= 0; i--) if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex]; } - timeline.SetFrame(frame, GetFloat(drawOrderMap, "time", 0), drawOrder); + timeline.SetFrame(frame, GetFloat(keyMap, "time", 0), drawOrder); ++frame; } timelines.Add(timeline); @@ -1087,17 +1180,17 @@ namespace Spine { List eventsMap = (List)map["events"]; EventTimeline timeline = new EventTimeline(eventsMap.Count); int frame = 0; - foreach (Dictionary eventMap in eventsMap) { - EventData eventData = skeletonData.FindEvent((string)eventMap["name"]); - if (eventData == null) throw new Exception("Event not found: " + eventMap["name"]); - Event e = new Event(GetFloat(eventMap, "time", 0), eventData) { - intValue = GetInt(eventMap, "int", eventData.Int), - floatValue = GetFloat(eventMap, "float", eventData.Float), - stringValue = GetString(eventMap, "string", eventData.String) + foreach (Dictionary keyMap in eventsMap) { + EventData eventData = skeletonData.FindEvent((string)keyMap["name"]); + if (eventData == null) throw new Exception("Event not found: " + keyMap["name"]); + Event e = new Event(GetFloat(keyMap, "time", 0), eventData) { + intValue = GetInt(keyMap, "int", eventData.Int), + floatValue = GetFloat(keyMap, "float", eventData.Float), + stringValue = GetString(keyMap, "string", eventData.String) }; if (e.data.AudioPath != null) { - e.volume = GetFloat(eventMap, "volume", eventData.Volume); - e.balance = GetFloat(eventMap, "balance", eventData.Balance); + e.volume = GetFloat(keyMap, "volume", eventData.Volume); + e.balance = GetFloat(keyMap, "balance", eventData.Balance); } timeline.SetFrame(frame, e); ++frame; @@ -1236,5 +1329,20 @@ namespace Spine { throw new ArgumentException("Color hexidecimal length must be " + expectedLength + ", recieved: " + hexString, "hexString"); return Convert.ToInt32(hexString.Substring(colorIndex * 2, 2), 16) / (float)255; } + + private class LinkedMesh { + internal string parent, skin; + internal int slotIndex; + internal MeshAttachment mesh; + internal bool inheritTimelines; + + public LinkedMesh (MeshAttachment mesh, string skin, int slotIndex, string parent, bool inheritTimelines) { + this.mesh = mesh; + this.skin = skin; + this.slotIndex = slotIndex; + this.parent = parent; + this.inheritTimelines = inheritTimelines; + } + } } } diff --git a/spine-csharp/src/SkeletonLoader.cs b/spine-csharp/src/SkeletonLoader.cs index 5a96a03ad..cda528179 100644 --- a/spine-csharp/src/SkeletonLoader.cs +++ b/spine-csharp/src/SkeletonLoader.cs @@ -42,7 +42,6 @@ namespace Spine { public abstract class SkeletonLoader { protected readonly AttachmentLoader attachmentLoader; protected float scale = 1; - protected readonly List linkedMeshes = new List(); /// Creates a skeleton loader that loads attachments using an with the specified atlas. /// @@ -72,21 +71,5 @@ namespace Spine { } public abstract SkeletonData ReadSkeletonData (string path); - - protected class LinkedMesh { - internal string parent, skin; - internal int slotIndex; - internal MeshAttachment mesh; - internal bool inheritTimelines; - - public LinkedMesh (MeshAttachment mesh, string skin, int slotIndex, string parent, bool inheritTimelines) { - this.mesh = mesh; - this.skin = skin; - this.slotIndex = slotIndex; - this.parent = parent; - this.inheritTimelines = inheritTimelines; - } - } - } } diff --git a/spine-csharp/src/Skin.cs b/spine-csharp/src/Skin.cs index 250a93be3..eeae906b7 100644 --- a/spine-csharp/src/Skin.cs +++ b/spine-csharp/src/Skin.cs @@ -45,7 +45,7 @@ namespace Spine { internal readonly ExposedList constraints = new ExposedList(); public string Name { get { return name; } } - ///Returns all attachments contained in this skin. + /// Returns all attachments contained in this skin. public ICollection Attachments { get { return attachments.Values; } } public ExposedList Bones { get { return bones; } } public ExposedList Constraints { get { return constraints; } } @@ -62,7 +62,7 @@ namespace Spine { attachments[new SkinKey(slotIndex, name)] = new SkinEntry(slotIndex, name, attachment); } - ///Adds all attachments, bones, and constraints from the specified skin to this skin. + /// Adds all attachments, bones, and constraints from the specified skin to this skin. public void AddSkin (Skin skin) { foreach (BoneData data in skin.bones) if (!bones.Contains(data)) bones.Add(data); @@ -76,7 +76,7 @@ namespace Spine { } } - ///Adds all attachments from the specified skin to this skin. Attachments are deep copied. + /// Adds all attachments from the specified skin to this skin. Attachments are deep copied. public void CopySkin (Skin skin) { foreach (BoneData data in skin.bones) if (!bones.Contains(data)) bones.Add(data); @@ -118,7 +118,7 @@ namespace Spine { } } - ///Clears all attachments, bones, and constraints. + /// Clears all attachments, bones, and constraints. public void Clear () { attachments.Clear(); bones.Clear(); diff --git a/spine-csharp/src/TransformConstraint.cs b/spine-csharp/src/TransformConstraint.cs index 9e4fa2556..8b8418b0f 100644 --- a/spine-csharp/src/TransformConstraint.cs +++ b/spine-csharp/src/TransformConstraint.cs @@ -30,6 +30,8 @@ using System; namespace Spine { + using Physics = Skeleton.Physics; + /// /// /// Stores the current pose for a transform constraint. A transform constraint adjusts the world transform of the constrained @@ -55,6 +57,7 @@ namespace Spine { mixScaleX = data.mixScaleX; mixScaleY = data.mixScaleY; mixShearY = data.mixShearY; + bones = new ExposedList(); foreach (BoneData boneData in data.bones) bones.Add(skeleton.bones.Items[boneData.index]); @@ -63,14 +66,12 @@ namespace Spine { } /// Copy constructor. - public TransformConstraint (TransformConstraint constraint, Skeleton skeleton) { + public TransformConstraint (TransformConstraint 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.Bones.Items[constraint.target.data.index]; + bones.AddRange(constraint.Bones); + target = constraint.target; mixRotate = constraint.mixRotate; mixX = constraint.mixX; mixY = constraint.mixY; @@ -79,7 +80,17 @@ namespace Spine { mixShearY = constraint.mixShearY; } - public void Update () { + public void SetToSetupPose () { + TransformConstraintData data = this.data; + mixRotate = data.mixRotate; + mixX = data.mixX; + mixY = data.mixY; + mixScaleX = data.mixScaleX; + mixScaleY = data.mixScaleY; + mixShearY = data.mixShearY; + } + + public void Update (Physics physics) { if (mixRotate == 0 && mixX == 0 && mixY == 0 && mixScaleX == 0 && mixScaleY == 0 && mixShearY == 0) return; if (data.local) { if (data.relative) @@ -238,7 +249,7 @@ namespace Spine { float rotation = bone.arotation; if (mixRotate != 0) { float r = target.arotation - rotation + data.offsetRotation; - r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; + r -= (float)Math.Ceiling(r / 360 - 0.5f) * 360; rotation += r * mixRotate; } @@ -255,7 +266,7 @@ namespace Spine { float shearY = bone.ashearY; if (mixShearY != 0) { float r = target.ashearY - shearY + data.offsetShearY; - r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; + r -= (float)Math.Ceiling(r / 360 - 0.5f) * 360; shearY += r * mixShearY; } diff --git a/spine-csharp/src/package.json b/spine-csharp/src/package.json index a4400e595..48dbf3569 100644 --- a/spine-csharp/src/package.json +++ b/spine-csharp/src/package.json @@ -2,7 +2,7 @@ "name": "com.esotericsoftware.spine.spine-csharp", "displayName": "spine-csharp Runtime", "description": "This plugin provides the spine-csharp core runtime.", - "version": "4.2.1", + "version": "4.2.2", "unity": "2018.3", "author": { "name": "Esoteric Software", diff --git a/spine-unity/Assets/Spine Examples/Scripts/Sample Components/Sample VertexEffects/TwoByTwoTransformEffectExample.cs b/spine-unity/Assets/Spine Examples/Scripts/Sample Components/Sample VertexEffects/TwoByTwoTransformEffectExample.cs index 339dcfa5c..341af1813 100644 --- a/spine-unity/Assets/Spine Examples/Scripts/Sample Components/Sample VertexEffects/TwoByTwoTransformEffectExample.cs +++ b/spine-unity/Assets/Spine Examples/Scripts/Sample Components/Sample VertexEffects/TwoByTwoTransformEffectExample.cs @@ -98,7 +98,11 @@ public class TwoByTwoTransformEffectExampleEditor : UnityEditor.Editor { Color originalColor = UnityEditor.Handles.color; UnityEditor.Handles.color = color; UnityEditor.Handles.DrawLine(transform.position, transform.TransformPoint(v)); +#if UNITY_2021_3_OR_NEWER + v = transform.InverseTransformPoint(UnityEditor.Handles.FreeMoveHandle(transform.TransformPoint(v), 0.3f, Vector3.zero, UnityEditor.Handles.CubeHandleCap)); +#else v = transform.InverseTransformPoint(UnityEditor.Handles.FreeMoveHandle(transform.TransformPoint(v), Quaternion.identity, 0.3f, Vector3.zero, UnityEditor.Handles.CubeHandleCap)); +#endif UnityEditor.Handles.color = originalColor; } }