mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-14 11:01:36 +08:00
[ts] Transform constraints alignment from reference runtime.
This commit is contained in:
parent
0f1022fb3f
commit
a46174b88b
@ -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;
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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. */
|
||||
|
||||
@ -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!);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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"));
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user