[ts] Transform constraints alignment from reference runtime.

This commit is contained in:
Davide Tantillo 2025-04-04 13:00:00 +02:00
parent 0f1022fb3f
commit a46174b88b
8 changed files with 230 additions and 177 deletions

View File

@ -564,7 +564,7 @@ export class AnimationState {
return this.addAnimationWith(trackIndex, animation, loop, delay);
}
/** Adds an animation to be played after the current or last queued animation for a track. If the track is empty, it is
/** Adds an animation to be played after the current or last queued animation for a track. If the track has no entries, this is
* equivalent to calling {@link #setAnimationWith()}.
* @param delay If > 0, sets {@link TrackEntry#delay}. If <= 0, the delay set is the duration of the previous track entry
* minus any mix duration (from the {@link AnimationStateData}) plus the specified `delay` (ie the mix
@ -609,7 +609,10 @@ export class AnimationState {
* {@link #addAnimation()} and on the returned track entry, set the
* {@link TrackEntry#setMixDuration()}. Mixing from an empty animation causes the new animation to be applied more and
* more over the mix duration. Properties keyed in the new animation transition from the value from lower tracks or from the
* setup pose value if no lower tracks key the property to the value keyed in the new animation. */
* setup pose value if no lower tracks key the property to the value keyed in the new animation.
* <p>
* See <a href='https://esotericsoftware.com/spine-applying-animations/#Empty-animations'>Empty animations</a> in the Spine
* Runtimes Guide. */
setEmptyAnimation (trackIndex: number, mixDuration: number = 0) {
let entry = this.setAnimationWith(trackIndex, AnimationState.emptyAnimation(), false);
entry.mixDuration = mixDuration;
@ -618,16 +621,18 @@ export class AnimationState {
}
/** Adds an empty animation to be played after the current or last queued animation for a track, and sets the track entry's
* {@link TrackEntry#mixDuration}. If the track is empty, it is equivalent to calling
* {@link #setEmptyAnimation()}.
*
* See {@link #setEmptyAnimation()}.
* @param delay If > 0, sets {@link TrackEntry#delay}. If <= 0, the delay set is the duration of the previous track entry
* minus any mix duration plus the specified `delay` (ie the mix ends at (`delay` = 0) or
* before (`delay` < 0) the previous track entry duration). If the previous entry is looping, its next
* {@link TrackEntry#getMixDuration()}. If the track has no entries, it is equivalent to calling
* {@link #setEmptyAnimation(int, float)}.
* <p>
* See {@link #setEmptyAnimation(int, float)} and
* <a href='https://esotericsoftware.com/spine-applying-animations/#Empty-animations'>Empty animations</a> in the Spine
* Runtimes Guide.
* @param delay If > 0, sets {@link TrackEntry#getDelay()}. If <= 0, the delay set is the duration of the previous track entry
* minus any 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.
* @return A track entry to allow further customization of animation playback. References to the track entry must not be kept
* after the {@link AnimationStateListener#dispose()} event occurs. */
* after the {@link AnimationStateListener#dispose(TrackEntry)} event occurs. */
addEmptyAnimation (trackIndex: number, mixDuration: number = 0, delay: number = 0) {
let entry = this.addAnimationWith(trackIndex, AnimationState.emptyAnimation(), false, delay);
if (delay <= 0) entry.delay += entry.mixDuration - mixDuration;
@ -636,8 +641,10 @@ export class AnimationState {
return entry;
}
/** Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix
* duration. */
/** Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix duration.
* <p>
* See <a href='https://esotericsoftware.com/spine-applying-animations/#Empty-animations'>Empty animations</a> in the Spine
* Runtimes Guide. */
setEmptyAnimations (mixDuration: number = 0) {
let oldDrainDisabled = this.queue.drainDisabled;
this.queue.drainDisabled = true;

View File

@ -51,7 +51,7 @@ export class PathConstraint implements Updatable {
bones: Array<Bone>;
/** The slot whose path attachment will be used to constrained the bones. */
target: Slot;
slot: Slot;
/** The position along the path. */
position = 0;
@ -82,9 +82,9 @@ export class PathConstraint implements Updatable {
if (!bone) throw new Error(`Couldn't find bone ${data.bones[i].name}.`);
this.bones.push(bone);
}
let target = skeleton.findSlot(data.target.name);
if (!target) throw new Error(`Couldn't find target bone ${data.target.name}`);
this.target = target;
let target = skeleton.findSlot(data.slot.name);
if (!target) throw new Error(`Couldn't find target bone ${data.slot.name}`);
this.slot = target;
this.position = data.position;
this.spacing = data.spacing;
@ -107,7 +107,7 @@ export class PathConstraint implements Updatable {
}
update (physics: Physics) {
let attachment = this.target.getAttachment();
let attachment = this.slot.getAttachment();
if (!(attachment instanceof PathAttachment)) return;
let mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY;
@ -179,7 +179,7 @@ export class PathConstraint implements Updatable {
tip = data.rotateMode == RotateMode.Chain;
else {
tip = false;
let p = this.target.bone;
let p = this.slot.bone;
offsetRotation *= p.a * p.d - p.b * p.c > 0 ? MathUtils.degRad : -MathUtils.degRad;
}
for (let i = 0, p = 3; i < boneCount; i++, p += 3) {
@ -232,7 +232,7 @@ export class PathConstraint implements Updatable {
}
computeWorldPositions (path: PathAttachment, spacesCount: number, tangents: boolean) {
let target = this.target;
let slot = this.slot;
let position = this.position;
let spaces = this.spaces, out = Utils.setArraySize(this.positions, spacesCount * 3 + 2), world: Array<number> = this.world;
let closed = path.closed;
@ -268,14 +268,14 @@ export class PathConstraint implements Updatable {
} else if (p < 0) {
if (prevCurve != PathConstraint.BEFORE) {
prevCurve = PathConstraint.BEFORE;
path.computeWorldVertices(target, 2, 4, world, 0, 2);
path.computeWorldVertices(slot, 2, 4, world, 0, 2);
}
this.addBeforePosition(p, world, 0, out, o);
continue;
} else if (p > pathLength) {
if (prevCurve != PathConstraint.AFTER) {
prevCurve = PathConstraint.AFTER;
path.computeWorldVertices(target, verticesLength - 6, 4, world, 0, 2);
path.computeWorldVertices(slot, verticesLength - 6, 4, world, 0, 2);
}
this.addAfterPosition(p - pathLength, world, 0, out, o);
continue;
@ -296,10 +296,10 @@ export class PathConstraint implements Updatable {
if (curve != prevCurve) {
prevCurve = curve;
if (closed && curve == curveCount) {
path.computeWorldVertices(target, verticesLength - 4, 4, world, 0, 2);
path.computeWorldVertices(target, 0, 4, world, 4, 2);
path.computeWorldVertices(slot, verticesLength - 4, 4, world, 0, 2);
path.computeWorldVertices(slot, 0, 4, world, 4, 2);
} else
path.computeWorldVertices(target, curve * 6 + 2, 8, world, 0, 2);
path.computeWorldVertices(slot, curve * 6 + 2, 8, world, 0, 2);
}
this.addCurvePosition(p, world[0], world[1], world[2], world[3], world[4], world[5], world[6], world[7], out, o,
tangents || (i > 0 && space == 0));
@ -311,15 +311,15 @@ export class PathConstraint implements Updatable {
if (closed) {
verticesLength += 2;
world = Utils.setArraySize(this.world, verticesLength);
path.computeWorldVertices(target, 2, verticesLength - 4, world, 0, 2);
path.computeWorldVertices(target, 0, 2, world, verticesLength - 4, 2);
path.computeWorldVertices(slot, 2, verticesLength - 4, world, 0, 2);
path.computeWorldVertices(slot, 0, 2, world, verticesLength - 4, 2);
world[verticesLength - 2] = world[0];
world[verticesLength - 1] = world[1];
} else {
curveCount--;
verticesLength -= 4;
world = Utils.setArraySize(this.world, verticesLength);
path.computeWorldVertices(target, 2, verticesLength, world, 0, 2);
path.computeWorldVertices(slot, 2, verticesLength, world, 0, 2);
}
// Curve lengths.

View File

@ -41,11 +41,11 @@ export class PathConstraintData extends ConstraintData {
bones = new Array<BoneData>();
/** The slot whose path attachment will be used to constrained the bones. */
private _target: SlotData | null = null;
public set target (slotData: SlotData) { this._target = slotData; }
public get target () {
if (!this._target) throw new Error("SlotData not set.")
else return this._target;
private _slot: SlotData | null = null;
public set slot (slotData: SlotData) { this._slot = slotData; }
public get slot () {
if (!this._slot) throw new Error("SlotData not set.")
else return this._slot;
}
/** The mode for positioning the first bone on the path. */

View File

@ -241,8 +241,7 @@ export class Skeleton {
constraint.active = constraint.target.isActive() && (!constraint.data.skinRequired || (this.skin && Utils.contains(this.skin.constraints, constraint.data, true)))!;
if (!constraint.active) return;
let target = constraint.target;
this.sortBone(target);
this.sortBone(constraint.target);
let constrained = constraint.bones;
let parent = constrained[0];
@ -263,11 +262,11 @@ export class Skeleton {
}
sortPathConstraint (constraint: PathConstraint) {
constraint.active = constraint.target.bone.isActive()
constraint.active = constraint.slot.bone.isActive()
&& (!constraint.data.skinRequired || (this.skin && Utils.contains(this.skin.constraints, constraint.data, true)))!;
if (!constraint.active) return;
let slot = constraint.target;
let slot = constraint.slot;
let slotIndex = slot.data.index;
let slotBone = slot.bone;
if (this.skin) this.sortPathConstraintAttachment(this.skin, slotIndex, slotBone);
@ -292,14 +291,14 @@ export class Skeleton {
}
sortTransformConstraint (constraint: TransformConstraint) {
constraint.active = constraint.target.isActive() && (!constraint.data.skinRequired || (this.skin && Utils.contains(this.skin.constraints, constraint.data, true)))!;
constraint.active = constraint.source.isActive() && (!constraint.data.skinRequired || (this.skin && Utils.contains(this.skin.constraints, constraint.data, true)))!;
if (!constraint.active) return;
this.sortBone(constraint.target);
this.sortBone(constraint.source);
let constrained = constraint.bones;
let boneCount = constrained.length;
if (constraint.data.localFrom) {
if (constraint.data.localSource) {
for (let i = 0; i < boneCount; i++) {
let child = constrained[i];
this.sortBone(child.parent!);

View File

@ -173,12 +173,12 @@ export class SkeletonBinary {
nn = input.readInt(true);
for (let ii = 0; ii < nn; ii++)
data.bones.push(skeletonData.bones[input.readInt(true)]);
data.target = skeletonData.bones[input.readInt(true)];
data.source = skeletonData.bones[input.readInt(true)];
let flags = input.readUnsignedByte();
data.skinRequired = (flags & 1) != 0;
data.localFrom = (flags & 2) != 0;
data.localTo = (flags & 4) != 0;
data.relative = (flags & 8) != 0;
data.localSource = (flags & 2) != 0;
data.localTarget = (flags & 4) != 0;
data.additive = (flags & 8) != 0;
data.clamp = (flags & 16) != 0;
nn = flags >> 5;
@ -219,12 +219,15 @@ export class SkeletonBinary {
}
flags = input.readByte();
if ((flags & 1) != 0) data.mixRotate = input.readFloat();
if ((flags & 2) != 0) data.mixX = input.readFloat();
if ((flags & 4) != 0) data.mixY = input.readFloat();
if ((flags & 8) != 0) data.mixScaleX = input.readFloat();
if ((flags & 16) != 0) data.mixScaleY = input.readFloat();
if ((flags & 32) != 0) data.mixShearY = input.readFloat();
if ((flags & 1) != 0) data.offsetX = input.readFloat();
if ((flags & 2) != 0) data.offsetY = input.readFloat();
if ((flags & 4) != 0) data.mixRotate = input.readFloat();
if ((flags & 8) != 0) data.mixX = input.readFloat();
if ((flags & 16) != 0) data.mixY = input.readFloat();
if ((flags & 32) != 0) data.mixScaleX = input.readFloat();
if ((flags & 64) != 0) data.mixScaleY = input.readFloat();
if ((flags & 128) != 0) data.mixShearY = input.readFloat();
skeletonData.transformConstraints.push(data);
}
@ -239,7 +242,7 @@ export class SkeletonBinary {
nn = input.readInt(true);
for (let ii = 0; ii < nn; ii++)
data.bones.push(skeletonData.bones[input.readInt(true)]);
data.target = skeletonData.slots[input.readInt(true)];
data.slot = skeletonData.slots[input.readInt(true)];
const flags = input.readByte();
data.positionMode = flags & 1;
data.spacingMode = (flags >> 1) & 3;

View File

@ -179,16 +179,17 @@ export class SkeletonJson {
data.bones.push(bone);
}
let targetName: string = constraintMap.target;
let target = skeletonData.findBone(targetName);
if (!target) throw new Error(`Couldn't find target bone ${targetName} for transform constraint ${constraintMap.name}.`);
data.target = target;
let sourceName: string = constraintMap.source;
let source = skeletonData.findBone(sourceName);
if (!source) throw new Error(`Couldn't find source bone ${sourceName} for transform constraint ${constraintMap.name}.`);
data.source = source;
data.localFrom = getValue(constraintMap, "localFrom", false);
data.localFrom = getValue(constraintMap, "localTo", false);
data.relative = getValue(constraintMap, "relative", false);
data.localSource = getValue(constraintMap, "localSource", false);
data.localTarget = getValue(constraintMap, "localTarget", false);
data.additive = getValue(constraintMap, "additive", false);
data.clamp = getValue(constraintMap, "clamp", false);
let rotate = false, x = false, y = false, scaleX = false, scaleY = false, shearY = false;
const propertiesEntries = Object.entries(getValue(constraintMap, "properties", {})) as [string, any][];
for (let ii = 0; ii < propertiesEntries.length; ii++) {
let name = propertiesEntries[ii][0];
@ -209,12 +210,36 @@ export class SkeletonJson {
let name = toEntries[t][0];
let to: ToProperty;
switch (name) {
case "rotate": to = new ToRotate(); break;
case "x": to = new ToX(); break;
case "y": to = new ToY(); break;
case "scaleX": to = new ToScaleX(); break;
case "scaleY": to = new ToScaleY(); break;
case "shearY": to = new ToShearY(); break;
case "rotate": {
rotate = true
to = new ToRotate();
break;
}
case "x": {
x = true
to = new ToX();
break;
}
case "y": {
y = true
to = new ToY();
break;
}
case "scaleX": {
scaleX = true
to = new ToScaleX();
break;
}
case "scaleY": {
scaleY = true
to = new ToScaleY();
break;
}
case "shearY": {
shearY = true
to = new ToShearY();
break;
}
default: throw new Error("Invalid transform constraint to property: " + name);
}
let toEntry = toEntries[t][1];
@ -226,12 +251,14 @@ export class SkeletonJson {
if (from.to.length > 0) data.properties.push(from);
}
data.mixRotate = getValue(constraintMap, "mixRotate", 1);
data.mixX = getValue(constraintMap, "mixX", 1);
data.mixY = getValue(constraintMap, "mixY", data.mixX);
data.mixScaleX = getValue(constraintMap, "mixScaleX", 1);
data.mixScaleY = getValue(constraintMap, "mixScaleY", data.mixScaleX);
data.mixShearY = getValue(constraintMap, "mixShearY", 1);
data.offsetX = getValue(constraintMap, "x", 0);
data.offsetY = getValue(constraintMap, "y", 0);
if (rotate) data.mixRotate = getValue(constraintMap, "mixRotate", 1);
if (x) data.mixX = getValue(constraintMap, "mixX", 1);
if (y) data.mixY = getValue(constraintMap, "mixY", data.mixX);
if (scaleX) data.mixScaleX = getValue(constraintMap, "mixScaleX", 1);
if (scaleY) data.mixScaleY = getValue(constraintMap, "mixScaleY", data.mixScaleX);
if (shearY) data.mixShearY = getValue(constraintMap, "mixShearY", 1);
skeletonData.transformConstraints.push(data);
}
@ -252,10 +279,10 @@ export class SkeletonJson {
data.bones.push(bone);
}
let targetName: string = constraintMap.target;
let target = skeletonData.findSlot(targetName);
if (!target) throw new Error(`Couldn't find target slot ${targetName} for path constraint ${constraintMap.name}.`);
data.target = target;
let slotName: string = constraintMap.slot;
let slot = skeletonData.findSlot(slotName);
if (!slot) throw new Error(`Couldn't find slot ${slotName} for path constraint ${constraintMap.name}.`);
data.slot = slot;
data.positionMode = Utils.enumValue(PositionMode, getValue(constraintMap, "positionMode", "Percent"));
data.spacingMode = Utils.enumValue(SpacingMode, getValue(constraintMap, "spacingMode", "Length"));

View File

@ -35,7 +35,7 @@ import { Vector2, MathUtils } from "./Utils.js";
/** 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.
* bones to match that of the source bone.
*
* See [Transform constraints](http://esotericsoftware.com/spine-transform-constraints) in the Spine User Guide. */
export class TransformConstraint implements Updatable {
@ -46,8 +46,8 @@ export class TransformConstraint implements Updatable {
/** The bones that will be modified by this transform constraint. */
bones: Array<Bone>;
/** The target bone whose world transform will be copied to the constrained bones. */
target: Bone;
/** The bone whose world transform will be copied to the constrained bones. */
source: Bone;
mixRotate = 0; mixX = 0; mixY = 0; mixScaleX = 0; mixScaleY = 0; mixShearY = 0;
@ -65,9 +65,9 @@ export class TransformConstraint implements Updatable {
if (!bone) throw new Error(`Couldn't find bone ${data.bones[i].name}.`);
this.bones.push(bone);
}
let target = skeleton.findBone(data.target.name);
if (!target) throw new Error(`Couldn't find target bone ${data.target.name}.`);
this.target = target;
let target = skeleton.findBone(data.source.name);
if (!target) throw new Error(`Couldn't find target bone ${data.source.name}.`);
this.source = target;
this.mixRotate = data.mixRotate;
this.mixX = data.mixX;
@ -94,8 +94,9 @@ export class TransformConstraint implements Updatable {
update (physics: Physics) {
if (this.mixRotate == 0 && this.mixX == 0 && this.mixY == 0 && this.mixScaleX == 0 && this.mixScaleY == 0 && this.mixShearY == 0) return;
const data = this.data, localFrom = data.localFrom, localTo = data.localTo, relative = data.relative, clamp = data.clamp;
const target = this.target;
const data = this.data;
const localFrom = data.localSource, localTarget = data.localTarget, additive = data.additive, clamp = data.clamp;
const source = this.source;
const fromItems = data.properties;
const fn = data.properties.length;
const bones = this.bones;
@ -103,12 +104,11 @@ export class TransformConstraint implements Updatable {
const bone = bones[i];
for (let f = 0; f < fn; f++) {
const from = fromItems[f];
const mix = from.mix(this);
if (mix != 0) {
const value = from.value(target, localFrom) - from.offset;
const toItems = from.to;
for (let t = 0, tn = from.to.length; t < tn; t++) {
var to = toItems[t];
const value = from.value(data, source, localFrom) - from.offset;
const toItems = from.to;
for (let t = 0, tn = from.to.length; t < tn; t++) {
var to = toItems[t];
if (to.mix(this) != 0) {
let clamped = to.offset + value * to.scale;
if (clamp) {
if (to.offset < to.max)
@ -116,11 +116,11 @@ export class TransformConstraint implements Updatable {
else
clamped = MathUtils.clamp(clamped, to.max, to.offset);
}
to.apply(bone, clamped, localTo, relative, mix);
to.apply(this, bone, clamped, localTarget, additive);
}
}
}
if (localTo)
if (localTarget)
bone.update(null);
else
bone.updateAppliedTransform();

View File

@ -41,29 +41,46 @@ export class TransformConstraintData extends ConstraintData {
/** The bones that will be modified by this transform constraint. */
bones = new Array<BoneData>();
/** The target bone whose world transform will be copied to the constrained bones. */
private _target: BoneData | null = null;
public set target (boneData: BoneData) { this._target = boneData; }
public get target () {
if (!this._target) throw new Error("BoneData not set.")
else return this._target;
/** The bone whose world transform will be copied to the constrained bones. */
private _source: BoneData | null = null;
public set source (source: BoneData) { this._source = source; }
public get source () {
if (!this._source) throw new Error("BoneData not set.")
else return this._source;
}
/** An offset added to the constrained bone X translation. */
offsetX = 0;
/** An offset added to the constrained bone Y translation. */
offsetY = 0;
/** A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. */
mixRotate = 0;
/** A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. */
mixX = 0;
/** A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. */
mixY = 0;
/** A percentage (0-1) that controls the mix between the constrained and unconstrained scale X. */
mixScaleX = 0;
/** A percentage (0-1) that controls the mix between the constrained and unconstrained scale Y. */
mixScaleY = 0;
/** A percentage (0-1) that controls the mix between the constrained and unconstrained shear Y. */
mixShearY = 0;
/** Reads the target bone's local transform instead of its world transform. */
localFrom = false;
/** Reads the source bone's local transform instead of its world transform. */
localSource = false;
/** Sets the constrained bones' local transforms instead of their world transforms. */
localTo = false;
localTarget = false;
/** Adds the target bone transform to the constrained bones instead of setting it absolutely. */
relative = false;
/** Adds the source bone transform to the constrained bones instead of setting it absolutely. */
additive = false;
/** Prevents constrained bones from exceeding the ranged defined by {@link ToProperty#offset} and {@link ToProperty#max}. */
clamp = false;
@ -86,10 +103,7 @@ export abstract class FromProperty {
readonly to: Array<ToProperty> = [];
/** Reads this property from the specified bone. */
abstract value (target: Bone, local: boolean): number;
/** Reads the mix for this property from the specified constraint. */
abstract mix (constraint: TransformConstraint): number;
abstract value (data: TransformConstraintData, source: Bone, local: boolean): number;
}
/** Constrained property for a {@link TransformConstraint}. */
@ -103,34 +117,37 @@ export abstract class ToProperty {
/** The scale of the {@link FromProperty} value in relation to this property. */
scale = 0;
/** Reads the mix for this property from the specified constraint. */
abstract mix (constraint: TransformConstraint): number;
/** Applies the value to this property. */
abstract apply (bone: Bone, value: number, local: boolean, relative: boolean, mix: number): void;
abstract apply (constraint: TransformConstraint, bone: Bone, value: number, local: boolean, additive: boolean): void;
}
export class FromRotate extends FromProperty {
value (target: Bone, local: boolean): number {
return local ? target.arotation : Math.atan2(target.c, target.a) * MathUtils.radDeg;
}
mix (constraint: TransformConstraint): number {
return constraint.mixRotate;
value (data: TransformConstraintData, source: Bone, local: boolean): number {
return local ? source.arotation : Math.atan2(source.c, source.a) * MathUtils.radDeg;
}
}
export class ToRotate extends ToProperty {
apply (bone: Bone, value: number, local: boolean, relative: boolean, mix: number): void {
mix (constraint: TransformConstraint): number {
return constraint.mixRotate;
}
apply (constraint: TransformConstraint, bone: Bone, value: number, local: boolean, additive: boolean): void {
if (local) {
if (!relative) value -= bone.arotation;
bone.arotation += value * mix;
if (!additive) value -= bone.arotation;
bone.arotation += value * constraint.mixRotate;
} else {
const a = bone.a, b = bone.b, c = bone.c, d = bone.d;
value *= MathUtils.degRad;
if (!relative) value -= Math.atan2(c, a);
if (!additive) value -= Math.atan2(c, a);
if (value > MathUtils.PI)
value -= MathUtils.PI2;
else if (value < -MathUtils.PI) //
value += MathUtils.PI2;
value *= mix;
value *= constraint.mixRotate;
const cos = Math.cos(value), sin = Math.sin(value);
bone.a = cos * a - sin * c;
bone.b = cos * b - sin * d;
@ -141,73 +158,73 @@ export class ToRotate extends ToProperty {
}
export class FromX extends FromProperty {
value (target: Bone, local: boolean): number {
return local ? target.ax : target.worldX;
}
mix (constraint: TransformConstraint): number {
return constraint.mixX;
value (data: TransformConstraintData, source: Bone, local: boolean): number {
return local ? source.ax + data.offsetX : data.offsetX * source.a + data.offsetY * source.b + source.worldX;
}
}
export class ToX extends ToProperty {
apply (bone: Bone, value: number, local: boolean, relative: boolean, mix: number): void {
mix (constraint: TransformConstraint): number {
return constraint.mixX;
}
apply (constraint: TransformConstraint, bone: Bone, value: number, local: boolean, additive: boolean): void {
if (local) {
if (!relative) value -= bone.ax;
bone.ax += value * mix;
if (!additive) value -= bone.ax;
bone.ax += value * constraint.mixX;
} else {
if (!relative) value -= bone.worldX;
bone.worldX += value * mix;
if (!additive) value -= bone.worldX;
bone.worldX += value * constraint.mixX;
}
}
}
export class FromY extends FromProperty {
value (target: Bone, local: boolean): number {
return local ? target.ay : target.worldY;
}
mix (constraint: TransformConstraint): number {
return constraint.mixY;
value (data: TransformConstraintData, source: Bone, local: boolean): number {
return local ? source.ay + data.offsetY : data.offsetX * source.c + data.offsetY * source.d + source.worldY;
}
}
export class ToY extends ToProperty {
apply (bone: Bone, value: number, local: boolean, relative: boolean, mix: number): void {
mix (constraint: TransformConstraint): number {
return constraint.mixY;
}
apply (constraint: TransformConstraint, bone: Bone, value: number, local: boolean, additive: boolean): void {
if (local) {
if (!relative) value -= bone.ay;
bone.ay += value * mix;
if (!additive) value -= bone.ay;
bone.ay += value * constraint.mixY;
} else {
if (!relative) value -= bone.worldY;
bone.worldY += value * mix;
if (!additive) value -= bone.worldY;
bone.worldY += value * constraint.mixY;
}
}
}
export class FromScaleX extends FromProperty {
value (target: Bone, local: boolean): number {
return local ? target.ascaleX : Math.sqrt(target.a * target.a + target.c * target.c);
}
mix (constraint: TransformConstraint): number {
return constraint.mixScaleX;
value (data: TransformConstraintData, source: Bone, local: boolean): number {
return local ? source.ascaleX : Math.sqrt(source.a * source.a + source.c * source.c);
}
}
export class ToScaleX extends ToProperty {
apply (bone: Bone, value: number, local: boolean, relative: boolean, mix: number): void {
mix (constraint: TransformConstraint): number {
return constraint.mixScaleX;
}
apply (constraint: TransformConstraint, bone: Bone, value: number, local: boolean, additive: boolean): void {
if (local) {
if (relative)
bone.ascaleX *= 1 + ((value - 1) * mix);
else if (bone.ascaleX != 0) //
bone.ascaleX = 1 + (value / bone.ascaleX - 1) * mix;
if (additive)
bone.ascaleX *= 1 + ((value - 1) * constraint.mixScaleX);
else if (bone.ascaleX != 0)
bone.ascaleX = 1 + (value / bone.ascaleX - 1) * constraint.mixScaleX;
} else {
let s: number;
if (relative)
s = 1 + (value - 1) * mix;
if (additive)
s = 1 + (value - 1) * constraint.mixScaleX;
else {
s = Math.sqrt(bone.a * bone.a + bone.c * bone.c);
if (s != 0) s = 1 + (value / s - 1) * mix;
if (s != 0) s = 1 + (value / s - 1) * constraint.mixScaleX;
}
bone.a *= s;
bone.c *= s;
@ -216,29 +233,29 @@ export class ToScaleX extends ToProperty {
}
export class FromScaleY extends FromProperty {
value (target: Bone, local: boolean): number {
return local ? target.ascaleY : Math.sqrt(target.b * target.b + target.d * target.d);
}
mix (constraint: TransformConstraint): number {
return constraint.mixScaleY;
value (data: TransformConstraintData, source: Bone, local: boolean): number {
return local ? source.ascaleY : Math.sqrt(source.b * source.b + source.d * source.d);
}
}
export class ToScaleY extends ToProperty {
apply (bone: Bone, value: number, local: boolean, relative: boolean, mix: number): void {
mix (constraint: TransformConstraint): number {
return constraint.mixScaleY;
}
apply (constraint: TransformConstraint, bone: Bone, value: number, local: boolean, additive: boolean): void {
if (local) {
if (relative)
bone.ascaleY *= 1 + ((value - 1) * mix);
if (additive)
bone.ascaleY *= 1 + ((value - 1) * constraint.mixScaleY);
else if (bone.ascaleY != 0) //
bone.ascaleY = 1 + (value / bone.ascaleY - 1) * mix;
bone.ascaleY = 1 + (value / bone.ascaleY - 1) * constraint.mixScaleY;
} else {
let s: number;
if (relative)
s = 1 + (value - 1) * mix;
if (additive)
s = 1 + (value - 1) * constraint.mixScaleY;
else {
s = Math.sqrt(bone.b * bone.b + bone.d * bone.d);
if (s != 0) s = 1 + (value / s - 1) * mix;
if (s != 0) s = 1 + (value / s - 1) * constraint.mixScaleY;
}
bone.b *= s;
bone.d *= s;
@ -247,33 +264,33 @@ export class ToScaleY extends ToProperty {
}
export class FromShearY extends FromProperty {
value (target: Bone, local: boolean): number {
return local ? target.ashearY : (Math.atan2(target.d, target.b) - Math.atan2(target.c, target.a)) * MathUtils.radDeg - 90;
}
mix (constraint: TransformConstraint): number {
return constraint.mixShearY;
value (data: TransformConstraintData, source: Bone, local: boolean): number {
return local ? source.ashearY : (Math.atan2(source.d, source.b) - Math.atan2(source.c, source.a)) * MathUtils.radDeg - 90;
}
}
export class ToShearY extends ToProperty {
apply (bone: Bone, value: number, local: boolean, relative: boolean, mix: number): void {
mix (constraint: TransformConstraint): number {
return constraint.mixShearY;
}
apply (constraint: TransformConstraint, bone: Bone, value: number, local: boolean, additive: boolean): void {
if (local) {
if (!relative) value -= bone.ashearY;
bone.ashearY += value * mix;
if (!additive) value -= bone.ashearY;
bone.ashearY += value * constraint.mixShearY;
} else {
const b = bone.b, d = bone.d, by = Math.atan2(d, b);
value = (value + 90) * MathUtils.degRad;
if (relative)
if (additive)
value -= MathUtils.PI / 2;
else {
value -= by - Math.atan2(bone.c, bone.a);
if (value > MathUtils.PI)
value -= MathUtils.PI2;
else if (value < -MathUtils.PI) //
else if (value < -MathUtils.PI)
value += MathUtils.PI2;
}
value = by + value * mix;
value = by + value * constraint.mixShearY;
const s = Math.sqrt(b * b + d * d);
bone.b = Math.cos(value) * s;
bone.d = Math.sin(value) * s;