mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-04 14:24:53 +08:00
305 lines
10 KiB
Haxe
305 lines
10 KiB
Haxe
/******************************************************************************
|
|
* Spine Runtimes License Agreement
|
|
* Last updated July 28, 2023. Replaces all prior versions.
|
|
*
|
|
* 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,
|
|
* "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.
|
|
*****************************************************************************/
|
|
|
|
package spine;
|
|
|
|
/** Stores the current pose for a transform constraint. A transform constraint adjusts the world transform of the constrained
|
|
* bones to match that of the target bone.
|
|
* <p>
|
|
* See <a href="https://esotericsoftware.com/spine-transform-constraints">Transform constraints</a> in the Spine User Guide. */
|
|
class TransformConstraint implements Updatable {
|
|
private var _data:TransformConstraintData;
|
|
private var _bones:Array<Bone>;
|
|
|
|
/** The target bone whose world transform will be copied to the constrained bones. */
|
|
public var target:Bone;
|
|
/** A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. */
|
|
public var mixRotate:Float = 0;
|
|
/** A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. */
|
|
public var mixX:Float = 0;
|
|
/** A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. */
|
|
public var mixY:Float = 0;
|
|
/** A percentage (0-1) that controls the mix between the constrained and unconstrained scale X. */
|
|
public var mixScaleX:Float = 0;
|
|
/** A percentage (0-1) that controls the mix between the constrained and unconstrained scale Y. */
|
|
public var mixScaleY:Float = 0;
|
|
/** A percentage (0-1) that controls the mix between the constrained and unconstrained shear Y. */
|
|
public var mixShearY:Float = 0;
|
|
|
|
private var _temp:Array<Float> = new Array<Float>();
|
|
|
|
public var active:Bool = false;
|
|
|
|
/** Copy constructor. */
|
|
public function new(data:TransformConstraintData, skeleton:Skeleton) {
|
|
if (data == null)
|
|
throw new SpineException("data cannot be null.");
|
|
if (skeleton == null)
|
|
throw new SpineException("skeleton cannot be null.");
|
|
_data = data;
|
|
|
|
_bones = new Array<Bone>();
|
|
for (boneData in data.bones) {
|
|
_bones.push(skeleton.findBone(boneData.name));
|
|
}
|
|
target = skeleton.findBone(data.target.name);
|
|
|
|
mixRotate = data.mixRotate;
|
|
mixX = data.mixX;
|
|
mixY = data.mixY;
|
|
mixScaleX = data.mixScaleX;
|
|
mixScaleY = data.mixScaleY;
|
|
mixShearY = data.mixShearY;
|
|
}
|
|
|
|
public function isActive():Bool {
|
|
return active;
|
|
}
|
|
|
|
public function setToSetupPose () {
|
|
var data:TransformConstraintData = _data;
|
|
mixRotate = data.mixRotate;
|
|
mixX = data.mixX;
|
|
mixY = data.mixY;
|
|
mixScaleX = data.mixScaleX;
|
|
mixScaleY = data.mixScaleY;
|
|
mixShearY = data.mixShearY;
|
|
}
|
|
|
|
/** Applies the constraint to the constrained bones. */
|
|
public function update(physics:Physics):Void {
|
|
if (mixRotate == 0 && mixX == 0 && mixY == 0 && mixScaleX == 0 && mixScaleY == 0 && mixShearY == 0)
|
|
return;
|
|
|
|
if (data.local) {
|
|
if (data.relative) {
|
|
applyRelativeLocal();
|
|
} else {
|
|
applyAbsoluteLocal();
|
|
}
|
|
} else {
|
|
if (data.relative) {
|
|
applyRelativeWorld();
|
|
} else {
|
|
applyAbsoluteWorld();
|
|
}
|
|
}
|
|
}
|
|
|
|
private function applyAbsoluteWorld():Void {
|
|
var translate:Bool = mixX != 0 || mixY != 0;
|
|
var ta:Float = target.a,
|
|
tb:Float = target.b,
|
|
tc:Float = target.c,
|
|
td:Float = target.d;
|
|
var degRadReflect:Float = ta * td - tb * tc > 0 ? MathUtils.degRad : -MathUtils.degRad;
|
|
var offsetRotation:Float = data.offsetRotation * degRadReflect;
|
|
var offsetShearY:Float = data.offsetShearY * degRadReflect;
|
|
for (bone in bones) {
|
|
if (mixRotate != 0) {
|
|
var a:Float = bone.a,
|
|
b:Float = bone.b,
|
|
c:Float = bone.c,
|
|
d:Float = bone.d;
|
|
var r:Float = Math.atan2(tc, ta) - Math.atan2(c, a) + offsetRotation;
|
|
if (r > Math.PI)
|
|
r -= Math.PI * 2;
|
|
else if (r < -Math.PI)
|
|
r += Math.PI * 2;
|
|
r *= mixRotate;
|
|
var cos:Float = Math.cos(r), sin:Float = Math.sin(r);
|
|
bone.a = cos * a - sin * c;
|
|
bone.b = cos * b - sin * d;
|
|
bone.c = sin * a + cos * c;
|
|
bone.d = sin * b + cos * d;
|
|
}
|
|
|
|
if (translate) {
|
|
_temp[0] = data.offsetX;
|
|
_temp[1] = data.offsetY;
|
|
target.localToWorld(_temp);
|
|
bone.worldX += (_temp[0] - bone.worldX) * mixX;
|
|
bone.worldY += (_temp[1] - bone.worldY) * mixY;
|
|
}
|
|
|
|
if (mixScaleX != 0) {
|
|
var s:Float = Math.sqrt(bone.a * bone.a + bone.c * bone.c);
|
|
if (s != 0)
|
|
s = (s + (Math.sqrt(ta * ta + tc * tc) - s + _data.offsetScaleX) * mixScaleX) / s;
|
|
bone.a *= s;
|
|
bone.c *= s;
|
|
}
|
|
|
|
if (mixScaleY != 0) {
|
|
var s:Float = Math.sqrt(bone.b * bone.b + bone.d * bone.d);
|
|
if (s != 0)
|
|
s = (s + (Math.sqrt(tb * tb + td * td) - s + _data.offsetScaleY) * mixScaleY) / s;
|
|
bone.b *= s;
|
|
bone.d *= s;
|
|
}
|
|
|
|
if (mixShearY > 0) {
|
|
var by:Float = Math.atan2(bone.d, bone.b);
|
|
var r:Float = Math.atan2(td, tb) - Math.atan2(tc, ta) - (by - Math.atan2(bone.c, bone.a));
|
|
if (r > Math.PI)
|
|
r -= Math.PI * 2;
|
|
else if (r < -Math.PI)
|
|
r += Math.PI * 2;
|
|
r = by + (r + offsetShearY) * mixShearY;
|
|
var s:Float = Math.sqrt(bone.b * bone.b + bone.d * bone.d);
|
|
bone.b = Math.cos(r) * s;
|
|
bone.d = Math.sin(r) * s;
|
|
}
|
|
|
|
bone.updateAppliedTransform();
|
|
}
|
|
}
|
|
|
|
public function applyRelativeWorld():Void {
|
|
var translate:Bool = mixX != 0 || mixY != 0;
|
|
var ta:Float = target.a,
|
|
tb:Float = target.b,
|
|
tc:Float = target.c,
|
|
td:Float = target.d;
|
|
var degRadReflect:Float = ta * td - tb * tc > 0 ? MathUtils.degRad : -MathUtils.degRad;
|
|
var offsetRotation:Float = _data.offsetRotation * degRadReflect,
|
|
offsetShearY:Float = _data.offsetShearY * degRadReflect;
|
|
for (bone in bones) {
|
|
if (mixRotate != 0) {
|
|
var a:Float = bone.a,
|
|
b:Float = bone.b,
|
|
c:Float = bone.c,
|
|
d:Float = bone.d;
|
|
var r:Float = Math.atan2(tc, ta) + offsetRotation;
|
|
if (r > MathUtils.PI)
|
|
r -= MathUtils.PI2;
|
|
else if (r < -MathUtils.PI)
|
|
r += MathUtils.PI2;
|
|
r *= mixRotate;
|
|
var cos:Float = Math.cos(r), sin:Float = Math.sin(r);
|
|
bone.a = cos * a - sin * c;
|
|
bone.b = cos * b - sin * d;
|
|
bone.c = sin * a + cos * c;
|
|
bone.d = sin * b + cos * d;
|
|
}
|
|
|
|
if (translate) {
|
|
var temp:Array<Float> = _temp;
|
|
temp[0] = _data.offsetX;
|
|
temp[1] = _data.offsetY;
|
|
target.localToWorld(temp);
|
|
bone.worldX += temp[0] * mixX;
|
|
bone.worldY += temp[1] * mixY;
|
|
}
|
|
|
|
if (mixScaleX != 0) {
|
|
var s:Float = (Math.sqrt(ta * ta + tc * tc) - 1 + _data.offsetScaleX) * mixScaleX + 1;
|
|
bone.a *= s;
|
|
bone.c *= s;
|
|
}
|
|
|
|
if (mixScaleY != 0) {
|
|
var s:Float = (Math.sqrt(tb * tb + td * td) - 1 + _data.offsetScaleY) * mixScaleY + 1;
|
|
bone.b *= s;
|
|
bone.d *= s;
|
|
}
|
|
|
|
if (mixShearY > 0) {
|
|
var r = Math.atan2(td, tb) - Math.atan2(tc, ta);
|
|
if (r > MathUtils.PI)
|
|
r -= MathUtils.PI2;
|
|
else if (r < -MathUtils.PI)
|
|
r += MathUtils.PI2;
|
|
var b = bone.b;
|
|
var d = bone.d;
|
|
r = Math.atan2(d, b) + (r - MathUtils.PI / 2 + offsetShearY) * mixShearY;
|
|
var s = Math.sqrt(b * b + d * d);
|
|
bone.b = Math.cos(r) * s;
|
|
bone.d = Math.sin(r) * s;
|
|
}
|
|
|
|
bone.updateAppliedTransform();
|
|
}
|
|
}
|
|
|
|
public function applyAbsoluteLocal():Void {
|
|
for (bone in bones) {
|
|
var rotation:Float = bone.arotation;
|
|
if (mixRotate != 0) rotation += (target.arotation - rotation + _data.offsetRotation) * mixRotate;
|
|
|
|
var x:Float = bone.ax, y:Float = bone.ay;
|
|
x += (target.ax - x + _data.offsetX) * mixX;
|
|
y += (target.ay - y + _data.offsetY) * mixY;
|
|
|
|
var scaleX:Float = bone.ascaleX, scaleY:Float = bone.ascaleY;
|
|
if (mixScaleX != 0 && scaleX != 0) {
|
|
scaleX = (scaleX + (target.ascaleX - scaleX + _data.offsetScaleX) * mixScaleX) / scaleX;
|
|
}
|
|
if (mixScaleY != 0 && scaleY != 0) {
|
|
scaleY = (scaleY + (target.ascaleY - scaleY + _data.offsetScaleY) * mixScaleX) / scaleY;
|
|
}
|
|
|
|
var shearY:Float = bone.ashearY;
|
|
if (mixShearY != 0) shearY += (target.ashearY - shearY + _data.offsetShearY) * mixShearY;
|
|
|
|
bone.updateWorldTransformWith(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY);
|
|
}
|
|
}
|
|
|
|
public function applyRelativeLocal():Void {
|
|
for (bone in bones) {
|
|
var rotation:Float = bone.arotation + (target.arotation + _data.offsetRotation) * mixRotate;
|
|
var x:Float = bone.ax + (target.ax + _data.offsetX) * mixX;
|
|
var y:Float = bone.ay + (target.ay + _data.offsetY) * mixY;
|
|
var scaleX:Float = bone.ascaleX * (((target.ascaleX - 1 + _data.offsetScaleX) * mixScaleX) + 1);
|
|
var scaleY:Float = bone.ascaleY * (((target.ascaleY - 1 + _data.offsetScaleY) * mixScaleY) + 1);
|
|
var shearY:Float = bone.ashearY + (target.ashearY + _data.offsetShearY) * mixShearY;
|
|
bone.updateWorldTransformWith(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY);
|
|
}
|
|
}
|
|
|
|
/** The transform constraint's setup pose data. */
|
|
public var data(get, never):TransformConstraintData;
|
|
|
|
private function get_data():TransformConstraintData {
|
|
return _data;
|
|
}
|
|
|
|
/** The bones that will be modified by this transform constraint. */
|
|
public var bones(get, never):Array<Bone>;
|
|
|
|
private function get_bones():Array<Bone> {
|
|
return _bones;
|
|
}
|
|
|
|
public function toString():String {
|
|
return _data.name != null ? _data.name : "TransformConstraint?";
|
|
}
|
|
}
|