/****************************************************************************** * Spine Runtimes License Agreement * Last updated April 5, 2025. Replaces all prior versions. * * Copyright (c) 2013-2025, 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, * "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. * * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (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. *****************************************************************************/ using System; namespace Spine { public class TransformConstraintData : ConstraintData { internal readonly ExposedList bones = new ExposedList(); internal BoneData source; internal float offsetX, offsetY, mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY; internal bool localSource, localTarget, additive, clamp; internal readonly ExposedList properties = new ExposedList(); public TransformConstraintData (string name) : base(name) { } public ExposedList Bones { get { return bones; } } /// The bone whose world transform will be copied to the constrained bones. public BoneData Source { get { return source; } set { if (source == null) throw new ArgumentNullException("Source", "source cannot be null."); source = value; } } /// The mapping of transform properties to other transform properties. public ExposedList Properties { get { return properties; } } /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. public float MixRotate { get { return mixRotate; } set { mixRotate = value; } } /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. public float MixX { get { return mixX; } set { mixX = value; } } /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. public float MixY { get { return mixY; } set { mixY = value; } } /// A percentage (0-1) that controls the mix between the constrained and unconstrained scale X. public float MixScaleX { get { return mixScaleX; } set { mixScaleX = value; } } /// A percentage (0-1) that controls the mix between the constrained and unconstrained scale Y. public float MixScaleY { get { return mixScaleY; } set { mixScaleY = value; } } /// A percentage (0-1) that controls the mix between the constrained and unconstrained shear Y. public float MixShearY { get { return mixShearY; } set { mixShearY = value; } } /// An offset added to the constrained bone X translation. public float OffsetX { get { return offsetX; } set { offsetX = value; } } /// An offset added to the constrained bone Y translation. public float OffsetY { get { return offsetY; } set { offsetY = value; } } /// Reads the source bone's local transform instead of its world transform. public bool LocalSource { get { return localSource; } set { localSource = value; } } /// Sets the constrained bones' local transforms instead of their world transforms. public bool LocalTarget { get { return localTarget; } set { localTarget = value; } } /// Adds the source bone transform to the constrained bones instead of setting it absolutely. public bool Additive { get { return additive; } set { additive = value; } } /// Prevents constrained bones from exceeding the ranged defined by and /// . public bool Clamp { get { return clamp; } set { clamp = value; } } /// Source property for a . abstract public class FromProperty { /// The value of this property that corresponds to . public float offset; /// Constrained properties. public readonly ExposedList to = new ExposedList(); /// Reads this property from the specified bone. abstract public float Value (TransformConstraintData data, Bone source, bool local); } ///Constrained property for a . abstract public class ToProperty { /// The value of this property that corresponds to . public float offset; /// The maximum value of this property when clamped. public float max; /// The scale of the value in relation to this property. public float scale; /// Reads the mix for this property from the specified constraint. public abstract float Mix (TransformConstraint constraint); /// Applies the value to this property. public abstract void Apply (TransformConstraint constraint, Bone bone, float value, bool local, bool additive); } public class FromRotate : FromProperty { public override float Value (TransformConstraintData data, Bone source, bool local) { return local ? source.arotation : MathUtils.Atan2(source.c, source.a) * MathUtils.RadDeg; } } public class ToRotate : ToProperty { public override float Mix (TransformConstraint constraint) { return constraint.mixRotate; } public override void Apply (TransformConstraint constraint, Bone bone, float value, bool local, bool additive) { if (local) { if (!additive) value -= bone.arotation; bone.arotation += value * constraint.mixRotate; } else { float a = bone.a, b = bone.b, c = bone.c, d = bone.d; value *= MathUtils.DegRad; if (!additive) value -= MathUtils.Atan2(c, a); if (value > MathUtils.PI) value -= MathUtils.PI2; else if (value < -MathUtils.PI) // value += MathUtils.PI2; value *= constraint.mixRotate; float cos = MathUtils.Cos(value), sin = MathUtils.Sin(value); bone.a = cos * a - sin * c; bone.b = cos * b - sin * d; bone.c = sin * a + cos * c; bone.d = sin * b + cos * d; } } } public class FromX : FromProperty { public override float Value (TransformConstraintData data, Bone source, bool local) { return local ? source.ax + data.offsetX : data.offsetX * source.a + data.offsetY * source.b + source.worldX; } } public class ToX : ToProperty { public override float Mix (TransformConstraint constraint) { return constraint.mixX; } public override void Apply (TransformConstraint constraint, Bone bone, float value, bool local, bool additive) { if (local) { if (!additive) value -= bone.ax; bone.ax += value * constraint.mixX; } else { if (!additive) value -= bone.worldX; bone.worldX += value * constraint.mixX; } } } public class FromY : FromProperty { public override float Value (TransformConstraintData data, Bone source, bool local) { return local ? source.ay + data.offsetY : data.offsetX * source.c + data.offsetY * source.d + source.worldY; } } public class ToY : ToProperty { public override float Mix (TransformConstraint constraint) { return constraint.mixY; } public override void Apply (TransformConstraint constraint, Bone bone, float value, bool local, bool additive) { if (local) { if (!additive) value -= bone.ay; bone.ay += value * constraint.mixY; } else { if (!additive) value -= bone.worldY; bone.worldY += value * constraint.mixY; } } } public class FromScaleX : FromProperty { public override float Value (TransformConstraintData data, Bone source, bool local) { return local ? source.ascaleX : (float)Math.Sqrt(source.a * source.a + source.c * source.c); } } public class ToScaleX : ToProperty { public override float Mix (TransformConstraint constraint) { return constraint.mixScaleX; } public override void Apply (TransformConstraint constraint, Bone bone, float value, bool local, bool additive) { if (local) { if (additive) bone.ascaleX *= 1 + ((value - 1) * constraint.mixScaleX); else if (bone.ascaleX != 0) // bone.ascaleX = 1 + (value / bone.ascaleX - 1) * constraint.mixScaleX; } else { float s; if (additive) s = 1 + (value - 1) * constraint.mixScaleX; else { s = (float)Math.Sqrt(bone.a * bone.a + bone.c * bone.c); if (s != 0) s = 1 + (value / s - 1) * constraint.mixScaleX; } bone.a *= s; bone.c *= s; } } } public class FromScaleY : FromProperty { public override float Value (TransformConstraintData data, Bone source, bool local) { return local ? source.ascaleY : (float)Math.Sqrt(source.b * source.b + source.d * source.d); } } public class ToScaleY : ToProperty { public override float Mix (TransformConstraint constraint) { return constraint.mixScaleY; } public override void Apply (TransformConstraint constraint, Bone bone, float value, bool local, bool additive) { if (local) { if (additive) bone.ascaleY *= 1 + ((value - 1) * constraint.mixScaleY); else if (bone.ascaleY != 0) // bone.ascaleY = 1 + (value / bone.ascaleY - 1) * constraint.mixScaleY; } else { float s; if (additive) s = 1 + (value - 1) * constraint.mixScaleY; else { s = (float)Math.Sqrt(bone.b * bone.b + bone.d * bone.d); if (s != 0) s = 1 + (value / s - 1) * constraint.mixScaleY; } bone.b *= s; bone.d *= s; } } } public class FromShearY : FromProperty { public override float Value (TransformConstraintData data, Bone source, bool local) { return local ? source.ashearY : (MathUtils.Atan2(source.d, source.b) - MathUtils.Atan2(source.c, source.a)) * MathUtils.RadDeg - 90; } } public class ToShearY : ToProperty { public override float Mix (TransformConstraint constraint) { return constraint.mixShearY; } public override void Apply (TransformConstraint constraint, Bone bone, float value, bool local, bool additive) { if (local) { if (!additive) value -= bone.ashearY; bone.ashearY += value * constraint.mixShearY; } else { float b = bone.b, d = bone.d, by = MathUtils.Atan2(d, b); value = (value + 90) * MathUtils.DegRad; if (additive) value -= MathUtils.PI / 2; else { value -= by - MathUtils.Atan2(bone.c, bone.a); if (value > MathUtils.PI) value -= MathUtils.PI2; else if (value < -MathUtils.PI) // value += MathUtils.PI2; } value = by + value * constraint.mixShearY; float s = (float)Math.Sqrt(b * b + d * d); bone.b = MathUtils.Cos(value) * s; bone.d = MathUtils.Sin(value) * s; } } } } }