[csharp] Ported 4.2-beta physics.

This commit is contained in:
Harald Csaszar 2023-11-17 16:41:44 +01:00
parent ad263d48be
commit fe53638f69
26 changed files with 1299 additions and 733 deletions

View File

@ -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

View File

@ -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>

View File

@ -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 ();
}
}

View File

@ -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);

View File

@ -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 () {

View File

@ -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;

View File

@ -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; } }

View File

@ -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 () {

View File

@ -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; } }

View File

@ -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 () {

View File

@ -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; }
}
}

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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; } }

View File

@ -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; } }
}
}

View File

@ -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
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -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;

View File

@ -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;
}
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -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();

View File

@ -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;
}

View File

@ -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",

View File

@ -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;
}
}