/****************************************************************************** * 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 { public const int ROTATION = 0, X = 1, Y = 2, SCALEX = 3, SCALEY = 4, SHEARY = 5; internal readonly ExposedList bones = new ExposedList(); internal BoneData source; internal float[] offsets = new float[6]; internal bool localSource, localTarget, additive, clamp; internal readonly ExposedList properties = new ExposedList(); public TransformConstraintData (string name) : base(name, new TransformConstraintPose()) { } override public IConstraint Create (Skeleton skeleton) { return new TransformConstraint(this, skeleton); } /// The bones that will be modified by this transform constraint. 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; } } /// An offset added to the constrained bone rotation. public float OffsetRotation { get { return offsets[ROTATION]; } set { offsets[ROTATION] = value; } } /// An offset added to the constrained bone X translation. public float OffsetX { get { return offsets[X]; } set { offsets[X] = value; } } /// An offset added to the constrained bone Y translation. public float OffsetY { get { return offsets[Y]; } set { offsets[Y] = value; } } /// An offset added to the constrained bone scaleX. public float OffsetScaleX { get { return offsets[SCALEX]; } set { offsets[SCALEX] = value; } } /// An offset added to the constrained bone scaleY. public float OffsetScaleY { get { return offsets[SCALEY]; } set { offsets[SCALEY] = value; } } /// An offset added to the constrained bone shearY. public float OffsetShearY { get { return offsets[SHEARY]; } set { offsets[SHEARY] = 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; } } /// The mapping of transform properties to other transform properties. public ExposedList Properties { get { return properties; } } /// 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(1); /// Reads this property from the specified bone. abstract public float Value (Skeleton skeleton, BonePose source, bool local, float[] offsets); } ///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 pose. public abstract float Mix (TransformConstraintPose pose); /// Applies the value to this property. public abstract void Apply (Skeleton skeleton, TransformConstraintPose pose, BonePose bone, float value, bool local, bool additive); } public class FromRotate : FromProperty { public override float Value (Skeleton skeleton, BonePose source, bool local, float[] offsets) { if (local) return source.rotation + offsets[ROTATION]; float sx = skeleton.scaleX, sy = skeleton.ScaleY; float value = MathUtils.Atan2(source.c / sy, source.a / sx) * MathUtils.RadDeg + ((source.a * source.d - source.b * source.c) * sx * sy > 0 ? offsets[ROTATION] : -offsets[ROTATION]); if (value < 0) value += 360; return value; } } public class ToRotate : ToProperty { public override float Mix (TransformConstraintPose pose) { return pose.mixRotate; } public override void Apply (Skeleton skeleton, TransformConstraintPose pose, BonePose bone, float value, bool local, bool additive) { if (local) bone.rotation += (additive ? value : value - bone.rotation) * pose.mixRotate; else { float sx = skeleton.scaleX, sy = skeleton.ScaleY, ix = 1 / sx, iy = 1 / sy; float a = bone.a * ix, b = bone.b * ix, c = bone.c * iy, d = bone.d * iy; 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 *= pose.mixRotate; float cos = MathUtils.Cos(value), sin = MathUtils.Sin(value); bone.a = (cos * a - sin * c) * sx; bone.b = (cos * b - sin * d) * sx; bone.c = (sin * a + cos * c) * sy; bone.d = (sin * b + cos * d) * sy; } } } public class FromX : FromProperty { public override float Value (Skeleton skeleton, BonePose source, bool local, float[] offsets) { return local ? source.x + offsets[X] : (offsets[X] * source.a + offsets[Y] * source.b + source.worldX) / skeleton.scaleX; } } public class ToX : ToProperty { public override float Mix (TransformConstraintPose pose) { return pose.mixX; } public override void Apply (Skeleton skeleton, TransformConstraintPose pose, BonePose bone, float value, bool local, bool additive) { if (local) bone.x += (additive ? value : value - bone.x) * pose.mixX; else { if (!additive) value -= bone.worldX / skeleton.scaleX; bone.worldX += value * pose.mixX * skeleton.scaleX; } } } public class FromY : FromProperty { public override float Value (Skeleton skeleton, BonePose source, bool local, float[] offsets) { return local ? source.y + offsets[Y] : (offsets[X] * source.c + offsets[Y] * source.d + source.worldY) / skeleton.ScaleY; } } public class ToY : ToProperty { public override float Mix (TransformConstraintPose pose) { return pose.mixY; } public override void Apply (Skeleton skeleton, TransformConstraintPose pose, BonePose bone, float value, bool local, bool additive) { if (local) bone.y += (additive ? value : value - bone.y) * pose.mixY; else { float skeletonScaleY = skeleton.ScaleY; if (!additive) value -= bone.worldY / skeletonScaleY; bone.worldY += value * pose.mixY * skeletonScaleY; } } } public class FromScaleX : FromProperty { public override float Value (Skeleton skeleton, BonePose source, bool local, float[] offsets) { if (local) return source.scaleX + offsets[SCALEX]; float a = source.a / skeleton.scaleX, c = source.c / skeleton.ScaleY; return (float)Math.Sqrt(a * a + c * c) + offsets[SCALEX]; } } public class ToScaleX : ToProperty { public override float Mix (TransformConstraintPose pose) { return pose.mixScaleX; } public override void Apply (Skeleton skeleton, TransformConstraintPose pose, BonePose bone, float value, bool local, bool additive) { if (local) { if (additive) bone.scaleX *= 1 + (value - 1) * pose.mixScaleX; else if (bone.scaleX != 0) // bone.scaleX += (value - bone.scaleX) * pose.mixScaleX; } else if (additive) { float s = 1 + (value - 1) * pose.mixScaleX; bone.a *= s; bone.c *= s; } else { float a = bone.a / skeleton.scaleX, c = bone.c / skeleton.ScaleY, s = (float)Math.Sqrt(a * a + c * c); if (s != 0) { s = 1 + (value - s) * pose.mixScaleX / s; bone.a *= s; bone.c *= s; } } } } public class FromScaleY : FromProperty { public override float Value (Skeleton skeleton, BonePose source, bool local, float[] offsets) { if (local) return source.scaleY + offsets[SCALEY]; float b = source.b / skeleton.scaleX, d = source.d / skeleton.ScaleY; return (float)Math.Sqrt(b * b + d * d) + offsets[SCALEY]; } } public class ToScaleY : ToProperty { public override float Mix (TransformConstraintPose pose) { return pose.mixScaleY; } public override void Apply (Skeleton skeleton, TransformConstraintPose pose, BonePose bone, float value, bool local, bool additive) { if (local) { if (additive) bone.scaleY *= 1 + (value - 1) * pose.mixScaleY; else if (bone.scaleY != 0) // bone.scaleY += (value - bone.scaleY) * pose.mixScaleY; } else if (additive) { float s = 1 + (value - 1) * pose.mixScaleY; bone.b *= s; bone.d *= s; } else { float b = bone.b / skeleton.scaleX, d = bone.d / skeleton.ScaleY, s = (float)Math.Sqrt(b * b + d * d); if (s != 0) { s = 1 + (value - s) * pose.mixScaleY / s; bone.b *= s; bone.d *= s; } } } } public class FromShearY : FromProperty { public override float Value (Skeleton skeleton, BonePose source, bool local, float[] offsets) { if (local) return source.shearY + offsets[SHEARY]; float ix = 1 / skeleton.scaleX, iy = 1 / skeleton.ScaleY; return (MathUtils.Atan2(source.d * iy, source.b * ix) - MathUtils.Atan2(source.c * iy, source.a * ix)) * MathUtils.RadDeg - 90 + offsets[SHEARY]; } } public class ToShearY : ToProperty { public override float Mix (TransformConstraintPose pose) { return pose.mixShearY; } public override void Apply (Skeleton skeleton, TransformConstraintPose pose, BonePose bone, float value, bool local, bool additive) { if (local) { if (!additive) value -= bone.shearY; bone.shearY += value * pose.mixShearY; } else { float sx = skeleton.scaleX, sy = skeleton.ScaleY, b = bone.b / sx, d = bone.d / sy, by = MathUtils.Atan2(d, b); value = (value + 90) * MathUtils.DegRad; if (additive) value -= MathUtils.PI / 2; else { value -= by - MathUtils.Atan2(bone.c / sy, bone.a / sx); if (value > MathUtils.PI) value -= MathUtils.PI2; else if (value < -MathUtils.PI) // value += MathUtils.PI2; } value = by + value * pose.mixShearY; float s = (float)Math.Sqrt(b * b + d * d); bone.b = MathUtils.Cos(value) * s * sx; bone.d = MathUtils.Sin(value) * s * sy; } } } } }