mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-04 14:24:53 +08:00
[csharp] Ported 4.2-beta physics.
This commit is contained in:
parent
ad263d48be
commit
fe53638f69
16
CHANGELOG.md
16
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.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -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 {
|
||||
/// </summary>
|
||||
public float TimeScale { get { return timeScale; } set { timeScale = value; } }
|
||||
|
||||
/// <summary>The AnimationStateData to look up mix durations.</summary>
|
||||
/// <summary>The <see cref="AnimationStateData"/> to look up mix durations.</summary>
|
||||
public AnimationStateData Data {
|
||||
get {
|
||||
return data;
|
||||
@ -1037,7 +1039,7 @@ namespace Spine {
|
||||
/// duration.</summary>
|
||||
public bool Loop { get { return loop; } set { loop = value; } }
|
||||
|
||||
///<summary>
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Seconds to postpone playing the animation. When this track entry is the current track entry, <code>Delay</code>
|
||||
/// postpones incrementing the <see cref="TrackEntry.TrackTime"/>. When this track entry is queued, <code>Delay</code> is the time from
|
||||
@ -1096,7 +1098,7 @@ namespace Spine {
|
||||
/// <summary>
|
||||
/// Seconds for the last frame of this animation. Non-looping animations won't play past this time. Looping animations will
|
||||
/// loop back to <see cref="TrackEntry.AnimationStart"/> at this time. Defaults to the animation <see cref="Animation.Duration"/>.
|
||||
///</summary>
|
||||
/// </summary>
|
||||
public float AnimationEnd { get { return animationEnd; } set { animationEnd = value; } }
|
||||
|
||||
/// <summary>
|
||||
@ -1173,7 +1175,7 @@ namespace Spine {
|
||||
/// When the mix percentage (<see cref="TrackEntry.MixTime"/> / <see cref="TrackEntry.MixDuration"/>) is less than the
|
||||
/// <code>AttachmentThreshold</code>, 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.
|
||||
///</summary>
|
||||
/// </summary>
|
||||
public float AttachmentThreshold { get { return attachmentThreshold; } set { attachmentThreshold = value; } }
|
||||
|
||||
/// <summary>
|
||||
@ -1194,6 +1196,12 @@ namespace Spine {
|
||||
/// The animation queued to play before this animation, or null. <code>previous</code> makes up a doubly linked list.</summary>
|
||||
public TrackEntry Previous { get { return previous; } }
|
||||
|
||||
/// <summary>Returns true if this track entry has been applied at least once.</summary>
|
||||
/// <seealso cref="AnimationState.Apply(Skeleton)"/>
|
||||
public bool WasApplied {
|
||||
get { return nextTrackLast != -1; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if at least one loop has been completed.</summary>
|
||||
/// <seealso cref="TrackEntry.Complete"/>
|
||||
@ -1222,6 +1230,17 @@ namespace Spine {
|
||||
/// </para></summary>
|
||||
public float MixDuration { get { return mixDuration; } set { mixDuration = value; } }
|
||||
|
||||
/// <summary>Sets both <see cref="MixDuration"/> and <see cref="Delay"/>.</summary>
|
||||
/// <param name="delay">If > 0, sets <see cref="TrackEntry.Delay"/>. If <= 0, the delay set is the duration of the previous track
|
||||
/// entry minus the specified mix duration plus the specified<code> delay</code> (ie the mix ends at
|
||||
/// (<code>delay</code> = 0) or before (<code>delay</code> < 0) the previous track entry duration). If the previous
|
||||
/// entry is looping, its next loop completion is used instead of its duration.</param>
|
||||
public void SetMixDuration (float mixDuration, float delay) {
|
||||
this.mixDuration = mixDuration;
|
||||
if (previous != null && delay <= 0) delay += previous.TrackComplete - mixDuration;
|
||||
this.delay = delay;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Controls how properties keyed in the animation are mixed with lower tracks. Defaults to <see cref="MixBlend.Replace"/>.
|
||||
@ -1235,12 +1254,12 @@ namespace Spine {
|
||||
|
||||
/// <summary>
|
||||
/// 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, <code>MixingFrom</code> makes up a linked list.</summary>
|
||||
/// mixing is currently occurring. When mixing from multiple animations, <code>MixingFrom</code> makes up a linked list.</summary>
|
||||
public TrackEntry MixingFrom { get { return mixingFrom; } }
|
||||
|
||||
/// <summary>
|
||||
/// 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, <code>MixingTo</code> makes up a linked list.</summary>
|
||||
/// currently occurring. When mixing to multiple animations, <code>MixingTo</code> makes up a linked list.</summary>
|
||||
public TrackEntry MixingTo { get { return mixingTo; } }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -50,7 +50,7 @@ namespace Spine {
|
||||
return Name;
|
||||
}
|
||||
|
||||
///<summary>Returns a copy of the attachment.</summary>
|
||||
/// <summary>Returns a copy of the attachment.</summary>
|
||||
public abstract Attachment Copy ();
|
||||
}
|
||||
}
|
||||
|
||||
@ -197,7 +197,7 @@ namespace Spine {
|
||||
base.ComputeWorldVertices(slot, start, count, worldVertices, offset, stride);
|
||||
}
|
||||
|
||||
///<summary>Returns a new mesh with this mesh set as the <see cref="ParentMesh"/>.
|
||||
/// <summary>Returns a new mesh with this mesh set as the <see cref="ParentMesh"/>.
|
||||
public MeshAttachment NewLinkedMesh () {
|
||||
MeshAttachment mesh = new MeshAttachment(Name);
|
||||
|
||||
|
||||
@ -27,6 +27,8 @@
|
||||
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using System;
|
||||
|
||||
namespace Spine {
|
||||
/// <summary>
|
||||
/// 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. */
|
||||
/// <summary>Copy constructor.</summary>
|
||||
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 () {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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; } }
|
||||
///<summary>Timelines for the timeline attachment are also applied to this attachment.
|
||||
/// <summary>Timelines for the timeline attachment are also applied to this attachment.
|
||||
/// May be null if no attachment-specific timelines should be applied.</summary>
|
||||
public VertexAttachment TimelineAttachment { get { return timelineAttachment; } set { timelineAttachment = value; } }
|
||||
|
||||
|
||||
@ -30,6 +30,8 @@
|
||||
using System;
|
||||
|
||||
namespace Spine {
|
||||
using Physics = Skeleton.Physics;
|
||||
|
||||
/// <summary>
|
||||
/// Stores a bone's current pose.
|
||||
/// <para>
|
||||
@ -57,8 +59,6 @@ namespace Spine {
|
||||
public Skeleton Skeleton { get { return skeleton; } }
|
||||
public Bone Parent { get { return parent; } }
|
||||
public ExposedList<Bone> Children { get { return children; } }
|
||||
/// <summary>Returns false when the bone has not been computed because <see cref="BoneData.SkinRequired"/> is true and the
|
||||
/// <see cref="Skeleton.Skin">active skin</see> does not <see cref="Skin.Bones">contain</see> this bone.</summary>
|
||||
public bool Active { get { return active; } }
|
||||
/// <summary>The local X translation.</summary>
|
||||
public float X { get { return x; } set { x = value; } }
|
||||
@ -113,8 +113,10 @@ namespace Spine {
|
||||
public float WorldX { get { return worldX; } set { worldX = value; } }
|
||||
/// <summary>The world Y position. If changed, <see cref="UpdateAppliedTransform()"/> should be called.</summary>
|
||||
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; } }
|
||||
/// <summary>The world rotation for the X axis, calculated using <see cref="a"/> and <see cref="c"/>.</summary>
|
||||
public float WorldRotationX { get { return MathUtils.Atan2Deg(c, a); } }
|
||||
/// <summary>The world rotation for the Y axis, calculated using <see cref="b"/> and <see cref="d"/>.</summary>
|
||||
public float WorldRotationY { get { return MathUtils.Atan2Deg(d, b); } }
|
||||
|
||||
/// <summary>Returns the magnitide (always positive) of the world scale X.</summary>
|
||||
public float WorldScaleX { get { return (float)Math.Sqrt(a * a + c * c); } }
|
||||
@ -148,7 +150,7 @@ namespace Spine {
|
||||
}
|
||||
|
||||
/// <summary>Computes the world transform using the parent bone and this bone's local applied transform.</summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>Sets this bone's local transform to the setup pose.</summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Transforms a point from world coordinates to the bone's local coordinates.</summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>Transforms a point from the bone's local coordinates to world coordinates.</summary>
|
||||
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;
|
||||
/// <summary>Transforms a point from world coordinates to the parent bone's local coordinates.</summary>
|
||||
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;
|
||||
/// <summary>Transforms a point from the parent bone's coordinates to world coordinates.</summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Transforms a world rotation to a local rotation.</summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>Transforms a local rotation to a world rotation.</summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates the world transform the specified amount.
|
||||
/// <para>
|
||||
/// After changes are made to the world transform, <see cref="UpdateAppliedTransform()"/> should be called and <see cref="Update()"/> will
|
||||
/// need to be called on any child bones, recursively.
|
||||
/// After changes are made to the world transform, <see cref="UpdateAppliedTransform()"/> should be called and
|
||||
/// <see cref="Update(Skeleton.Physics)"/> will need to be called on any child bones, recursively.
|
||||
/// </para></summary>
|
||||
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 () {
|
||||
|
||||
@ -56,7 +56,7 @@ namespace Spine {
|
||||
/// <summary>Local Y translation.</summary>
|
||||
public float Y { get { return y; } set { y = value; } }
|
||||
|
||||
/// <summary>Local rotation.</summary>
|
||||
/// <summary>Local rotation in degrees, counter clockwise.</summary>
|
||||
public float Rotation { get { return rotation; } set { rotation = value; } }
|
||||
|
||||
/// <summary>Local scaleX.</summary>
|
||||
@ -74,8 +74,8 @@ namespace Spine {
|
||||
/// <summary>The transform mode for how parent world transforms affect this bone.</summary>
|
||||
public TransformMode TransformMode { get { return transformMode; } set { transformMode = value; } }
|
||||
|
||||
///<summary>When true, <see cref="Skeleton.UpdateWorldTransform()"/> only updates this bone if the <see cref="Skeleton.Skin"/> contains this
|
||||
/// bone.</summary>
|
||||
/// <summary>When true, <see cref="Skeleton.UpdateWorldTransform(Skeleton.Physics)"/> only updates this bone if the <see cref="Skeleton.Skin"/> contains
|
||||
/// this bone.</summary>
|
||||
/// <seealso cref="Skin.Bones"/>
|
||||
public bool SkinRequired { get { return skinRequired; } set { skinRequired = value; } }
|
||||
|
||||
|
||||
@ -45,13 +45,13 @@ namespace Spine {
|
||||
/// <summary> The constraint's name, which is unique across all constraints in the skeleton of the same type.</summary>
|
||||
public string Name { get { return name; } }
|
||||
|
||||
///<summary>The ordinal of this constraint for the order a skeleton's constraints will be applied by
|
||||
/// <see cref="Skeleton.UpdateWorldTransform()"/>.</summary>
|
||||
/// <summary>The ordinal of this constraint for the order a skeleton's constraints will be applied by
|
||||
/// <see cref="Skeleton.UpdateWorldTransform(Skeleton.Physics)"/>.</summary>
|
||||
public int Order { get { return order; } set { order = value; } }
|
||||
|
||||
///<summary>When true, <see cref="Skeleton.UpdateWorldTransform()"/> only updates this constraint if the <see cref="Skeleton.Skin"/> contains
|
||||
/// this constraint.</summary>
|
||||
///<seealso cref="Skin.Constraints"/>
|
||||
/// <summary>When true, <see cref="Skeleton.UpdateWorldTransform(Skeleton.Physics)"/> only updates this constraint if the <see cref="Skeleton.Skin"/>
|
||||
/// contains this constraint.</summary>
|
||||
/// <seealso cref="Skin.Constraints"/>
|
||||
public bool SkinRequired { get { return skinRequired; } set { skinRequired = value; } }
|
||||
|
||||
override public string ToString () {
|
||||
|
||||
@ -28,15 +28,20 @@
|
||||
*****************************************************************************/
|
||||
|
||||
namespace Spine {
|
||||
using Physics = Skeleton.Physics;
|
||||
|
||||
///<summary>The interface for items updated by <see cref="Skeleton.UpdateWorldTransform()"/>.</summary>
|
||||
/// <summary>The interface for items updated by <see cref="Skeleton.UpdateWorldTransform(Physics)"/>.</summary>
|
||||
public interface IUpdatable {
|
||||
void Update ();
|
||||
/// <param name="physics">Determines how physics and other non-deterministic updates are applied.</param>
|
||||
void Update (Physics physics);
|
||||
|
||||
///<summary>Returns false when this item has not been updated because a skin is required and the <see cref="Skeleton.Skin">active
|
||||
/// skin</see> does not contain this item.</summary>
|
||||
/// <summary>Returns false when this item won't be updated by
|
||||
/// <see cref="Skeleton.UpdateWorldTransform(Skeleton.Physics)"/> because a skin is required and the
|
||||
/// <see cref="Skeleton.Skin">active skin</see> does not contain this item.</summary>
|
||||
/// <seealso cref="Skin.Bones"/>
|
||||
/// <seealso cref="Skin.Constraints"/>
|
||||
/// <seealso cref="BoneData.SkinRequired"/>
|
||||
/// <seealso cref="ConstraintData.SkinRequired"/>
|
||||
bool Active { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,6 +30,8 @@
|
||||
using System;
|
||||
|
||||
namespace Spine {
|
||||
using Physics = Skeleton.Physics;
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// 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<Bone>(data.bones.Count);
|
||||
foreach (BoneData boneData in data.bones)
|
||||
bones.Add(skeleton.bones.Items[boneData.index]);
|
||||
|
||||
target = skeleton.bones.Items[data.target.index];
|
||||
}
|
||||
|
||||
/// <summary>Copy constructor.</summary>
|
||||
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<Bone>(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);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Returns the atan2 using Math.Atan2.</summary>
|
||||
static public float Atan2 (float y, float x) {
|
||||
return (float)Math.Atan2(y, x);
|
||||
|
||||
@ -30,6 +30,7 @@
|
||||
using System;
|
||||
|
||||
namespace Spine {
|
||||
using Physics = Skeleton.Physics;
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
@ -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<Bone>(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 {
|
||||
}
|
||||
|
||||
/// <summary>Copy constructor.</summary>
|
||||
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<Bone>(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<Bone>(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);
|
||||
|
||||
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Stores the current pose for a physics constraint. A physics constraint applies physics to bones.
|
||||
/// <para>
|
||||
@ -37,66 +40,244 @@ namespace Spine {
|
||||
/// </summary>
|
||||
public class PhysicsConstraint : IUpdatable {
|
||||
internal readonly PhysicsConstraintData data;
|
||||
internal readonly ExposedList<Bone> 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<Bone>(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;
|
||||
}
|
||||
|
||||
/// <summary>Copy constructor.</summary>
|
||||
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<Bone>(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;
|
||||
}
|
||||
|
||||
/// <summary>Applies the constraint to the constrained bones.</summary>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>The bones that will be modified by this physics constraint.</summary>
|
||||
public ExposedList<Bone> Bones { get { return bones; } }
|
||||
/// <summary>The bone constrained by this physics constraint.</summary>
|
||||
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; } }
|
||||
/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained poses.</summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>The physics constraint's setup pose data.</summary>
|
||||
public PhysicsConstraintData Data { get { return data; } }
|
||||
|
||||
|
||||
@ -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 {
|
||||
/// <summary>
|
||||
/// Stores the setup pose for a <see cref="PhysicsConstraint"/>.
|
||||
@ -36,24 +34,37 @@ namespace Spine {
|
||||
/// See <a href="http://esotericsoftware.com/spine-physics-constraints">Physics constraints</a> in the Spine User Guide.</para>
|
||||
/// </summary>
|
||||
public class PhysicsConstraintData : ConstraintData {
|
||||
internal ExposedList<BoneData> bones = new ExposedList<BoneData>();
|
||||
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) {
|
||||
}
|
||||
|
||||
/// <summary>The bones that are constrained by this physics constraint.</summary>
|
||||
public ExposedList<BoneData> Bones { get { return bones; } }
|
||||
/// <summary>The bone constrained by this physics constraint.</summary>
|
||||
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; } }
|
||||
/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained poses.</summary>
|
||||
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; } }
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,8 +42,9 @@ namespace Spine {
|
||||
internal ExposedList<IUpdatable> updateCache = new ExposedList<IUpdatable>();
|
||||
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;
|
||||
|
||||
/// <summary>The skeleton's setup pose data.</summary>
|
||||
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; } }
|
||||
/// <summary>Returns the skeleton's time. This is used for time-based manipulations, such as <see cref="PhysicsConstraint"/>.</summary>
|
||||
/// <seealso cref="Update(float)"/>
|
||||
public float Time { get { return time; } set { time = value; } }
|
||||
|
||||
/// <summary>Returns the root bone, or null if the skeleton has no bones.</summary>
|
||||
public Bone RootBone {
|
||||
@ -183,27 +187,30 @@ namespace Spine {
|
||||
|
||||
ikConstraints = new ExposedList<IkConstraint>(skeleton.ikConstraints.Count);
|
||||
foreach (IkConstraint ikConstraint in skeleton.ikConstraints)
|
||||
ikConstraints.Add(new IkConstraint(ikConstraint, this));
|
||||
ikConstraints.Add(new IkConstraint(ikConstraint));
|
||||
|
||||
transformConstraints = new ExposedList<TransformConstraint>(skeleton.transformConstraints.Count);
|
||||
foreach (TransformConstraint transformConstraint in skeleton.transformConstraints)
|
||||
transformConstraints.Add(new TransformConstraint(transformConstraint, this));
|
||||
transformConstraints.Add(new TransformConstraint(transformConstraint));
|
||||
|
||||
pathConstraints = new ExposedList<PathConstraint>(skeleton.pathConstraints.Count);
|
||||
foreach (PathConstraint pathConstraint in skeleton.pathConstraints)
|
||||
pathConstraints.Add(new PathConstraint(pathConstraint, this));
|
||||
pathConstraints.Add(new PathConstraint(pathConstraint));
|
||||
|
||||
physicsConstraints = new ExposedList<PhysicsConstraint>(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 <a href="http://esotericsoftware.com/spine-runtime-skeletons#World-transforms">World transforms</a> in the Spine
|
||||
/// Runtimes Guide.</para>
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Temporarily sets the root bone as a child of the specified bone, then updates the world transform for each bone and applies
|
||||
/// all constraints.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Increments the skeleton's <see cref="time"/>.</summary>
|
||||
public void Update (float delta) {
|
||||
time += delta;
|
||||
}
|
||||
|
||||
/// <summary>Sets the bones, constraints, and slots to their setup pose values.</summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>Determines how physics and other non-deterministic updates are applied.</summary>
|
||||
public enum Physics {
|
||||
/// <summary>Physics are not updated or applied.</summary>
|
||||
None,
|
||||
|
||||
/// <summary>Physics are reset to the current pose.</summary>
|
||||
Reset,
|
||||
|
||||
/// <summary>Physics are updated and the pose from physics is applied.</summary>
|
||||
Update,
|
||||
|
||||
/// <summary>Physics are not updated but the pose from physics is applied.</summary>
|
||||
Pose
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<LinkedMesh> linkedMeshes = new List<LinkedMesh>();
|
||||
|
||||
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<float> weights = new ExposedList<float>(verticesLength * 3 * 3);
|
||||
ExposedList<int> bonesArray = new ExposedList<int>(verticesLength * 3);
|
||||
ExposedList<float> weights = new ExposedList<float>(vertices.length * 3 * 3);
|
||||
ExposedList<int> bonesArray = new ExposedList<int>(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 {
|
||||
}
|
||||
|
||||
/// <exception cref="IOException">Throws IOException when a read operation fails.</exception>
|
||||
private Timeline ReadTimeline (SkeletonInput input, CurveTimeline1 timeline, float scale) {
|
||||
private void ReadTimeline (SkeletonInput input, ExposedList<Timeline> 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);
|
||||
}
|
||||
|
||||
/// <exception cref="IOException">Throws IOException when a read operation fails.</exception>
|
||||
private Timeline ReadTimeline (SkeletonInput input, CurveTimeline2 timeline, float scale) {
|
||||
private void ReadTimeline (SkeletonInput input, ExposedList<Timeline> 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);
|
||||
}
|
||||
|
||||
/// <exception cref="IOException">Throws IOException when a read operation fails.</exception>
|
||||
@ -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);
|
||||
}
|
||||
|
||||
///<return>May be null.</return>
|
||||
/// <return>May be null.</return>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,8 +51,8 @@ namespace Spine {
|
||||
internal float fps;
|
||||
internal string imagesPath, audioPath;
|
||||
|
||||
///<summary>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.</summary>
|
||||
/// <summary>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.</summary>
|
||||
public string Name { get { return name; } set { name = value; } }
|
||||
|
||||
/// <summary>The skeleton's bones, sorted parent first. The root bone is always the first bone.</summary>
|
||||
@ -90,8 +90,8 @@ namespace Spine {
|
||||
/// <summary>The Spine version used to export this data, or null.</summary>
|
||||
public string Version { get { return version; } set { version = value; } }
|
||||
|
||||
///<summary>The skeleton data hash. This value will change if any of the skeleton data has changed.
|
||||
///May be null.</summary>
|
||||
/// <summary>The skeleton data hash. This value will change if any of the skeleton data has changed.
|
||||
/// May be null.</summary>
|
||||
public string Hash { get { return hash; } set { hash = value; } }
|
||||
|
||||
public string ImagesPath { get { return imagesPath; } set { imagesPath = value; } }
|
||||
@ -217,7 +217,7 @@ namespace Spine {
|
||||
/// <returns>May be null.</returns>
|
||||
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;
|
||||
|
||||
@ -52,6 +52,7 @@ namespace Spine {
|
||||
/// Runtimes Guide.</para>
|
||||
/// </summary>
|
||||
public class SkeletonJson : SkeletonLoader {
|
||||
private readonly List<LinkedMesh> linkedMeshes = new List<LinkedMesh>();
|
||||
|
||||
public SkeletonJson (AttachmentLoader attachmentLoader)
|
||||
: base(attachmentLoader) {
|
||||
@ -277,6 +278,42 @@ namespace Spine {
|
||||
}
|
||||
}
|
||||
|
||||
// Physics constraints.
|
||||
if (root.ContainsKey("physics")) {
|
||||
foreach (Dictionary<string, Object> constraintMap in (List<Object>)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<string, object> skinMap in (List<object>)root["skins"]) {
|
||||
@ -310,6 +347,13 @@ namespace Spine {
|
||||
skin.constraints.Add(constraint);
|
||||
}
|
||||
}
|
||||
if (skinMap.ContainsKey("physics")) {
|
||||
foreach (string entryName in (List<Object>)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<string, Object> slotEntry in (Dictionary<string, Object>)skinMap["attachments"]) {
|
||||
@ -964,6 +1008,55 @@ namespace Spine {
|
||||
}
|
||||
}
|
||||
|
||||
// Physics constraint timelines.
|
||||
if (map.ContainsKey("physics")) {
|
||||
foreach (KeyValuePair<string, Object> constraintMap in (Dictionary<string, Object>)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<string, object> timelineMap = (Dictionary<string, Object>)constraintMap.Value;
|
||||
foreach (KeyValuePair<string, Object> timelineEntry in timelineMap) {
|
||||
List<object> values = (List<Object>)timelineEntry.Value;
|
||||
List<object>.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<string, Object> 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<string, Object> attachmentsMap in (Dictionary<string, Object>)map["attachments"]) {
|
||||
@ -1051,13 +1144,13 @@ namespace Spine {
|
||||
DrawOrderTimeline timeline = new DrawOrderTimeline(values.Count);
|
||||
int slotCount = skeletonData.slots.Count;
|
||||
int frame = 0;
|
||||
foreach (Dictionary<string, Object> drawOrderMap in values) {
|
||||
foreach (Dictionary<string, Object> 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<object> offsets = (List<Object>)drawOrderMap["offsets"];
|
||||
List<object> offsets = (List<Object>)keyMap["offsets"];
|
||||
int[] unchanged = new int[slotCount - offsets.Count];
|
||||
int originalIndex = 0, unchangedIndex = 0;
|
||||
foreach (Dictionary<string, Object> 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<object> eventsMap = (List<Object>)map["events"];
|
||||
EventTimeline timeline = new EventTimeline(eventsMap.Count);
|
||||
int frame = 0;
|
||||
foreach (Dictionary<string, Object> 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<string, Object> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,7 +42,6 @@ namespace Spine {
|
||||
public abstract class SkeletonLoader {
|
||||
protected readonly AttachmentLoader attachmentLoader;
|
||||
protected float scale = 1;
|
||||
protected readonly List<LinkedMesh> linkedMeshes = new List<LinkedMesh>();
|
||||
|
||||
/// <summary>Creates a skeleton loader that loads attachments using an <see cref="AtlasAttachmentLoader"/> with the specified atlas.
|
||||
/// </summary>
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,7 +45,7 @@ namespace Spine {
|
||||
internal readonly ExposedList<ConstraintData> constraints = new ExposedList<ConstraintData>();
|
||||
|
||||
public string Name { get { return name; } }
|
||||
///<summary>Returns all attachments contained in this skin.</summary>
|
||||
/// <summary>Returns all attachments contained in this skin.</summary>
|
||||
public ICollection<SkinEntry> Attachments { get { return attachments.Values; } }
|
||||
public ExposedList<BoneData> Bones { get { return bones; } }
|
||||
public ExposedList<ConstraintData> Constraints { get { return constraints; } }
|
||||
@ -62,7 +62,7 @@ namespace Spine {
|
||||
attachments[new SkinKey(slotIndex, name)] = new SkinEntry(slotIndex, name, attachment);
|
||||
}
|
||||
|
||||
///<summary>Adds all attachments, bones, and constraints from the specified skin to this skin.</summary>
|
||||
/// <summary>Adds all attachments, bones, and constraints from the specified skin to this skin.</summary>
|
||||
public void AddSkin (Skin skin) {
|
||||
foreach (BoneData data in skin.bones)
|
||||
if (!bones.Contains(data)) bones.Add(data);
|
||||
@ -76,7 +76,7 @@ namespace Spine {
|
||||
}
|
||||
}
|
||||
|
||||
///<summary>Adds all attachments from the specified skin to this skin. Attachments are deep copied.</summary>
|
||||
/// <summary>Adds all attachments from the specified skin to this skin. Attachments are deep copied.</summary>
|
||||
public void CopySkin (Skin skin) {
|
||||
foreach (BoneData data in skin.bones)
|
||||
if (!bones.Contains(data)) bones.Add(data);
|
||||
@ -118,7 +118,7 @@ namespace Spine {
|
||||
}
|
||||
}
|
||||
|
||||
///<summary>Clears all attachments, bones, and constraints.</summary>
|
||||
/// <summary>Clears all attachments, bones, and constraints.</summary>
|
||||
public void Clear () {
|
||||
attachments.Clear();
|
||||
bones.Clear();
|
||||
|
||||
@ -30,6 +30,8 @@
|
||||
using System;
|
||||
|
||||
namespace Spine {
|
||||
using Physics = Skeleton.Physics;
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// 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<Bone>();
|
||||
foreach (BoneData boneData in data.bones)
|
||||
bones.Add(skeleton.bones.Items[boneData.index]);
|
||||
@ -63,14 +66,12 @@ namespace Spine {
|
||||
}
|
||||
|
||||
/// <summary>Copy constructor.</summary>
|
||||
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<Bone>(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;
|
||||
}
|
||||
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user