mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-10 09:08:42 +08:00
[ts] Port of physics constraints, SkeletonJson and SkeletonBinary incomplete.
This commit is contained in:
parent
5d9b361b1c
commit
c7aac73dee
@ -30,13 +30,15 @@
|
||||
import { VertexAttachment, Attachment } from "./attachments/Attachment.js";
|
||||
import { IkConstraint } from "./IkConstraint.js";
|
||||
import { PathConstraint } from "./PathConstraint.js";
|
||||
import { Skeleton } from "./Skeleton.js";
|
||||
import { Physics, Skeleton } from "./Skeleton.js";
|
||||
import { Slot } from "./Slot.js";
|
||||
import { TransformConstraint } from "./TransformConstraint.js";
|
||||
import { StringSet, Utils, MathUtils, NumberArrayLike } from "./Utils.js";
|
||||
import { Event } from "./Event.js";
|
||||
import { HasTextureRegion } from "./attachments/HasTextureRegion.js";
|
||||
import { SequenceMode, SequenceModeValues } from "./attachments/Sequence.js";
|
||||
import { PhysicsConstraint } from "./PhysicsConstraint.js";
|
||||
import { PhysicsConstraintData } from "./PhysicsConstraintData.js";
|
||||
|
||||
/** A simple container for a list of timelines and a name. */
|
||||
export class Animation {
|
||||
@ -150,7 +152,16 @@ const Property = {
|
||||
pathConstraintSpacing: 17,
|
||||
pathConstraintMix: 18,
|
||||
|
||||
sequence: 19
|
||||
physicsConstraintInertia: 19,
|
||||
physicsConstraintStrength: 20,
|
||||
physicsConstraintDamping: 21,
|
||||
physicsConstraintMass: 22,
|
||||
physicsConstraintWind: 23,
|
||||
physicsConstraintGravity: 24,
|
||||
physicsConstraintMix: 25,
|
||||
physicsConstraintReset: 26,
|
||||
|
||||
sequence: 27,
|
||||
}
|
||||
|
||||
/** The interface for all timelines. */
|
||||
@ -335,6 +346,96 @@ export abstract class CurveTimeline1 extends CurveTimeline {
|
||||
}
|
||||
return this.getBezierValue(time, i, 1/*VALUE*/, curveType - 2/*BEZIER*/);
|
||||
}
|
||||
|
||||
getRelativeValue (time: number, alpha: number, blend: MixBlend, current: number, setup: number) {
|
||||
if (time < this.frames[0]) {
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
return setup;
|
||||
case MixBlend.first:
|
||||
return current + (setup - current) * alpha;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
let value = this.getCurveValue(time);
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
return setup + value * alpha;
|
||||
case MixBlend.first:
|
||||
case MixBlend.replace:
|
||||
value += setup - current;
|
||||
}
|
||||
return current + value * alpha;
|
||||
}
|
||||
|
||||
getAbsoluteValue (time: number, alpha: number, blend: MixBlend, current: number, setup: number) {
|
||||
if (time < this.frames[0]) {
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
return setup;
|
||||
case MixBlend.first:
|
||||
return current + (setup - current) * alpha;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
let value = this.getCurveValue(time);
|
||||
if (blend == MixBlend.setup) return setup + (value - setup) * alpha;
|
||||
return current + (value - current) * alpha;
|
||||
}
|
||||
|
||||
getAbsoluteValue2 (time: number, alpha: number, blend: MixBlend , current: number, setup: number, value: number) {
|
||||
if (time < this.frames[0]) {
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
return setup;
|
||||
case MixBlend.first:
|
||||
return current + (setup - current) * alpha;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
if (blend == MixBlend.setup) return setup + (value - setup) * alpha;
|
||||
return current + (value - current) * alpha;
|
||||
}
|
||||
|
||||
getScaleValue (time: number, alpha: number, blend: MixBlend, direction: MixDirection, current: number, setup: number) {
|
||||
const frames = this.frames;
|
||||
if (time < frames[0]) {
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
return setup;
|
||||
case MixBlend.first:
|
||||
return current + (setup - current) * alpha;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
let value = this.getCurveValue(time) * setup;
|
||||
if (alpha == 1) {
|
||||
if (blend == MixBlend.add) return current + value - setup;
|
||||
return value;
|
||||
}
|
||||
// Mixing out uses sign of setup or current pose, else use sign of key.
|
||||
if (direction == MixDirection.mixOut) {
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
return setup + (Math.abs(value) * MathUtils.signum(setup) - setup) * alpha;
|
||||
case MixBlend.first:
|
||||
case MixBlend.replace:
|
||||
return current + (Math.abs(value) * MathUtils.signum(current) - current) * alpha;
|
||||
}
|
||||
} else {
|
||||
let s = 0;
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
s = Math.abs(setup) * MathUtils.signum(value);
|
||||
return s + (value - s) * alpha;
|
||||
case MixBlend.first:
|
||||
case MixBlend.replace:
|
||||
s = Math.abs(current) * MathUtils.signum(value);
|
||||
return s + (value - s) * alpha;
|
||||
}
|
||||
}
|
||||
return current + (value - setup) * alpha;
|
||||
}
|
||||
}
|
||||
|
||||
/** The base class for a {@link CurveTimeline} which sets two properties. */
|
||||
@ -371,31 +472,7 @@ export class RotateTimeline extends CurveTimeline1 implements BoneTimeline {
|
||||
|
||||
apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event> | null, alpha: number, blend: MixBlend, direction: MixDirection) {
|
||||
let bone = skeleton.bones[this.boneIndex];
|
||||
if (!bone.active) return;
|
||||
|
||||
let frames = this.frames;
|
||||
if (time < frames[0]) {
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
bone.rotation = bone.data.rotation;
|
||||
return;
|
||||
case MixBlend.first:
|
||||
bone.rotation += (bone.data.rotation - bone.rotation) * alpha;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let r = this.getCurveValue(time);
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
bone.rotation = bone.data.rotation + r * alpha;
|
||||
break;
|
||||
case MixBlend.first:
|
||||
case MixBlend.replace:
|
||||
r += bone.data.rotation - bone.rotation;
|
||||
case MixBlend.add:
|
||||
bone.rotation += r * alpha;
|
||||
}
|
||||
if (bone.active) bone.rotation = this.getRelativeValue(time, alpha, blend, bone.rotation, bone.data.rotation);
|
||||
}
|
||||
}
|
||||
|
||||
@ -478,32 +555,7 @@ export class TranslateXTimeline extends CurveTimeline1 implements BoneTimeline {
|
||||
|
||||
apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
|
||||
let bone = skeleton.bones[this.boneIndex];
|
||||
if (!bone.active) return;
|
||||
|
||||
let frames = this.frames;
|
||||
if (time < frames[0]) {
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
bone.x = bone.data.x;
|
||||
return;
|
||||
case MixBlend.first:
|
||||
bone.x += (bone.data.x - bone.x) * alpha;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let x = this.getCurveValue(time);
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
bone.x = bone.data.x + x * alpha;
|
||||
break;
|
||||
case MixBlend.first:
|
||||
case MixBlend.replace:
|
||||
bone.x += (bone.data.x + x - bone.x) * alpha;
|
||||
break;
|
||||
case MixBlend.add:
|
||||
bone.x += x * alpha;
|
||||
}
|
||||
if (bone.active) bone.x = this.getRelativeValue(time, alpha, blend, bone.x, bone.data.x);
|
||||
}
|
||||
}
|
||||
|
||||
@ -518,32 +570,7 @@ export class TranslateYTimeline extends CurveTimeline1 implements BoneTimeline {
|
||||
|
||||
apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
|
||||
let bone = skeleton.bones[this.boneIndex];
|
||||
if (!bone.active) return;
|
||||
|
||||
let frames = this.frames;
|
||||
if (time < frames[0]) {
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
bone.y = bone.data.y;
|
||||
return;
|
||||
case MixBlend.first:
|
||||
bone.y += (bone.data.y - bone.y) * alpha;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let y = this.getCurveValue(time);
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
bone.y = bone.data.y + y * alpha;
|
||||
break;
|
||||
case MixBlend.first:
|
||||
case MixBlend.replace:
|
||||
bone.y += (bone.data.y + y - bone.y) * alpha;
|
||||
break;
|
||||
case MixBlend.add:
|
||||
bone.y += y * alpha;
|
||||
}
|
||||
if (bone.active) bone.y = this.getRelativeValue(time, alpha, blend, bone.y, bone.data.y);
|
||||
}
|
||||
}
|
||||
|
||||
@ -664,59 +691,7 @@ export class ScaleXTimeline extends CurveTimeline1 implements BoneTimeline {
|
||||
|
||||
apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
|
||||
let bone = skeleton.bones[this.boneIndex];
|
||||
if (!bone.active) return;
|
||||
|
||||
let frames = this.frames;
|
||||
if (time < frames[0]) {
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
bone.scaleX = bone.data.scaleX;
|
||||
return;
|
||||
case MixBlend.first:
|
||||
bone.scaleX += (bone.data.scaleX - bone.scaleX) * alpha;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let x = this.getCurveValue(time) * bone.data.scaleX;
|
||||
if (alpha == 1) {
|
||||
if (blend == MixBlend.add)
|
||||
bone.scaleX += x - bone.data.scaleX;
|
||||
else
|
||||
bone.scaleX = x;
|
||||
} else {
|
||||
// Mixing out uses sign of setup or current pose, else use sign of key.
|
||||
let bx = 0;
|
||||
if (direction == MixDirection.mixOut) {
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
bx = bone.data.scaleX;
|
||||
bone.scaleX = bx + (Math.abs(x) * MathUtils.signum(bx) - bx) * alpha;
|
||||
break;
|
||||
case MixBlend.first:
|
||||
case MixBlend.replace:
|
||||
bx = bone.scaleX;
|
||||
bone.scaleX = bx + (Math.abs(x) * MathUtils.signum(bx) - bx) * alpha;
|
||||
break;
|
||||
case MixBlend.add:
|
||||
bone.scaleX += (x - bone.data.scaleX) * alpha;
|
||||
}
|
||||
} else {
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
bx = Math.abs(bone.data.scaleX) * MathUtils.signum(x);
|
||||
bone.scaleX = bx + (x - bx) * alpha;
|
||||
break;
|
||||
case MixBlend.first:
|
||||
case MixBlend.replace:
|
||||
bx = Math.abs(bone.scaleX) * MathUtils.signum(x);
|
||||
bone.scaleX = bx + (x - bx) * alpha;
|
||||
break;
|
||||
case MixBlend.add:
|
||||
bone.scaleX += (x - bone.data.scaleX) * alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bone.active) bone.scaleX = this.getScaleValue(time, alpha, blend, direction, bone.scaleX, bone.data.scaleX);
|
||||
}
|
||||
}
|
||||
|
||||
@ -731,59 +706,7 @@ export class ScaleYTimeline extends CurveTimeline1 implements BoneTimeline {
|
||||
|
||||
apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
|
||||
let bone = skeleton.bones[this.boneIndex];
|
||||
if (!bone.active) return;
|
||||
|
||||
let frames = this.frames;
|
||||
if (time < frames[0]) {
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
bone.scaleY = bone.data.scaleY;
|
||||
return;
|
||||
case MixBlend.first:
|
||||
bone.scaleY += (bone.data.scaleY - bone.scaleY) * alpha;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let y = this.getCurveValue(time) * bone.data.scaleY;
|
||||
if (alpha == 1) {
|
||||
if (blend == MixBlend.add)
|
||||
bone.scaleY += y - bone.data.scaleY;
|
||||
else
|
||||
bone.scaleY = y;
|
||||
} else {
|
||||
// Mixing out uses sign of setup or current pose, else use sign of key.
|
||||
let by = 0;
|
||||
if (direction == MixDirection.mixOut) {
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
by = bone.data.scaleY;
|
||||
bone.scaleY = by + (Math.abs(y) * MathUtils.signum(by) - by) * alpha;
|
||||
break;
|
||||
case MixBlend.first:
|
||||
case MixBlend.replace:
|
||||
by = bone.scaleY;
|
||||
bone.scaleY = by + (Math.abs(y) * MathUtils.signum(by) - by) * alpha;
|
||||
break;
|
||||
case MixBlend.add:
|
||||
bone.scaleY += (y - bone.data.scaleY) * alpha;
|
||||
}
|
||||
} else {
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
by = Math.abs(bone.data.scaleY) * MathUtils.signum(y);
|
||||
bone.scaleY = by + (y - by) * alpha;
|
||||
break;
|
||||
case MixBlend.first:
|
||||
case MixBlend.replace:
|
||||
by = Math.abs(bone.scaleY) * MathUtils.signum(y);
|
||||
bone.scaleY = by + (y - by) * alpha;
|
||||
break;
|
||||
case MixBlend.add:
|
||||
bone.scaleY += (y - bone.data.scaleY) * alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bone.active) bone.scaleY = this.getScaleValue(time, alpha, blend, direction, bone.scaleX, bone.data.scaleY);
|
||||
}
|
||||
}
|
||||
|
||||
@ -866,32 +789,7 @@ export class ShearXTimeline extends CurveTimeline1 implements BoneTimeline {
|
||||
|
||||
apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
|
||||
let bone = skeleton.bones[this.boneIndex];
|
||||
if (!bone.active) return;
|
||||
|
||||
let frames = this.frames;
|
||||
if (time < frames[0]) {
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
bone.shearX = bone.data.shearX;
|
||||
return;
|
||||
case MixBlend.first:
|
||||
bone.shearX += (bone.data.shearX - bone.shearX) * alpha;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let x = this.getCurveValue(time);
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
bone.shearX = bone.data.shearX + x * alpha;
|
||||
break;
|
||||
case MixBlend.first:
|
||||
case MixBlend.replace:
|
||||
bone.shearX += (bone.data.shearX + x - bone.shearX) * alpha;
|
||||
break;
|
||||
case MixBlend.add:
|
||||
bone.shearX += x * alpha;
|
||||
}
|
||||
if (bone.active) bone.shearX = this.getRelativeValue(time, alpha, blend, bone.shearX, bone.data.shearX);
|
||||
}
|
||||
}
|
||||
|
||||
@ -906,32 +804,7 @@ export class ShearYTimeline extends CurveTimeline1 implements BoneTimeline {
|
||||
|
||||
apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
|
||||
let bone = skeleton.bones[this.boneIndex];
|
||||
if (!bone.active) return;
|
||||
|
||||
let frames = this.frames;
|
||||
if (time < frames[0]) {
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
bone.shearY = bone.data.shearY;
|
||||
return;
|
||||
case MixBlend.first:
|
||||
bone.shearY += (bone.data.shearY - bone.shearY) * alpha;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let y = this.getCurveValue(time);
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
bone.shearY = bone.data.shearY + y * alpha;
|
||||
break;
|
||||
case MixBlend.first:
|
||||
case MixBlend.replace:
|
||||
bone.shearY += (bone.data.shearY + y - bone.shearY) * alpha;
|
||||
break;
|
||||
case MixBlend.add:
|
||||
bone.shearY += y * alpha;
|
||||
}
|
||||
if (bone.active) bone.shearY = this.getRelativeValue(time, alpha, blend, bone.shearX, bone.data.shearY);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1119,7 +992,7 @@ export class AlphaTimeline extends CurveTimeline1 implements SlotTimeline {
|
||||
if (!slot.bone.active) return;
|
||||
|
||||
let color = slot.color;
|
||||
if (time < this.frames[0]) { // Time is before first frame.
|
||||
if (time < this.frames[0]) {
|
||||
let setup = slot.data.color;
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
@ -1547,7 +1420,7 @@ export class DeformTimeline extends CurveTimeline implements SlotTimeline {
|
||||
}
|
||||
|
||||
deform.length = vertexCount;
|
||||
if (time >= frames[frames.length - 1]) { // Time is after last frame.
|
||||
if (time >= frames[frames.length - 1]) {
|
||||
let lastVertices = vertices[frames.length - 1];
|
||||
if (alpha == 1) {
|
||||
if (blend == MixBlend.add) {
|
||||
@ -1711,12 +1584,12 @@ export class EventTimeline extends Timeline {
|
||||
let frames = this.frames;
|
||||
let frameCount = this.frames.length;
|
||||
|
||||
if (lastTime > time) { // Fire events after last time for looped animations.
|
||||
if (lastTime > time) { // Apply after lastTime for looped animations.
|
||||
this.apply(skeleton, lastTime, Number.MAX_VALUE, firedEvents, alpha, blend, direction);
|
||||
lastTime = -1;
|
||||
} else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame.
|
||||
return;
|
||||
if (time < frames[0]) return; // Time is before first frame.
|
||||
if (time < frames[0]) return;
|
||||
|
||||
let i = 0;
|
||||
if (lastTime < frames[0])
|
||||
@ -1785,14 +1658,14 @@ export class DrawOrderTimeline extends Timeline {
|
||||
/** Changes an IK constraint's {@link IkConstraint#mix}, {@link IkConstraint#softness},
|
||||
* {@link IkConstraint#bendDirection}, {@link IkConstraint#stretch}, and {@link IkConstraint#compress}. */
|
||||
export class IkConstraintTimeline extends CurveTimeline {
|
||||
/** The index of the IK constraint slot in {@link Skeleton#ikConstraints} that will be changed. */
|
||||
ikConstraintIndex: number = 0;
|
||||
/** The index of the IK constraint in {@link Skeleton#getIkConstraints()} that will be changed when this timeline is */
|
||||
constraintIndex: number = 0;
|
||||
|
||||
constructor (frameCount: number, bezierCount: number, ikConstraintIndex: number) {
|
||||
super(frameCount, bezierCount, [
|
||||
Property.ikConstraint + "|" + ikConstraintIndex
|
||||
]);
|
||||
this.ikConstraintIndex = ikConstraintIndex;
|
||||
this.constraintIndex = ikConstraintIndex;
|
||||
}
|
||||
|
||||
getFrameEntries () {
|
||||
@ -1811,7 +1684,7 @@ export class IkConstraintTimeline extends CurveTimeline {
|
||||
}
|
||||
|
||||
apply (skeleton: Skeleton, lastTime: number, time: number, firedEvents: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
|
||||
let constraint: IkConstraint = skeleton.ikConstraints[this.ikConstraintIndex];
|
||||
let constraint: IkConstraint = skeleton.ikConstraints[this.constraintIndex];
|
||||
if (!constraint.active) return;
|
||||
|
||||
let frames = this.frames;
|
||||
@ -1884,13 +1757,13 @@ export class IkConstraintTimeline extends CurveTimeline {
|
||||
* {@link TransformConstraint#scaleMix}, and {@link TransformConstraint#shearMix}. */
|
||||
export class TransformConstraintTimeline extends CurveTimeline {
|
||||
/** The index of the transform constraint slot in {@link Skeleton#transformConstraints} that will be changed. */
|
||||
transformConstraintIndex: number = 0;
|
||||
constraintIndex: number = 0;
|
||||
|
||||
constructor (frameCount: number, bezierCount: number, transformConstraintIndex: number) {
|
||||
super(frameCount, bezierCount, [
|
||||
Property.transformConstraint + "|" + transformConstraintIndex
|
||||
]);
|
||||
this.transformConstraintIndex = transformConstraintIndex;
|
||||
this.constraintIndex = transformConstraintIndex;
|
||||
}
|
||||
|
||||
getFrameEntries () {
|
||||
@ -1912,7 +1785,7 @@ export class TransformConstraintTimeline extends CurveTimeline {
|
||||
}
|
||||
|
||||
apply (skeleton: Skeleton, lastTime: number, time: number, firedEvents: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
|
||||
let constraint: TransformConstraint = skeleton.transformConstraints[this.transformConstraintIndex];
|
||||
let constraint: TransformConstraint = skeleton.transformConstraints[this.constraintIndex];
|
||||
if (!constraint.active) return;
|
||||
|
||||
let frames = this.frames;
|
||||
@ -1996,85 +1869,52 @@ export class TransformConstraintTimeline extends CurveTimeline {
|
||||
|
||||
/** Changes a path constraint's {@link PathConstraint#position}. */
|
||||
export class PathConstraintPositionTimeline extends CurveTimeline1 {
|
||||
/** The index of the path constraint slot in {@link Skeleton#pathConstraints} that will be changed. */
|
||||
pathConstraintIndex: number = 0;
|
||||
/** The index of the path constraint in {@link Skeleton#getPathConstraints()} that will be changed when this timeline is
|
||||
* applied. */
|
||||
constraintIndex: number = 0;
|
||||
|
||||
constructor (frameCount: number, bezierCount: number, pathConstraintIndex: number) {
|
||||
super(frameCount, bezierCount, Property.pathConstraintPosition + "|" + pathConstraintIndex);
|
||||
this.pathConstraintIndex = pathConstraintIndex;
|
||||
this.constraintIndex = pathConstraintIndex;
|
||||
}
|
||||
|
||||
apply (skeleton: Skeleton, lastTime: number, time: number, firedEvents: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
|
||||
let constraint: PathConstraint = skeleton.pathConstraints[this.pathConstraintIndex];
|
||||
if (!constraint.active) return;
|
||||
|
||||
let frames = this.frames;
|
||||
if (time < frames[0]) {
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
constraint.position = constraint.data.position;
|
||||
return;
|
||||
case MixBlend.first:
|
||||
constraint.position += (constraint.data.position - constraint.position) * alpha;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let position = this.getCurveValue(time);
|
||||
|
||||
if (blend == MixBlend.setup)
|
||||
constraint.position = constraint.data.position + (position - constraint.data.position) * alpha;
|
||||
else
|
||||
constraint.position += (position - constraint.position) * alpha;
|
||||
let constraint: PathConstraint = skeleton.pathConstraints[this.constraintIndex];
|
||||
if (constraint.active)
|
||||
constraint.position = this.getAbsoluteValue(time, alpha, blend, constraint.position, constraint.data.position);
|
||||
}
|
||||
}
|
||||
|
||||
/** Changes a path constraint's {@link PathConstraint#spacing}. */
|
||||
export class PathConstraintSpacingTimeline extends CurveTimeline1 {
|
||||
/** The index of the path constraint slot in {@link Skeleton#getPathConstraints()} that will be changed. */
|
||||
pathConstraintIndex = 0;
|
||||
/** The index of the path constraint in {@link Skeleton#getPathConstraints()} that will be changed when this timeline is
|
||||
* applied. */
|
||||
constraintIndex = 0;
|
||||
|
||||
constructor (frameCount: number, bezierCount: number, pathConstraintIndex: number) {
|
||||
super(frameCount, bezierCount, Property.pathConstraintSpacing + "|" + pathConstraintIndex);
|
||||
this.pathConstraintIndex = pathConstraintIndex;
|
||||
this.constraintIndex = pathConstraintIndex;
|
||||
}
|
||||
|
||||
apply (skeleton: Skeleton, lastTime: number, time: number, firedEvents: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
|
||||
let constraint: PathConstraint = skeleton.pathConstraints[this.pathConstraintIndex];
|
||||
if (!constraint.active) return;
|
||||
|
||||
let frames = this.frames;
|
||||
if (time < frames[0]) {
|
||||
switch (blend) {
|
||||
case MixBlend.setup:
|
||||
constraint.spacing = constraint.data.spacing;
|
||||
return;
|
||||
case MixBlend.first:
|
||||
constraint.spacing += (constraint.data.spacing - constraint.spacing) * alpha;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let spacing = this.getCurveValue(time);
|
||||
|
||||
if (blend == MixBlend.setup)
|
||||
constraint.spacing = constraint.data.spacing + (spacing - constraint.data.spacing) * alpha;
|
||||
else
|
||||
constraint.spacing += (spacing - constraint.spacing) * alpha;
|
||||
let constraint: PathConstraint = skeleton.pathConstraints[this.constraintIndex];
|
||||
if (constraint.active)
|
||||
constraint.spacing = this.getAbsoluteValue(time, alpha, blend, constraint.spacing, constraint.data.spacing);
|
||||
}
|
||||
}
|
||||
|
||||
/** Changes a transform constraint's {@link PathConstraint#getMixRotate()}, {@link PathConstraint#getMixX()}, and
|
||||
* {@link PathConstraint#getMixY()}. */
|
||||
export class PathConstraintMixTimeline extends CurveTimeline {
|
||||
/** The index of the path constraint slot in {@link Skeleton#getPathConstraints()} that will be changed. */
|
||||
pathConstraintIndex = 0;
|
||||
/** The index of the path constraint in {@link Skeleton#getPathConstraints()} that will be changed when this timeline is
|
||||
* applied. */
|
||||
constraintIndex = 0;
|
||||
|
||||
constructor (frameCount: number, bezierCount: number, pathConstraintIndex: number) {
|
||||
super(frameCount, bezierCount, [
|
||||
Property.pathConstraintMix + "|" + pathConstraintIndex
|
||||
]);
|
||||
this.pathConstraintIndex = pathConstraintIndex;
|
||||
this.constraintIndex = pathConstraintIndex;
|
||||
}
|
||||
|
||||
getFrameEntries () {
|
||||
@ -2091,7 +1931,7 @@ export class PathConstraintMixTimeline extends CurveTimeline {
|
||||
}
|
||||
|
||||
apply (skeleton: Skeleton, lastTime: number, time: number, firedEvents: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
|
||||
let constraint: PathConstraint = skeleton.pathConstraints[this.pathConstraintIndex];
|
||||
let constraint: PathConstraint = skeleton.pathConstraints[this.constraintIndex];
|
||||
if (!constraint.active) return;
|
||||
|
||||
let frames = this.frames;
|
||||
@ -2148,6 +1988,257 @@ export class PathConstraintMixTimeline extends CurveTimeline {
|
||||
}
|
||||
}
|
||||
|
||||
/** The base class for most {@link PhysicsConstraint} timelines. */
|
||||
export abstract class PhysicsConstraintTimeline extends CurveTimeline1 {
|
||||
/** The index of the physics constraint in {@link Skeleton#getPhysicsConstraints()} that will be changed when this timeline
|
||||
* is applied, or -1 if all physics constraints in the skeleton will be changed. */
|
||||
constraintIndex = 0;
|
||||
|
||||
/** @param physicsConstraintIndex -1 for all physics constraints in the skeleton. */
|
||||
constructor (frameCount: number, bezierCount: number, physicsConstraintIndex: number, property: number) {
|
||||
super(frameCount, bezierCount, property + "|" + physicsConstraintIndex);
|
||||
this.constraintIndex = physicsConstraintIndex;
|
||||
}
|
||||
|
||||
apply (skeleton: Skeleton, lastTime: number, time: number, firedEvents: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
|
||||
let constraint: PhysicsConstraint;
|
||||
if (this.constraintIndex == -1) {
|
||||
const value = time >= this.frames[0] ? this.getCurveValue(time) : 0;
|
||||
|
||||
for (const constraint of skeleton.physicsConstraints) {
|
||||
if (constraint.active && this.global(constraint.data))
|
||||
this.set(constraint, this.getAbsoluteValue2(time, alpha, blend, this.get(constraint), this.setup(constraint), value));
|
||||
}
|
||||
} else {
|
||||
constraint = skeleton.physicsConstraints[this.constraintIndex];
|
||||
if (constraint.active) this.set(constraint, this.getAbsoluteValue(time, alpha, blend, this.get(constraint), this.setup(constraint)));
|
||||
}
|
||||
}
|
||||
|
||||
abstract setup (constraint: PhysicsConstraint): number;
|
||||
|
||||
abstract get (constraint: PhysicsConstraint): number;
|
||||
|
||||
abstract set (constraint: PhysicsConstraint, value: number): void;
|
||||
|
||||
abstract global (constraint: PhysicsConstraintData): boolean;
|
||||
}
|
||||
|
||||
/** Changes a physics constraint's {@link PhysicsConstraint#getInertia()}. */
|
||||
export class PhysicsConstraintInertiaTimeline extends PhysicsConstraintTimeline {
|
||||
constructor (frameCount: number, bezierCount: number, physicsConstraintIndex: number, property: number) {
|
||||
super(frameCount, bezierCount, physicsConstraintIndex, Property.physicsConstraintInertia);
|
||||
}
|
||||
|
||||
setup (constraint: PhysicsConstraint): number {
|
||||
return constraint.data.inertia;
|
||||
}
|
||||
|
||||
get (constraint: PhysicsConstraint): number {
|
||||
return constraint.inertia;
|
||||
}
|
||||
|
||||
set (constraint: PhysicsConstraint, value: number): void {
|
||||
constraint.inertia = value;
|
||||
}
|
||||
|
||||
global (constraint: PhysicsConstraintData): boolean {
|
||||
return constraint.inertiaGlobal;
|
||||
}
|
||||
}
|
||||
|
||||
/** Changes a physics constraint's {@link PhysicsConstraint#getStrength()}. */
|
||||
export class PhysicsConstraintStrengthTimeline extends PhysicsConstraintTimeline {
|
||||
constructor (frameCount: number, bezierCount: number, physicsConstraintIndex: number, property: number) {
|
||||
super(frameCount, bezierCount, physicsConstraintIndex, Property.physicsConstraintStrength);
|
||||
}
|
||||
|
||||
setup (constraint: PhysicsConstraint): number {
|
||||
return constraint.data.strength;
|
||||
}
|
||||
|
||||
get (constraint: PhysicsConstraint): number {
|
||||
return constraint.strength;
|
||||
}
|
||||
|
||||
set (constraint: PhysicsConstraint, value: number): void {
|
||||
constraint.strength = value;
|
||||
}
|
||||
|
||||
global (constraint: PhysicsConstraintData): boolean {
|
||||
return constraint.strengthGlobal;
|
||||
}
|
||||
}
|
||||
|
||||
/** Changes a physics constraint's {@link PhysicsConstraint#getDamping()}. */
|
||||
export class PhysicsConstraintDampingTimeline extends PhysicsConstraintTimeline {
|
||||
constructor (frameCount: number, bezierCount: number, physicsConstraintIndex: number, property: number) {
|
||||
super(frameCount, bezierCount, physicsConstraintIndex, Property.physicsConstraintDamping);
|
||||
}
|
||||
|
||||
setup (constraint: PhysicsConstraint): number {
|
||||
return constraint.data.damping;
|
||||
}
|
||||
|
||||
get (constraint: PhysicsConstraint): number {
|
||||
return constraint.damping;
|
||||
}
|
||||
|
||||
set (constraint: PhysicsConstraint, value: number): void {
|
||||
constraint.damping = value;
|
||||
}
|
||||
|
||||
global (constraint: PhysicsConstraintData): boolean {
|
||||
return constraint.dampingGlobal;
|
||||
}
|
||||
}
|
||||
|
||||
/** Changes a physics constraint's {@link PhysicsConstraint#getMassInverse()}. The timeline values are not inverted. */
|
||||
export class PhysicsConstraintMassTimeline extends PhysicsConstraintTimeline {
|
||||
constructor (frameCount: number, bezierCount: number, physicsConstraintIndex: number, property: number) {
|
||||
super(frameCount, bezierCount, physicsConstraintIndex, Property.physicsConstraintMass);
|
||||
}
|
||||
|
||||
setup (constraint: PhysicsConstraint): number {
|
||||
return 1 / constraint.data.massInverse;
|
||||
}
|
||||
|
||||
get (constraint: PhysicsConstraint): number {
|
||||
return 1 / constraint.massInverse;
|
||||
}
|
||||
|
||||
set (constraint: PhysicsConstraint, value: number): void {
|
||||
constraint.massInverse = 1 / value;
|
||||
}
|
||||
|
||||
global (constraint: PhysicsConstraintData): boolean {
|
||||
return constraint.massGlobal;
|
||||
}
|
||||
}
|
||||
|
||||
/** Changes a physics constraint's {@link PhysicsConstraint#getWind()}. */
|
||||
export class PhysicsConstraintWindTimeline extends PhysicsConstraintTimeline {
|
||||
constructor (frameCount: number, bezierCount: number, physicsConstraintIndex: number, property: number) {
|
||||
super(frameCount, bezierCount, physicsConstraintIndex, Property.physicsConstraintWind);
|
||||
}
|
||||
|
||||
setup (constraint: PhysicsConstraint): number {
|
||||
return constraint.data.wind;
|
||||
}
|
||||
|
||||
get (constraint: PhysicsConstraint): number {
|
||||
return constraint.wind;
|
||||
}
|
||||
|
||||
set (constraint: PhysicsConstraint, value: number): void {
|
||||
constraint.wind = value;
|
||||
}
|
||||
|
||||
global (constraint: PhysicsConstraintData): boolean {
|
||||
return constraint.windGlobal;
|
||||
}
|
||||
}
|
||||
|
||||
/** Changes a physics constraint's {@link PhysicsConstraint#getGravity()}. */
|
||||
export class PhysicsConstraintGravityTimeline extends PhysicsConstraintTimeline {
|
||||
constructor (frameCount: number, bezierCount: number, physicsConstraintIndex: number, property: number) {
|
||||
super(frameCount, bezierCount, physicsConstraintIndex, Property.physicsConstraintGravity);
|
||||
}
|
||||
|
||||
setup (constraint: PhysicsConstraint): number {
|
||||
return constraint.data.gravity;
|
||||
}
|
||||
|
||||
get (constraint: PhysicsConstraint): number {
|
||||
return constraint.gravity;
|
||||
}
|
||||
|
||||
set (constraint: PhysicsConstraint, value: number): void {
|
||||
constraint.gravity = value;
|
||||
}
|
||||
|
||||
global (constraint: PhysicsConstraintData): boolean {
|
||||
return constraint.gravityGlobal;
|
||||
}
|
||||
}
|
||||
|
||||
/** Changes a physics constraint's {@link PhysicsConstraint#getMix()}. */
|
||||
export class PhysicsConstraintMixTimeline extends PhysicsConstraintTimeline {
|
||||
constructor (frameCount: number, bezierCount: number, physicsConstraintIndex: number, property: number) {
|
||||
super(frameCount, bezierCount, physicsConstraintIndex, Property.physicsConstraintMix);
|
||||
}
|
||||
|
||||
setup (constraint: PhysicsConstraint): number {
|
||||
return constraint.data.mix;
|
||||
}
|
||||
|
||||
get (constraint: PhysicsConstraint): number {
|
||||
return constraint.mix;
|
||||
}
|
||||
|
||||
set (constraint: PhysicsConstraint, value: number): void {
|
||||
constraint.mix = value;
|
||||
}
|
||||
|
||||
global (constraint: PhysicsConstraintData): boolean {
|
||||
return constraint.mixGlobal;
|
||||
}
|
||||
}
|
||||
|
||||
/** Resets a physics constraint when specific animation times are reached. */
|
||||
export class PhysicsConstraintResetTimeline extends Timeline {
|
||||
private static propertyIds: string[] = [Property.physicsConstraintReset.toString()];
|
||||
|
||||
/** The index of the physics constraint in {@link Skeleton#getPhysicsConstraints()} that will be reset when this timeline is
|
||||
* applied, or -1 if all physics constraints in the skeleton will be reset. */
|
||||
constraintIndex: number;
|
||||
|
||||
/** @param physicsConstraintIndex -1 for all physics constraints in the skeleton. */
|
||||
constructor (frameCount: number, physicsConstraintIndex: number) {
|
||||
super(frameCount, PhysicsConstraintResetTimeline.propertyIds);
|
||||
this.constraintIndex = physicsConstraintIndex;
|
||||
}
|
||||
|
||||
getFrameCount () {
|
||||
return this.frames.length;
|
||||
}
|
||||
|
||||
/** Sets the time for the specified frame.
|
||||
* @param frame Between 0 and <code>frameCount</code>, inclusive. */
|
||||
setFrame (frame: number, time: number) {
|
||||
this.frames[frame] = time;
|
||||
}
|
||||
|
||||
/** Resets the physics constraint when frames > <code>lastTime</code> and <= <code>time</code>. */
|
||||
apply (skeleton: Skeleton, lastTime: number, time: number, firedEvents: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
|
||||
|
||||
let constraint: PhysicsConstraint | undefined;
|
||||
if (this.constraintIndex != -1) {
|
||||
constraint = skeleton.physicsConstraints[this.constraintIndex];
|
||||
if (!constraint.active) return;
|
||||
}
|
||||
|
||||
const frames = this.frames;
|
||||
|
||||
if (lastTime > time) { // Apply after lastTime for looped animations.
|
||||
this.apply(skeleton, lastTime, Number.MAX_VALUE, [], alpha, blend, direction);
|
||||
lastTime = -1;
|
||||
} else if (lastTime >= frames[frames.length - 1]) // Last time is after last frame.
|
||||
return;
|
||||
if (time < frames[0]) return;
|
||||
|
||||
if (lastTime < frames[0] || time >= frames[Timeline.search1(frames, lastTime) + 1]) {
|
||||
if (constraint != null)
|
||||
constraint.reset();
|
||||
else {
|
||||
for (const constraint of skeleton.physicsConstraints) {
|
||||
if (constraint.active) constraint.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Changes a slot's {@link Slot#getSequenceIndex()} for an attachment's {@link Sequence}. */
|
||||
export class SequenceTimeline extends Timeline implements SlotTimeline {
|
||||
static ENTRIES = 3;
|
||||
@ -2199,7 +2290,7 @@ export class SequenceTimeline extends Timeline implements SlotTimeline {
|
||||
}
|
||||
|
||||
let frames = this.frames;
|
||||
if (time < frames[0]) { // Time is before first frame.
|
||||
if (time < frames[0]) {
|
||||
if (blend == MixBlend.setup || blend == MixBlend.first) slot.sequenceIndex = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
@ -173,11 +173,13 @@ export class AnimationState {
|
||||
let blend: MixBlend = i == 0 ? MixBlend.first : current.mixBlend;
|
||||
|
||||
// Apply mixing from entries first.
|
||||
let mix = current.alpha;
|
||||
let alpha = current.alpha;
|
||||
if (current.mixingFrom)
|
||||
mix *= this.applyMixingFrom(current, skeleton, blend);
|
||||
alpha *= this.applyMixingFrom(current, skeleton, blend);
|
||||
else if (current.trackTime >= current.trackEnd && !current.next)
|
||||
mix = 0;
|
||||
alpha = 0;
|
||||
let attachments = alpha >= current.alphaAttachmentThreshold;
|
||||
|
||||
|
||||
// Apply current entry.
|
||||
let animationLast = current.animationLast, animationTime = current.getAnimationTime(), applyTime = animationTime;
|
||||
@ -188,17 +190,18 @@ export class AnimationState {
|
||||
}
|
||||
let timelines = current.animation!.timelines;
|
||||
let timelineCount = timelines.length;
|
||||
if ((i == 0 && mix == 1) || blend == MixBlend.add) {
|
||||
if ((i == 0 && alpha == 1) || blend == MixBlend.add) {
|
||||
if (i == 0) attachments = true;
|
||||
for (let ii = 0; ii < timelineCount; ii++) {
|
||||
// Fixes issue #302 on IOS9 where mix, blend sometimes became undefined and caused assets
|
||||
// to sometimes stop rendering when using color correction, as their RGBA values become NaN.
|
||||
// (https://github.com/pixijs/pixi-spine/issues/302)
|
||||
Utils.webkit602BugfixHelper(mix, blend);
|
||||
Utils.webkit602BugfixHelper(alpha, blend);
|
||||
var timeline = timelines[ii];
|
||||
if (timeline instanceof AttachmentTimeline)
|
||||
this.applyAttachmentTimeline(timeline, skeleton, applyTime, blend, true);
|
||||
this.applyAttachmentTimeline(timeline, skeleton, applyTime, blend, attachments);
|
||||
else
|
||||
timeline.apply(skeleton, animationLast, applyTime, applyEvents, mix, blend, MixDirection.mixIn);
|
||||
timeline.apply(skeleton, animationLast, applyTime, applyEvents, alpha, blend, MixDirection.mixIn);
|
||||
}
|
||||
} else {
|
||||
let timelineMode = current.timelineMode;
|
||||
@ -211,13 +214,13 @@ export class AnimationState {
|
||||
let timeline = timelines[ii];
|
||||
let timelineBlend = timelineMode[ii] == SUBSEQUENT ? blend : MixBlend.setup;
|
||||
if (!shortestRotation && timeline instanceof RotateTimeline) {
|
||||
this.applyRotateTimeline(timeline, skeleton, applyTime, mix, timelineBlend, current.timelinesRotation, ii << 1, firstFrame);
|
||||
this.applyRotateTimeline(timeline, skeleton, applyTime, alpha, timelineBlend, current.timelinesRotation, ii << 1, firstFrame);
|
||||
} else if (timeline instanceof AttachmentTimeline) {
|
||||
this.applyAttachmentTimeline(timeline, skeleton, applyTime, blend, true);
|
||||
this.applyAttachmentTimeline(timeline, skeleton, applyTime, blend, attachments);
|
||||
} else {
|
||||
// This fixes the WebKit 602 specific issue described at http://esotericsoftware.com/forum/iOS-10-disappearing-graphics-10109
|
||||
Utils.webkit602BugfixHelper(mix, blend);
|
||||
timeline.apply(skeleton, animationLast, applyTime, applyEvents, mix, timelineBlend, MixDirection.mixIn);
|
||||
Utils.webkit602BugfixHelper(alpha, blend);
|
||||
timeline.apply(skeleton, animationLast, applyTime, applyEvents, alpha, timelineBlend, MixDirection.mixIn);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -259,7 +262,7 @@ export class AnimationState {
|
||||
if (blend != MixBlend.first) blend = from.mixBlend;
|
||||
}
|
||||
|
||||
let attachments = mix < from.attachmentThreshold, drawOrder = mix < from.drawOrderThreshold;
|
||||
let attachments = mix < from.mixAttachmentThreshold, drawOrder = mix < from.mixDrawOrderThreshold;
|
||||
let timelines = from.animation!.timelines;
|
||||
let timelineCount = timelines.length;
|
||||
let alphaHold = from.alpha * to.interruptAlpha, alphaMix = alphaHold * (1 - mix);
|
||||
@ -316,7 +319,7 @@ export class AnimationState {
|
||||
if (!shortestRotation && timeline instanceof RotateTimeline)
|
||||
this.applyRotateTimeline(timeline, skeleton, applyTime, alpha, timelineBlend, from.timelinesRotation, i << 1, firstFrame);
|
||||
else if (timeline instanceof AttachmentTimeline)
|
||||
this.applyAttachmentTimeline(timeline, skeleton, applyTime, timelineBlend, attachments);
|
||||
this.applyAttachmentTimeline(timeline, skeleton, applyTime, timelineBlend, attachments && alpha >= from.alphaAttachmentThreshold);
|
||||
else {
|
||||
// This fixes the WebKit 602 specific issue described at http://esotericsoftware.com/forum/iOS-10-disappearing-graphics-10109
|
||||
Utils.webkit602BugfixHelper(alpha, blend);
|
||||
@ -385,7 +388,7 @@ export class AnimationState {
|
||||
|
||||
// Mix between rotations using the direction of the shortest route on the first frame while detecting crosses.
|
||||
let total = 0, diff = r2 - r1;
|
||||
diff -= (16384 - ((16384.499999999996 - diff / 360) | 0)) * 360;
|
||||
diff -= Math.ceil(diff / 360 - 0.5) * 360;
|
||||
if (diff == 0) {
|
||||
total = timelinesRotation[i];
|
||||
} else {
|
||||
@ -661,8 +664,9 @@ export class AnimationState {
|
||||
entry.shortestRotation = false;
|
||||
|
||||
entry.eventThreshold = 0;
|
||||
entry.attachmentThreshold = 0;
|
||||
entry.drawOrderThreshold = 0;
|
||||
entry.alphaAttachmentThreshold = 0;
|
||||
entry.mixAttachmentThreshold = 0;
|
||||
entry.mixDrawOrderThreshold = 0;
|
||||
|
||||
entry.animationStart = 0;
|
||||
entry.animationEnd = animation.duration;
|
||||
@ -843,12 +847,16 @@ export class TrackEntry {
|
||||
/** When the mix percentage ({@link #mixtime} / {@link #mixDuration}) is less than the
|
||||
* `attachmentThreshold`, attachment timelines are applied while this animation is being mixed out. Defaults to
|
||||
* 0, so attachment timelines are not applied while this animation is being mixed out. */
|
||||
attachmentThreshold: number = 0;
|
||||
mixAttachmentThreshold: number = 0;
|
||||
|
||||
/** When the mix percentage ({@link #mixTime} / {@link #mixDuration}) is less than the
|
||||
* `drawOrderThreshold`, draw order timelines are applied while this animation is being mixed out. Defaults to 0,
|
||||
* so draw order timelines are not applied while this animation is being mixed out. */
|
||||
drawOrderThreshold: number = 0;
|
||||
/** When {@link #getAlpha()} is greater than <code>alphaAttachmentThreshold</code>, attachment timelines are applied.
|
||||
* Defaults to 0, so attachment timelines are always applied. */
|
||||
alphaAttachmentThreshold: number = 0;
|
||||
|
||||
/** When the mix percentage ({@link #getMixTime()} / {@link #getMixDuration()}) is less than the
|
||||
* <code>mixDrawOrderThreshold</code>, draw order timelines are applied while this animation is being mixed out. Defaults to
|
||||
* 0, so draw order timelines are not applied while this animation is being mixed out. */
|
||||
mixDrawOrderThreshold: number = 0;
|
||||
|
||||
/** Seconds when this animation starts, both initially and after looping. Defaults to 0.
|
||||
*
|
||||
@ -930,7 +938,17 @@ export class TrackEntry {
|
||||
* When using {@link AnimationState#addAnimation()} with a `delay` <= 0, note the
|
||||
* {@link #delay} is set using the mix duration from the {@link AnimationStateData}, not a mix duration set
|
||||
* afterward. */
|
||||
mixDuration: number = 0; interruptAlpha: number = 0; totalAlpha: number = 0;
|
||||
_mixDuration: number = 0; interruptAlpha: number = 0; totalAlpha: number = 0;
|
||||
|
||||
get mixDuration () {
|
||||
return this._mixDuration;
|
||||
}
|
||||
|
||||
set mixDuration (mixDuration: number) {
|
||||
this.mixDuration = mixDuration;
|
||||
if (this.previous != null && this.delay <= 0) this.delay += this.previous.getTrackComplete() - mixDuration;
|
||||
this.delay = this.delay;
|
||||
}
|
||||
|
||||
/** Controls how properties keyed in the animation are mixed with lower tracks. Defaults to {@link MixBlend#replace}, which
|
||||
* replaces the values from the lower tracks with the animation values. {@link MixBlend#add} adds the animation values to
|
||||
@ -998,6 +1016,13 @@ export class TrackEntry {
|
||||
}
|
||||
return this.trackTime; // Next update.
|
||||
}
|
||||
|
||||
/** Returns true if this track entry has been applied at least once.
|
||||
* <p>
|
||||
* See {@link AnimationState#apply(Skeleton)}. */
|
||||
wasApplied () {
|
||||
return this.nextTrackLast != -1;
|
||||
}
|
||||
}
|
||||
|
||||
export class EventQueue {
|
||||
|
||||
@ -28,7 +28,7 @@
|
||||
*****************************************************************************/
|
||||
|
||||
import { BoneData, TransformMode } from "./BoneData.js";
|
||||
import { Skeleton } from "./Skeleton.js";
|
||||
import { Physics, Skeleton } from "./Skeleton.js";
|
||||
import { Updatable } from "./Updatable.js";
|
||||
import { MathUtils, Vector2 } from "./Utils.js";
|
||||
|
||||
@ -130,7 +130,7 @@ export class Bone implements Updatable {
|
||||
}
|
||||
|
||||
/** Computes the world transform using the parent bone and this bone's local applied transform. */
|
||||
update () {
|
||||
update (physics: Physics) {
|
||||
this.updateWorldTransformWith(this.ax, this.ay, this.arotation, this.ascaleX, this.ascaleY, this.ashearX, this.ashearY);
|
||||
}
|
||||
|
||||
@ -158,13 +158,13 @@ export class Bone implements Updatable {
|
||||
let parent = this.parent;
|
||||
if (!parent) { // Root bone.
|
||||
let skeleton = this.skeleton;
|
||||
let rotationY = rotation + 90 + shearY;
|
||||
let sx = skeleton.scaleX;
|
||||
let sy = skeleton.scaleY;
|
||||
this.a = MathUtils.cosDeg(rotation + shearX) * scaleX * sx;
|
||||
this.b = MathUtils.cosDeg(rotationY) * scaleY * sx;
|
||||
this.c = MathUtils.sinDeg(rotation + shearX) * scaleX * sy;
|
||||
this.d = MathUtils.sinDeg(rotationY) * scaleY * sy;
|
||||
const sx = skeleton.scaleX, sy = skeleton.scaleY;
|
||||
const rx = (rotation + shearX) * MathUtils.degRad;
|
||||
const ry = (rotation + 90 + shearY) * MathUtils.degRad;
|
||||
this.a = Math.cos(rx) * scaleX * sx;
|
||||
this.b = Math.cos(ry) * scaleY * sx;
|
||||
this.c = Math.sin(rx) * scaleX * sy;
|
||||
this.d = Math.sin(ry) * scaleY * sy;
|
||||
this.worldX = x * sx + skeleton.x;
|
||||
this.worldY = y * sy + skeleton.y;
|
||||
return;
|
||||
@ -176,11 +176,12 @@ export class Bone implements Updatable {
|
||||
|
||||
switch (this.data.transformMode) {
|
||||
case TransformMode.Normal: {
|
||||
let rotationY = rotation + 90 + shearY;
|
||||
let la = MathUtils.cosDeg(rotation + shearX) * scaleX;
|
||||
let lb = MathUtils.cosDeg(rotationY) * scaleY;
|
||||
let lc = MathUtils.sinDeg(rotation + shearX) * scaleX;
|
||||
let ld = MathUtils.sinDeg(rotationY) * scaleY;
|
||||
const rx = (rotation + shearX) * MathUtils.degRad;
|
||||
const ry = (rotation + 90 + shearY) * MathUtils.degRad;
|
||||
const la = Math.cos(rx) * scaleX;
|
||||
const lb = Math.cos(ry) * scaleY;
|
||||
const lc = Math.sin(rx) * scaleX;
|
||||
const ld = Math.sin(ry) * scaleY;
|
||||
this.a = pa * la + pb * lc;
|
||||
this.b = pa * lb + pb * ld;
|
||||
this.c = pc * la + pd * lc;
|
||||
@ -188,11 +189,12 @@ export class Bone implements Updatable {
|
||||
return;
|
||||
}
|
||||
case TransformMode.OnlyTranslation: {
|
||||
let rotationY = rotation + 90 + shearY;
|
||||
this.a = MathUtils.cosDeg(rotation + shearX) * scaleX;
|
||||
this.b = MathUtils.cosDeg(rotationY) * scaleY;
|
||||
this.c = MathUtils.sinDeg(rotation + shearX) * scaleX;
|
||||
this.d = MathUtils.sinDeg(rotationY) * scaleY;
|
||||
const rx = (rotation + shearX) * MathUtils.degRad;
|
||||
const ry = (rotation + 90 + shearY) * MathUtils.degRad;
|
||||
this.a = Math.cos(rx) * scaleX;
|
||||
this.b = Math.cos(ry) * scaleY;
|
||||
this.c = Math.sin(rx) * scaleX;
|
||||
this.d = Math.sin(ry) * scaleY;
|
||||
break;
|
||||
}
|
||||
case TransformMode.NoRotationOrReflection: {
|
||||
@ -210,12 +212,12 @@ export class Bone implements Updatable {
|
||||
pc = 0;
|
||||
prx = 90 - Math.atan2(pd, pb) * MathUtils.radDeg;
|
||||
}
|
||||
let rx = rotation + shearX - prx;
|
||||
let ry = rotation + shearY - prx + 90;
|
||||
let la = MathUtils.cosDeg(rx) * scaleX;
|
||||
let lb = MathUtils.cosDeg(ry) * scaleY;
|
||||
let lc = MathUtils.sinDeg(rx) * scaleX;
|
||||
let ld = MathUtils.sinDeg(ry) * scaleY;
|
||||
const rx = (rotation + shearX - prx) * MathUtils.degRad;
|
||||
const ry = (rotation + shearY - prx + 90) * MathUtils.degRad;
|
||||
const la = Math.cos(rx) * scaleX;
|
||||
const lb = Math.cos(ry) * scaleY;
|
||||
const lc = Math.sin(rx) * scaleX;
|
||||
const ld = Math.sin(ry) * scaleY;
|
||||
this.a = pa * la - pb * lc;
|
||||
this.b = pa * lb - pb * ld;
|
||||
this.c = pc * la + pd * lc;
|
||||
@ -224,8 +226,8 @@ export class Bone implements Updatable {
|
||||
}
|
||||
case TransformMode.NoScale:
|
||||
case TransformMode.NoScaleOrReflection: {
|
||||
let cos = MathUtils.cosDeg(rotation);
|
||||
let sin = MathUtils.sinDeg(rotation);
|
||||
rotation *= MathUtils.degRad;
|
||||
const cos = Math.cos(rotation), sin = Math.sin(rotation);
|
||||
let za = (pa * cos + pb * sin) / this.skeleton.scaleX;
|
||||
let zc = (pc * cos + pd * sin) / this.skeleton.scaleY;
|
||||
let s = Math.sqrt(za * za + zc * zc);
|
||||
@ -235,13 +237,15 @@ export class Bone implements Updatable {
|
||||
s = Math.sqrt(za * za + zc * zc);
|
||||
if (this.data.transformMode == TransformMode.NoScale
|
||||
&& (pa * pd - pb * pc < 0) != (this.skeleton.scaleX < 0 != this.skeleton.scaleY < 0)) s = -s;
|
||||
let r = Math.PI / 2 + Math.atan2(zc, za);
|
||||
let zb = Math.cos(r) * s;
|
||||
let zd = Math.sin(r) * s;
|
||||
let la = MathUtils.cosDeg(shearX) * scaleX;
|
||||
let lb = MathUtils.cosDeg(90 + shearY) * scaleY;
|
||||
let lc = MathUtils.sinDeg(shearX) * scaleX;
|
||||
let ld = MathUtils.sinDeg(90 + shearY) * scaleY;
|
||||
rotation = Math.PI / 2 + Math.atan2(zc, za);
|
||||
const zb = Math.cos(rotation) * s;
|
||||
const zd = Math.sin(rotation) * s;
|
||||
shearX *= MathUtils.degRad;
|
||||
shearY = (90 + shearY) * MathUtils.degRad;
|
||||
const la = Math.cos(shearX) * scaleX;
|
||||
const lb = Math.cos(shearY) * scaleY;
|
||||
const lc = Math.sin(shearX) * scaleX;
|
||||
const ld = Math.sin(shearY) * scaleY;
|
||||
this.a = za * la + zb * lc;
|
||||
this.b = za * lb + zb * ld;
|
||||
this.c = zc * la + zd * lc;
|
||||
@ -267,26 +271,6 @@ export class Bone implements Updatable {
|
||||
this.shearY = data.shearY;
|
||||
}
|
||||
|
||||
/** The world rotation for the X axis, calculated using {@link #a} and {@link #c}. */
|
||||
getWorldRotationX () {
|
||||
return Math.atan2(this.c, this.a) * MathUtils.radDeg;
|
||||
}
|
||||
|
||||
/** The world rotation for the Y axis, calculated using {@link #b} and {@link #d}. */
|
||||
getWorldRotationY () {
|
||||
return Math.atan2(this.d, this.b) * MathUtils.radDeg;
|
||||
}
|
||||
|
||||
/** The magnitude (always positive) of the world scale X, calculated using {@link #a} and {@link #c}. */
|
||||
getWorldScaleX () {
|
||||
return Math.sqrt(this.a * this.a + this.c * this.c);
|
||||
}
|
||||
|
||||
/** The magnitude (always positive) of the world scale Y, calculated using {@link #b} and {@link #d}. */
|
||||
getWorldScaleY () {
|
||||
return Math.sqrt(this.b * this.b + this.d * this.d);
|
||||
}
|
||||
|
||||
/** Computes the applied transform values from the world transform.
|
||||
*
|
||||
* If the world transform is modified (by a constraint, {@link #rotateWorld(float)}, etc) then this method should be called so
|
||||
@ -374,6 +358,27 @@ export class Bone implements Updatable {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** The world rotation for the X axis, calculated using {@link #a} and {@link #c}. */
|
||||
getWorldRotationX () {
|
||||
return Math.atan2(this.c, this.a) * MathUtils.radDeg;
|
||||
}
|
||||
|
||||
/** The world rotation for the Y axis, calculated using {@link #b} and {@link #d}. */
|
||||
getWorldRotationY () {
|
||||
return Math.atan2(this.d, this.b) * MathUtils.radDeg;
|
||||
}
|
||||
|
||||
/** The magnitude (always positive) of the world scale X, calculated using {@link #a} and {@link #c}. */
|
||||
getWorldScaleX () {
|
||||
return Math.sqrt(this.a * this.a + this.c * this.c);
|
||||
}
|
||||
|
||||
/** The magnitude (always positive) of the world scale Y, calculated using {@link #b} and {@link #d}. */
|
||||
getWorldScaleY () {
|
||||
return Math.sqrt(this.b * this.b + this.d * this.d);
|
||||
}
|
||||
|
||||
/** Transforms a point from world coordinates to the bone's local coordinates. */
|
||||
worldToLocal (world: Vector2) {
|
||||
let invDet = 1 / (this.a * this.d - this.b * this.c);
|
||||
@ -391,6 +396,18 @@ export class Bone implements Updatable {
|
||||
return local;
|
||||
}
|
||||
|
||||
/** Transforms a point from world coordinates to the parent bone's local coordinates. */
|
||||
worldToParent (world: Vector2) {
|
||||
if (world == null) throw new Error("world cannot be null.");
|
||||
return this.parent == null ? world : this.parent.worldToLocal(world);
|
||||
}
|
||||
|
||||
/** Transforms a point from the parent bone's coordinates to world coordinates. */
|
||||
parentToWorld (world: Vector2) {
|
||||
if (world == null) throw new Error("world cannot be null.");
|
||||
return this.parent == null ? world : this.parent.localToWorld(world);
|
||||
}
|
||||
|
||||
/** Transforms a world rotation to a local rotation. */
|
||||
worldToLocalRotation (worldRotation: number) {
|
||||
let sin = MathUtils.sinDeg(worldRotation), cos = MathUtils.cosDeg(worldRotation);
|
||||
@ -406,14 +423,15 @@ export class Bone implements Updatable {
|
||||
|
||||
/** Rotates the world transform the specified amount.
|
||||
* <p>
|
||||
* After changes are made to the world transform, {@link #updateAppliedTransform()} should be called and {@link #update()} will
|
||||
* need to be called on any child bones, recursively. */
|
||||
* After changes are made to the world transform, {@link #updateAppliedTransform()} should be called and
|
||||
* {@link #update(Physics)} will need to be called on any child bones, recursively. */
|
||||
rotateWorld (degrees: number) {
|
||||
let a = this.a, b = this.b, c = this.c, d = this.d;
|
||||
let cos = MathUtils.cosDeg(degrees), sin = MathUtils.sinDeg(degrees);
|
||||
this.a = cos * a - sin * c;
|
||||
this.b = cos * b - sin * d;
|
||||
this.c = sin * a + cos * c;
|
||||
this.d = sin * b + cos * d;
|
||||
degrees *= MathUtils.degRad;
|
||||
const sin = Math.sin(degrees), cos = Math.cos(degrees);
|
||||
const ra = this.a, rb = this.b;
|
||||
this.a = cos * ra - sin * this.c;
|
||||
this.b = cos * rb - sin * this.d;
|
||||
this.c = sin * ra + cos * this.c;
|
||||
this.d = sin * rb + cos * this.d;
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,7 +49,7 @@ export class BoneData {
|
||||
/** The local y translation. */
|
||||
y = 0;
|
||||
|
||||
/** The local rotation. */
|
||||
/** The local rotation in degrees, counter clockwise. */
|
||||
rotation = 0;
|
||||
|
||||
/** The local scaleX. */
|
||||
@ -76,6 +76,12 @@ export class BoneData {
|
||||
* rendered at runtime. */
|
||||
color = new Color();
|
||||
|
||||
/** The bone icon as it was in Spine, or null if nonessential data was not exported. */
|
||||
icon?: string;
|
||||
|
||||
/** False if the bone was hidden in Spine and nonessential data was exported. Does not affect runtime rendering. */
|
||||
visible = false;
|
||||
|
||||
constructor (index: number, name: string, parent: BoneData | null) {
|
||||
if (index < 0) throw new Error("index must be >= 0.");
|
||||
if (!name) throw new Error("name cannot be null.");
|
||||
|
||||
@ -30,7 +30,7 @@
|
||||
import { Bone } from "./Bone.js";
|
||||
import { TransformMode } from "./BoneData.js";
|
||||
import { IkConstraintData } from "./IkConstraintData.js";
|
||||
import { Skeleton } from "./Skeleton.js";
|
||||
import { Physics, Skeleton } from "./Skeleton.js";
|
||||
import { Updatable } from "./Updatable.js";
|
||||
import { MathUtils } from "./Utils.js";
|
||||
|
||||
@ -90,7 +90,16 @@ export class IkConstraint implements Updatable {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
update () {
|
||||
setToSetupPose () {
|
||||
const data = this.data;
|
||||
this.mix = data.mix;
|
||||
this.softness = data.softness;
|
||||
this.bendDirection = data.bendDirection;
|
||||
this.compress = data.compress;
|
||||
this.stretch = data.stretch;
|
||||
}
|
||||
|
||||
update (physics: Physics) {
|
||||
if (this.mix == 0) return;
|
||||
let target = this.target;
|
||||
let bones = this.bones;
|
||||
@ -149,11 +158,14 @@ export class IkConstraint implements Updatable {
|
||||
tx = targetX - bone.worldX;
|
||||
ty = targetY - bone.worldY;
|
||||
}
|
||||
let b = bone.data.length * sx, dd = Math.sqrt(tx * tx + ty * ty);
|
||||
if ((compress && dd < b) || (stretch && dd > b) && b > 0.0001) {
|
||||
let s = (dd / b - 1) * alpha + 1;
|
||||
sx *= s;
|
||||
if (uniform) sy *= s;
|
||||
const b = bone.data.length * sx;
|
||||
if (b > 0.0001) {
|
||||
const dd = tx * tx + ty * ty;
|
||||
if ((compress && dd < b * b) || (stretch && dd > b * b)) {
|
||||
const s = (Math.sqrt(dd) / b - 1) * alpha + 1;
|
||||
sx *= s;
|
||||
if (uniform) sy *= s;
|
||||
}
|
||||
}
|
||||
}
|
||||
bone.updateWorldTransformWith(bone.ax, bone.ay, bone.arotation + rotationIK * alpha, sx, sy, bone.ashearX,
|
||||
|
||||
@ -30,7 +30,7 @@
|
||||
import { PathAttachment } from "./attachments/PathAttachment.js";
|
||||
import { Bone } from "./Bone.js";
|
||||
import { PathConstraintData, RotateMode, SpacingMode, PositionMode } from "./PathConstraintData.js";
|
||||
import { Skeleton } from "./Skeleton.js";
|
||||
import { Physics, Skeleton } from "./Skeleton.js";
|
||||
import { Slot } from "./Slot.js";
|
||||
import { Updatable } from "./Updatable.js";
|
||||
import { Utils, MathUtils } from "./Utils.js";
|
||||
@ -95,7 +95,16 @@ export class PathConstraint implements Updatable {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
update () {
|
||||
setToSetupPose () {
|
||||
const data = this.data;
|
||||
this.position = data.position;
|
||||
this.spacing = data.spacing;
|
||||
this.mixRotate = data.mixRotate;
|
||||
this.mixX = data.mixX;
|
||||
this.mixY = data.mixY;
|
||||
}
|
||||
|
||||
update (physics: Physics) {
|
||||
let attachment = this.target.getAttachment();
|
||||
if (!(attachment instanceof PathAttachment)) return;
|
||||
|
||||
@ -116,12 +125,8 @@ export class PathConstraint implements Updatable {
|
||||
for (let i = 0, n = spacesCount - 1; i < n; i++) {
|
||||
let bone = bones[i];
|
||||
let setupLength = bone.data.length;
|
||||
if (setupLength < PathConstraint.epsilon)
|
||||
lengths[i] = 0;
|
||||
else {
|
||||
let x = setupLength * bone.a, y = setupLength * bone.c;
|
||||
lengths[i] = Math.sqrt(x * x + y * y);
|
||||
}
|
||||
let x = setupLength * bone.a, y = setupLength * bone.c;
|
||||
lengths[i] = Math.sqrt(x * x + y * y);
|
||||
}
|
||||
}
|
||||
Utils.arrayFill(spaces, 1, spacesCount, spacing);
|
||||
|
||||
270
spine-ts/spine-core/src/PhysicsConstraint.ts
Normal file
270
spine-ts/spine-core/src/PhysicsConstraint.ts
Normal file
@ -0,0 +1,270 @@
|
||||
/******************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
|
||||
import { Bone } from "./Bone.js";
|
||||
import { PhysicsConstraintData } from "./PhysicsConstraintData.js";
|
||||
import { Physics, Skeleton } from "./Skeleton.js";
|
||||
import { Updatable } from "./Updatable.js";
|
||||
import { MathUtils } from "./Utils.js";
|
||||
|
||||
|
||||
/** Stores the current pose for a physics constraint. A physics constraint applies physics to bones.
|
||||
* <p>
|
||||
* See <a href="http://esotericsoftware.com/spine-physics-constraints">Physics constraints</a> in the Spine User Guide. */
|
||||
export class PhysicsConstraint implements Updatable {
|
||||
readonly data: PhysicsConstraintData;
|
||||
private _bone: Bone | null = null;
|
||||
/** The bone constrained by this physics constraint. */
|
||||
public set bone (bone: Bone) { this._bone = bone; }
|
||||
public get bone () {
|
||||
if (!this._bone) throw new Error("Bone not set.")
|
||||
else return this._bone;
|
||||
}
|
||||
inertia = 0;
|
||||
strength = 0;
|
||||
damping = 0;
|
||||
massInverse = 0;
|
||||
wind = 0;
|
||||
gravity = 0;
|
||||
mix = 0;
|
||||
|
||||
_reset = true;
|
||||
ux = 0;
|
||||
uy = 0;
|
||||
cx = 0;
|
||||
cy = 0;
|
||||
tx = 0;
|
||||
ty = 0;
|
||||
xOffset = 0;
|
||||
xVelocity = 0;
|
||||
yOffset = 0;
|
||||
yVelocity = 0;
|
||||
rotateOffset = 0;
|
||||
rotateVelocity = 0;
|
||||
scaleOffset = 0
|
||||
scaleVelocity = 0;
|
||||
|
||||
active = false;
|
||||
|
||||
readonly skeleton: Skeleton;
|
||||
remaining = 0;
|
||||
lastTime = 0;
|
||||
|
||||
constructor(data: PhysicsConstraintData, skeleton: Skeleton) {
|
||||
this.data = data;
|
||||
this.skeleton = skeleton;
|
||||
this.bone = skeleton.bones[data.bone.index];
|
||||
this.inertia = data.inertia;
|
||||
this.strength = data.strength;
|
||||
this.damping = data.damping;
|
||||
this.massInverse = data.massInverse;
|
||||
this.wind = data.wind;
|
||||
this.gravity = data.gravity;
|
||||
this.mix = data.mix;
|
||||
}
|
||||
|
||||
reset () {
|
||||
this.remaining = 0;
|
||||
this.lastTime = this.skeleton.time;
|
||||
this._reset = true;
|
||||
this.xOffset = 0;
|
||||
this.xVelocity = 0;
|
||||
this.yOffset = 0;
|
||||
this.yVelocity = 0;
|
||||
this.rotateOffset = 0;
|
||||
this.rotateVelocity = 0;
|
||||
this.scaleOffset = 0;
|
||||
this.scaleVelocity = 0;
|
||||
}
|
||||
|
||||
setToSetupPose () {
|
||||
const data = this.data;
|
||||
this.inertia = data.inertia;
|
||||
this.strength = data.strength;
|
||||
this.damping = data.damping;
|
||||
this.massInverse = data.massInverse;
|
||||
this.wind = data.wind;
|
||||
this.gravity = data.gravity;
|
||||
this.mix = data.mix;
|
||||
}
|
||||
|
||||
isActive () {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
/** Applies the constraint to the constrained bones. */
|
||||
update (physics: Physics) {
|
||||
const mix = this.mix;
|
||||
if (mix == 0) return;
|
||||
|
||||
const x = this.data.x > 0, y = this.data.y > 0, rotateOrShearX = this.data.rotate > 0 || this.data.shearX > 0, scaleX = this.data.scaleX > 0;
|
||||
const bone = this.bone;
|
||||
const l = bone.data.length;
|
||||
|
||||
switch (physics) {
|
||||
case Physics.none:
|
||||
return;
|
||||
case Physics.reset:
|
||||
this.reset();
|
||||
// Fall through.
|
||||
case Physics.update:
|
||||
this.remaining += Math.max(this.skeleton.time - this.lastTime, 0);
|
||||
this.lastTime = this.skeleton.time;
|
||||
|
||||
const bx = bone.worldX, by = bone.worldY;
|
||||
if (this._reset) {
|
||||
this._reset = false;
|
||||
this.ux = bx;
|
||||
this.uy = by;
|
||||
} else {
|
||||
let remaining = this.remaining, i = this.inertia, step = this.data.step;
|
||||
if (x || y) {
|
||||
if (x) {
|
||||
this.xOffset += (this.ux - bx) * i;
|
||||
this.ux = bx;
|
||||
}
|
||||
if (y) {
|
||||
this.yOffset += (this.uy - by) * i;
|
||||
this.uy = by;
|
||||
}
|
||||
if (remaining >= step) {
|
||||
const m = this.massInverse * step, e = this.strength, w = this.wind * 100, g = this.gravity * -100;
|
||||
const d = Math.pow(this.damping, 60 * step);
|
||||
do {
|
||||
if (x) {
|
||||
this.xVelocity += (w - this.xOffset * e) * m;
|
||||
this.xOffset += this.xVelocity * step;
|
||||
this.xVelocity *= d;
|
||||
}
|
||||
if (y) {
|
||||
this.yVelocity += (g - this.yOffset * e) * m;
|
||||
this.yOffset += this.yVelocity * step;
|
||||
this.yVelocity *= d;
|
||||
}
|
||||
remaining -= step;
|
||||
} while (remaining >= step);
|
||||
}
|
||||
if (x) bone.worldX += this.xOffset * mix * this.data.x;
|
||||
if (y) bone.worldY += this.yOffset * mix * this.data.y;
|
||||
}
|
||||
if (rotateOrShearX || scaleX) {
|
||||
let ca = Math.atan2(bone.c, bone.a), c = 0, s = 0, mr = 0;
|
||||
if (rotateOrShearX) {
|
||||
mr = mix * this.data.rotate;
|
||||
let dx = this.cx - bone.worldX, dy = this.cy - bone.worldY, r = Math.atan2(dy + this.ty, dx + this.tx) - ca - this.rotateOffset * mr;
|
||||
this.rotateOffset += (r - Math.ceil(r * MathUtils.invPI2 - 0.5) * MathUtils.PI2) * i;
|
||||
r = this.rotateOffset * mr + ca;
|
||||
c = Math.cos(r);
|
||||
s = Math.sin(r);
|
||||
if (scaleX) {
|
||||
r = l * bone.getWorldScaleX();
|
||||
if (r > 0) this.scaleOffset += (dx * c + dy * s) * i / r;
|
||||
}
|
||||
} else {
|
||||
c = Math.cos(ca);
|
||||
s = Math.sin(ca);
|
||||
const r = l * bone.getWorldScaleX();
|
||||
if (r > 0) this.scaleOffset += ((this.cx - bone.worldX) * c + (this.cy - bone.worldY) * s) * i / r;
|
||||
}
|
||||
remaining = this.remaining;
|
||||
if (remaining >= step) {
|
||||
const m = this.massInverse * step, e = this.strength, w = this.wind, g = this.gravity;
|
||||
const d = Math.pow(this.damping, 60 * step);
|
||||
while (true) {
|
||||
remaining -= step;
|
||||
if (scaleX) {
|
||||
this.scaleVelocity += (w * c - g * s - this.scaleOffset * e) * m;
|
||||
this.scaleOffset += this.scaleVelocity * step;
|
||||
this.scaleVelocity *= d;
|
||||
}
|
||||
if (rotateOrShearX) {
|
||||
this.rotateVelocity += (-0.01 * l * (w * s + g * c) - this.rotateOffset * e) * m;
|
||||
this.rotateOffset += this.rotateVelocity * step;
|
||||
this.rotateVelocity *= d;
|
||||
if (remaining < step) break;
|
||||
const r = this.rotateOffset * mr + ca;
|
||||
c = Math.cos(r);
|
||||
s = Math.sin(r);
|
||||
} else if (remaining < step) //
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.remaining = remaining;
|
||||
}
|
||||
this.cx = bone.worldX;
|
||||
this.cy = bone.worldY;
|
||||
break;
|
||||
case Physics.pose:
|
||||
if (x) bone.worldX += this.xOffset * mix * this.data.x;
|
||||
if (y) bone.worldY += this.yOffset * mix * this.data.y;
|
||||
}
|
||||
|
||||
if (rotateOrShearX) {
|
||||
let o = this.rotateOffset * mix, s = 0, c = 0, a = 0;
|
||||
if (this.data.shearX > 0) {
|
||||
let r = 0;
|
||||
if (this.data.rotate > 0) {
|
||||
r = o * this.data.rotate;
|
||||
s = Math.sin(r);
|
||||
c = Math.cos(r);
|
||||
a = bone.b;
|
||||
bone.b = c * a - s * bone.d;
|
||||
bone.d = s * a + c * bone.d;
|
||||
}
|
||||
r += o * this.data.shearX;
|
||||
s = Math.sin(r);
|
||||
c = Math.cos(r);
|
||||
a = bone.a;
|
||||
bone.a = c * a - s * bone.c;
|
||||
bone.c = s * a + c * bone.c;
|
||||
} else {
|
||||
o *= this.data.rotate;
|
||||
s = Math.sin(o);
|
||||
c = Math.cos(o);
|
||||
a = bone.a;
|
||||
bone.a = c * a - s * bone.c;
|
||||
bone.c = s * a + c * bone.c;
|
||||
a = bone.b;
|
||||
bone.b = c * a - s * bone.d;
|
||||
bone.d = s * a + c * bone.d;
|
||||
}
|
||||
}
|
||||
if (scaleX) {
|
||||
const s = 1 + this.scaleOffset * mix * this.data.scaleX;
|
||||
bone.a *= s;
|
||||
bone.c *= s;
|
||||
}
|
||||
if (physics != Physics.pose) {
|
||||
this.tx = l * bone.a;
|
||||
this.ty = l * bone.c;
|
||||
}
|
||||
bone.updateAppliedTransform();
|
||||
}
|
||||
}
|
||||
71
spine-ts/spine-core/src/PhysicsConstraintData.ts
Normal file
71
spine-ts/spine-core/src/PhysicsConstraintData.ts
Normal file
@ -0,0 +1,71 @@
|
||||
/******************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
|
||||
import { BoneData } from "./BoneData.js";
|
||||
import { ConstraintData } from "./ConstraintData.js";
|
||||
|
||||
|
||||
/** Stores the setup pose for a {@link PhysicsConstraint}.
|
||||
* <p>
|
||||
* See <a href="http://esotericsoftware.com/spine-physics-constraints">Physics constraints</a> in the Spine User Guide. */
|
||||
export class PhysicsConstraintData extends ConstraintData {
|
||||
private _bone: BoneData | null = null;
|
||||
/** The bone constrained by this physics constraint. */
|
||||
public set bone (boneData: BoneData) { this._bone = boneData; }
|
||||
public get bone () {
|
||||
if (!this._bone) throw new Error("BoneData not set.")
|
||||
else return this._bone;
|
||||
}
|
||||
|
||||
x = 0;
|
||||
y = 0;
|
||||
rotate = 0;
|
||||
scaleX = 1;
|
||||
shearX = 1;
|
||||
step = 0;
|
||||
inertia = 0;
|
||||
strength = 0;
|
||||
damping = 0;
|
||||
massInverse = 0;
|
||||
wind = 0;
|
||||
gravity = 0;
|
||||
/** A percentage (0-1) that controls the mix between the constrained and unconstrained poses. */
|
||||
mix = 0;
|
||||
inertiaGlobal = false;
|
||||
strengthGlobal = false;
|
||||
dampingGlobal = false;
|
||||
massGlobal = false;
|
||||
windGlobal = false;
|
||||
gravityGlobal = false;
|
||||
mixGlobal = false;
|
||||
|
||||
constructor (name: string) {
|
||||
super(name, 0, false);
|
||||
}
|
||||
}
|
||||
@ -34,6 +34,7 @@ import { RegionAttachment } from "./attachments/RegionAttachment.js";
|
||||
import { Bone } from "./Bone.js";
|
||||
import { IkConstraint } from "./IkConstraint.js";
|
||||
import { PathConstraint } from "./PathConstraint.js";
|
||||
import { PhysicsConstraint } from "./PhysicsConstraint.js";
|
||||
import { SkeletonData } from "./SkeletonData.js";
|
||||
import { Skin } from "./Skin.js";
|
||||
import { Slot } from "./Slot.js";
|
||||
@ -68,6 +69,10 @@ export class Skeleton {
|
||||
/** The skeleton's path constraints. */
|
||||
pathConstraints: Array<PathConstraint>;
|
||||
|
||||
|
||||
/** The skeleton's physics constraints. */
|
||||
physicsConstraints: Array<PhysicsConstraint>;
|
||||
|
||||
/** The list of bones and constraints, sorted in the order they should be updated, as computed by {@link #updateCache()}. */
|
||||
_updateCache = new Array<Updatable>();
|
||||
|
||||
@ -99,6 +104,11 @@ export class Skeleton {
|
||||
/** Sets the skeleton Y position, which is added to the root bone worldY position. */
|
||||
y = 0;
|
||||
|
||||
/** Returns the skeleton's time. This is used for time-based manipulations, such as {@link PhysicsConstraint}.
|
||||
* <p>
|
||||
* See {@link #update(float)}. */
|
||||
time = 0;
|
||||
|
||||
constructor (data: SkeletonData) {
|
||||
if (!data) throw new Error("data cannot be null.");
|
||||
this.data = data;
|
||||
@ -145,6 +155,12 @@ export class Skeleton {
|
||||
this.pathConstraints.push(new PathConstraint(pathConstraintData, this));
|
||||
}
|
||||
|
||||
this.physicsConstraints = new Array<PhysicsConstraint>();
|
||||
for (let i = 0; i < data.physicsConstraints.length; i++) {
|
||||
let physicsConstraintData = data.physicsConstraints[i];
|
||||
this.physicsConstraints.push(new PhysicsConstraint(physicsConstraintData, this));
|
||||
}
|
||||
|
||||
this.color = new Color(1, 1, 1, 1);
|
||||
this.updateCache();
|
||||
}
|
||||
@ -178,8 +194,9 @@ export class Skeleton {
|
||||
let ikConstraints = this.ikConstraints;
|
||||
let transformConstraints = this.transformConstraints;
|
||||
let pathConstraints = this.pathConstraints;
|
||||
let ikCount = ikConstraints.length, transformCount = transformConstraints.length, pathCount = pathConstraints.length;
|
||||
let constraintCount = ikCount + transformCount + pathCount;
|
||||
let physicsConstraints = this.physicsConstraints;
|
||||
let ikCount = ikConstraints.length, transformCount = transformConstraints.length, pathCount = pathConstraints.length, physicsCount = this.physicsConstraints.length;
|
||||
let constraintCount = ikCount + transformCount + pathCount + physicsCount;
|
||||
|
||||
outer:
|
||||
for (let i = 0; i < constraintCount; i++) {
|
||||
@ -204,6 +221,13 @@ export class Skeleton {
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
for (let ii = 0; ii < physicsCount; ii++) {
|
||||
const constraint = physicsConstraints[ii];
|
||||
if (constraint.data.order == i) {
|
||||
this.sortPhysicsConstraint(constraint);
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0, n = bones.length; i < n; i++)
|
||||
@ -316,6 +340,22 @@ export class Skeleton {
|
||||
}
|
||||
}
|
||||
|
||||
sortPhysicsConstraint (constraint: PhysicsConstraint) {
|
||||
constraint.active = !constraint.data.skinRequired || (this.skin != null && Utils.contains(this.skin.constraints, constraint.data, true));
|
||||
if (!constraint.active) return;
|
||||
|
||||
const bone = constraint.bone;
|
||||
constraint.active = bone.active;
|
||||
if (!constraint.active) return;
|
||||
|
||||
this.sortBone(bone);
|
||||
|
||||
this._updateCache.push(constraint);
|
||||
|
||||
this.sortReset(bone.children);
|
||||
bone.sorted = true;
|
||||
}
|
||||
|
||||
sortBone (bone: Bone) {
|
||||
if (!bone) return;
|
||||
if (bone.sorted) return;
|
||||
@ -338,7 +378,7 @@ export class Skeleton {
|
||||
*
|
||||
* See [World transforms](http://esotericsoftware.com/spine-runtime-skeletons#World-transforms) in the Spine
|
||||
* Runtimes Guide. */
|
||||
updateWorldTransform () {
|
||||
updateWorldTransform (physics: Physics) {
|
||||
let bones = this.bones;
|
||||
for (let i = 0, n = bones.length; i < n; i++) {
|
||||
let bone = bones[i];
|
||||
@ -353,10 +393,10 @@ export class Skeleton {
|
||||
|
||||
let updateCache = this._updateCache;
|
||||
for (let i = 0, n = updateCache.length; i < n; i++)
|
||||
updateCache[i].update();
|
||||
updateCache[i].update(physics);
|
||||
}
|
||||
|
||||
updateWorldTransformWith (parent: Bone) {
|
||||
updateWorldTransformWith (physics: Physics, parent: Bone) {
|
||||
// Apply the parent bone transform to the root bone. The root bone always inherits scale, rotation and reflection.
|
||||
let rootBone = this.getRootBone();
|
||||
if (!rootBone) throw new Error("Root bone must not be null.");
|
||||
@ -364,11 +404,12 @@ export class Skeleton {
|
||||
rootBone.worldX = pa * this.x + pb * this.y + parent.worldX;
|
||||
rootBone.worldY = pc * this.x + pd * this.y + parent.worldY;
|
||||
|
||||
let rotationY = rootBone.rotation + 90 + rootBone.shearY;
|
||||
let la = MathUtils.cosDeg(rootBone.rotation + rootBone.shearX) * rootBone.scaleX;
|
||||
let lb = MathUtils.cosDeg(rotationY) * rootBone.scaleY;
|
||||
let lc = MathUtils.sinDeg(rootBone.rotation + rootBone.shearX) * rootBone.scaleX;
|
||||
let ld = MathUtils.sinDeg(rotationY) * rootBone.scaleY;
|
||||
const rx = (rootBone.rotation + rootBone.shearX) * MathUtils.degRad;
|
||||
const ry = (rootBone.rotation + 90 + rootBone.shearY) * MathUtils.degRad;
|
||||
const la = Math.cos(rx) * rootBone.scaleX;
|
||||
const lb = Math.cos(ry) * rootBone.scaleY;
|
||||
const lc = Math.sin(rx) * rootBone.scaleX;
|
||||
const ld = Math.sin(ry) * rootBone.scaleY;
|
||||
rootBone.a = (pa * la + pb * lc) * this.scaleX;
|
||||
rootBone.b = (pa * lb + pb * ld) * this.scaleX;
|
||||
rootBone.c = (pc * la + pd * lc) * this.scaleY;
|
||||
@ -378,7 +419,7 @@ export class Skeleton {
|
||||
let updateCache = this._updateCache;
|
||||
for (let i = 0, n = updateCache.length; i < n; i++) {
|
||||
let updatable = updateCache[i];
|
||||
if (updatable != rootBone) updatable.update();
|
||||
if (updatable != rootBone) updatable.update(physics);
|
||||
}
|
||||
}
|
||||
|
||||
@ -390,42 +431,11 @@ export class Skeleton {
|
||||
|
||||
/** Sets the bones and constraints to their setup pose values. */
|
||||
setBonesToSetupPose () {
|
||||
let bones = this.bones;
|
||||
for (let i = 0, n = bones.length; i < n; i++)
|
||||
bones[i].setToSetupPose();
|
||||
|
||||
let ikConstraints = this.ikConstraints;
|
||||
for (let i = 0, n = ikConstraints.length; i < n; i++) {
|
||||
let constraint = ikConstraints[i];
|
||||
constraint.mix = constraint.data.mix;
|
||||
constraint.softness = constraint.data.softness;
|
||||
constraint.bendDirection = constraint.data.bendDirection;
|
||||
constraint.compress = constraint.data.compress;
|
||||
constraint.stretch = constraint.data.stretch;
|
||||
}
|
||||
|
||||
let transformConstraints = this.transformConstraints;
|
||||
for (let i = 0, n = transformConstraints.length; i < n; i++) {
|
||||
let constraint = transformConstraints[i];
|
||||
let data = constraint.data;
|
||||
constraint.mixRotate = data.mixRotate;
|
||||
constraint.mixX = data.mixX;
|
||||
constraint.mixY = data.mixY;
|
||||
constraint.mixScaleX = data.mixScaleX;
|
||||
constraint.mixScaleY = data.mixScaleY;
|
||||
constraint.mixShearY = data.mixShearY;
|
||||
}
|
||||
|
||||
let pathConstraints = this.pathConstraints;
|
||||
for (let i = 0, n = pathConstraints.length; i < n; i++) {
|
||||
let constraint = pathConstraints[i];
|
||||
let data = constraint.data;
|
||||
constraint.position = data.position;
|
||||
constraint.spacing = data.spacing;
|
||||
constraint.mixRotate = data.mixRotate;
|
||||
constraint.mixX = data.mixX;
|
||||
constraint.mixY = data.mixY;
|
||||
}
|
||||
for (const bone of this.bones) bone.setToSetupPose();
|
||||
for (const constraint of this.ikConstraints) constraint.setToSetupPose();
|
||||
for (const constraint of this.transformConstraints) constraint.setToSetupPose();
|
||||
for (const constraint of this.pathConstraints) constraint.setToSetupPose();
|
||||
for (const constraint of this.physicsConstraints) constraint.setToSetupPose();
|
||||
}
|
||||
|
||||
/** Sets the slots and draw order to their setup pose values. */
|
||||
@ -560,12 +570,7 @@ export class Skeleton {
|
||||
* @return May be null. */
|
||||
findIkConstraint (constraintName: string) {
|
||||
if (!constraintName) throw new Error("constraintName cannot be null.");
|
||||
let ikConstraints = this.ikConstraints;
|
||||
for (let i = 0, n = ikConstraints.length; i < n; i++) {
|
||||
let ikConstraint = ikConstraints[i];
|
||||
if (ikConstraint.data.name == constraintName) return ikConstraint;
|
||||
}
|
||||
return null;
|
||||
return this.ikConstraints.find((constraint) => constraint.data.name == constraintName) ?? null;
|
||||
}
|
||||
|
||||
/** Finds a transform constraint by comparing each transform constraint's name. It is more efficient to cache the results of
|
||||
@ -573,12 +578,7 @@ export class Skeleton {
|
||||
* @return May be null. */
|
||||
findTransformConstraint (constraintName: string) {
|
||||
if (!constraintName) throw new Error("constraintName cannot be null.");
|
||||
let transformConstraints = this.transformConstraints;
|
||||
for (let i = 0, n = transformConstraints.length; i < n; i++) {
|
||||
let constraint = transformConstraints[i];
|
||||
if (constraint.data.name == constraintName) return constraint;
|
||||
}
|
||||
return null;
|
||||
return this.transformConstraints.find((constraint) => constraint.data.name == constraintName) ?? null;
|
||||
}
|
||||
|
||||
/** Finds a path constraint by comparing each path constraint's name. It is more efficient to cache the results of this method
|
||||
@ -586,12 +586,14 @@ export class Skeleton {
|
||||
* @return May be null. */
|
||||
findPathConstraint (constraintName: string) {
|
||||
if (!constraintName) throw new Error("constraintName cannot be null.");
|
||||
let pathConstraints = this.pathConstraints;
|
||||
for (let i = 0, n = pathConstraints.length; i < n; i++) {
|
||||
let constraint = pathConstraints[i];
|
||||
if (constraint.data.name == constraintName) return constraint;
|
||||
}
|
||||
return null;
|
||||
return this.pathConstraints.find((constraint) => constraint.data.name == constraintName) ?? null;
|
||||
}
|
||||
|
||||
/** Finds a physics constraint by comparing each physics constraint's name. It is more efficient to cache the results of this
|
||||
* method than to call it repeatedly. */
|
||||
findPhysicsConstraint (constraintName: string) {
|
||||
if (constraintName == null) throw new Error("constraintName cannot be null.");
|
||||
return this.physicsConstraints.find((constraint) => constraint.data.name == constraintName) ?? null;
|
||||
}
|
||||
|
||||
/** Returns the axis aligned bounding box (AABB) of the region and mesh attachments for the current pose as `{ x: number, y: number, width: number, height: number }`.
|
||||
@ -641,4 +643,24 @@ export class Skeleton {
|
||||
offset.set(minX, minY);
|
||||
size.set(maxX - minX, maxY - minY);
|
||||
}
|
||||
|
||||
/** Increments the skeleton's {@link #time}. */
|
||||
update (delta: number) {
|
||||
this.time += delta;
|
||||
}
|
||||
}
|
||||
|
||||
/** Determines how physics and other non-deterministic updates are applied. */
|
||||
export enum Physics {
|
||||
/** Physics are not updated or applied. */
|
||||
none,
|
||||
|
||||
/** Physics are reset to the current pose. */
|
||||
reset,
|
||||
|
||||
/** Physics are updated and the pose from physics is applied. */
|
||||
update,
|
||||
|
||||
/** Physics are not updated but the pose from physics is applied. */
|
||||
pose
|
||||
}
|
||||
@ -32,6 +32,7 @@ import { BoneData } from "./BoneData.js";
|
||||
import { EventData } from "./EventData.js";
|
||||
import { IkConstraintData } from "./IkConstraintData.js";
|
||||
import { PathConstraintData } from "./PathConstraintData.js";
|
||||
import { PhysicsConstraintData } from "./PhysicsConstraintData.js";
|
||||
import { Skin } from "./Skin.js";
|
||||
import { SlotData } from "./SlotData.js";
|
||||
import { TransformConstraintData } from "./TransformConstraintData.js";
|
||||
@ -73,6 +74,9 @@ export class SkeletonData {
|
||||
/** The skeleton's path constraints. */
|
||||
pathConstraints = new Array<PathConstraintData>();
|
||||
|
||||
/** The skeleton's physics constraints. */
|
||||
physicsConstraints = new Array<PhysicsConstraintData>();
|
||||
|
||||
/** The X coordinate of the skeleton's axis aligned bounding box in the setup pose. */
|
||||
x: number = 0;
|
||||
|
||||
@ -171,9 +175,9 @@ export class SkeletonData {
|
||||
* @return May be null. */
|
||||
findIkConstraint (constraintName: string) {
|
||||
if (!constraintName) throw new Error("constraintName cannot be null.");
|
||||
let ikConstraints = this.ikConstraints;
|
||||
const ikConstraints = this.ikConstraints;
|
||||
for (let i = 0, n = ikConstraints.length; i < n; i++) {
|
||||
let constraint = ikConstraints[i];
|
||||
const constraint = ikConstraints[i];
|
||||
if (constraint.name == constraintName) return constraint;
|
||||
}
|
||||
return null;
|
||||
@ -184,9 +188,9 @@ export class SkeletonData {
|
||||
* @return May be null. */
|
||||
findTransformConstraint (constraintName: string) {
|
||||
if (!constraintName) throw new Error("constraintName cannot be null.");
|
||||
let transformConstraints = this.transformConstraints;
|
||||
const transformConstraints = this.transformConstraints;
|
||||
for (let i = 0, n = transformConstraints.length; i < n; i++) {
|
||||
let constraint = transformConstraints[i];
|
||||
const constraint = transformConstraints[i];
|
||||
if (constraint.name == constraintName) return constraint;
|
||||
}
|
||||
return null;
|
||||
@ -197,9 +201,22 @@ export class SkeletonData {
|
||||
* @return May be null. */
|
||||
findPathConstraint (constraintName: string) {
|
||||
if (!constraintName) throw new Error("constraintName cannot be null.");
|
||||
let pathConstraints = this.pathConstraints;
|
||||
const pathConstraints = this.pathConstraints;
|
||||
for (let i = 0, n = pathConstraints.length; i < n; i++) {
|
||||
let constraint = pathConstraints[i];
|
||||
const constraint = pathConstraints[i];
|
||||
if (constraint.name == constraintName) return constraint;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Finds a physics constraint by comparing each physics constraint's name. It is more efficient to cache the results of this method
|
||||
* than to call it multiple times.
|
||||
* @return May be null. */
|
||||
findPhysicsConstraint (constraintName: string) {
|
||||
if (!constraintName) throw new Error("constraintName cannot be null.");
|
||||
const physicsConstraints = this.physicsConstraints;
|
||||
for (let i = 0, n = physicsConstraints.length; i < n; i++) {
|
||||
const constraint = physicsConstraints[i];
|
||||
if (constraint.name == constraintName) return constraint;
|
||||
}
|
||||
return null;
|
||||
|
||||
@ -32,7 +32,7 @@ import { MeshAttachment } from "./attachments/MeshAttachment.js";
|
||||
import { BoneData } from "./BoneData.js";
|
||||
import { ConstraintData } from "./ConstraintData.js";
|
||||
import { Skeleton } from "./Skeleton.js";
|
||||
import { StringMap } from "./Utils.js";
|
||||
import { Color, StringMap } from "./Utils.js";
|
||||
|
||||
/** Stores an entry in the skin consisting of the slot index, name, and attachment **/
|
||||
export class SkinEntry {
|
||||
@ -51,6 +51,9 @@ export class Skin {
|
||||
bones = Array<BoneData>();
|
||||
constraints = new Array<ConstraintData>();
|
||||
|
||||
/** The color of the skin as it was in Spine, or a default color if nonessential data was not exported. */
|
||||
color = new Color(0.99607843, 0.61960787, 0.30980393, 1); // fe9e4fff
|
||||
|
||||
constructor (name: string) {
|
||||
if (!name) throw new Error("name cannot be null.");
|
||||
this.name = name;
|
||||
|
||||
@ -55,6 +55,9 @@ export class SlotData {
|
||||
/** The blend mode for drawing the slot's attachment. */
|
||||
blendMode: BlendMode = BlendMode.Normal;
|
||||
|
||||
/** False if the slot was hidden in Spine and nonessential data was exported. Does not affect runtime rendering. */
|
||||
visible = true;
|
||||
|
||||
constructor (index: number, name: string, boneData: BoneData) {
|
||||
if (index < 0) throw new Error("index must be >= 0.");
|
||||
if (!name) throw new Error("name cannot be null.");
|
||||
|
||||
@ -28,7 +28,7 @@
|
||||
*****************************************************************************/
|
||||
|
||||
import { Bone } from "./Bone.js";
|
||||
import { Skeleton } from "./Skeleton.js";
|
||||
import { Physics, Skeleton } from "./Skeleton.js";
|
||||
import { TransformConstraintData } from "./TransformConstraintData.js";
|
||||
import { Updatable } from "./Updatable.js";
|
||||
import { Vector2, MathUtils } from "./Utils.js";
|
||||
@ -79,7 +79,17 @@ export class TransformConstraint implements Updatable {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
update () {
|
||||
setToSetupPose () {
|
||||
const data = this.data;
|
||||
this.mixRotate = data.mixRotate;
|
||||
this.mixX = data.mixX;
|
||||
this.mixY = data.mixY;
|
||||
this.mixScaleX = data.mixScaleX;
|
||||
this.mixScaleY = data.mixScaleY;
|
||||
this.mixShearY = data.mixShearY;
|
||||
}
|
||||
|
||||
update (physics: Physics) {
|
||||
if (this.mixRotate == 0 && this.mixX == 0 && this.mixY == 0 && this.mixScaleX == 0 && this.mixScaleY == 0 && this.mixShearY == 0) return;
|
||||
|
||||
if (this.data.local) {
|
||||
@ -240,7 +250,7 @@ export class TransformConstraint implements Updatable {
|
||||
let rotation = bone.arotation;
|
||||
if (mixRotate != 0) {
|
||||
let r = target.arotation - rotation + this.data.offsetRotation;
|
||||
r -= (16384 - ((16384.499999999996 - r / 360) | 0)) * 360;
|
||||
r -= Math.ceil(r / 360 - 0.5) * 360;
|
||||
rotation += r * mixRotate;
|
||||
}
|
||||
|
||||
@ -257,7 +267,7 @@ export class TransformConstraint implements Updatable {
|
||||
let shearY = bone.ashearY;
|
||||
if (mixShearY != 0) {
|
||||
let r = target.ashearY - shearY + this.data.offsetShearY;
|
||||
r -= (16384 - ((16384.499999999996 - r / 360) | 0)) * 360;
|
||||
r -= Math.ceil(r / 360 - 0.5) * 360;
|
||||
shearY += r * mixShearY;
|
||||
}
|
||||
|
||||
|
||||
@ -27,13 +27,19 @@
|
||||
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
import { Physics } from "./Skeleton.js";
|
||||
|
||||
/** The interface for items updated by {@link Skeleton#updateWorldTransform()}. */
|
||||
export interface Updatable {
|
||||
update (): void;
|
||||
/** @param physics Determines how physics and other non-deterministic updates are applied. */
|
||||
update (physics: Physics): void;
|
||||
|
||||
/** Returns false when this item has not been updated because a skin is required and the {@link Skeleton#skin active skin}
|
||||
* does not contain this item.
|
||||
/** Returns false when this item won't be updated by
|
||||
* {@link Skeleton#updateWorldTransform()} because a skin is required and the
|
||||
* {@link Skeleton#getSkin() active skin} does not contain this item.
|
||||
* @see Skin#getBones()
|
||||
* @see Skin#getConstraints() */
|
||||
* @see Skin#getConstraints()
|
||||
* @see BoneData#getSkinRequired()
|
||||
* @see ConstraintData#getSkinRequired() */
|
||||
isActive (): boolean;
|
||||
}
|
||||
|
||||
@ -179,6 +179,7 @@ export class Color {
|
||||
export class MathUtils {
|
||||
static PI = 3.1415927;
|
||||
static PI2 = MathUtils.PI * 2;
|
||||
static invPI2 = 1 / MathUtils.PI2;
|
||||
static radiansToDegrees = 180 / MathUtils.PI;
|
||||
static radDeg = MathUtils.radiansToDegrees;
|
||||
static degreesToRadians = MathUtils.PI / 180;
|
||||
@ -198,6 +199,10 @@ export class MathUtils {
|
||||
return Math.sin(degrees * MathUtils.degRad);
|
||||
}
|
||||
|
||||
static atan2Deg(y: number, x: number) {
|
||||
return Math.atan2(y, x) * MathUtils.degRad;
|
||||
}
|
||||
|
||||
static signum (value: number): number {
|
||||
return value > 0 ? 1 : value < 0 ? -1 : 0;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user