mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-03-26 22:49:01 +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 { VertexAttachment, Attachment } from "./attachments/Attachment.js";
|
||||||
import { IkConstraint } from "./IkConstraint.js";
|
import { IkConstraint } from "./IkConstraint.js";
|
||||||
import { PathConstraint } from "./PathConstraint.js";
|
import { PathConstraint } from "./PathConstraint.js";
|
||||||
import { Skeleton } from "./Skeleton.js";
|
import { Physics, Skeleton } from "./Skeleton.js";
|
||||||
import { Slot } from "./Slot.js";
|
import { Slot } from "./Slot.js";
|
||||||
import { TransformConstraint } from "./TransformConstraint.js";
|
import { TransformConstraint } from "./TransformConstraint.js";
|
||||||
import { StringSet, Utils, MathUtils, NumberArrayLike } from "./Utils.js";
|
import { StringSet, Utils, MathUtils, NumberArrayLike } from "./Utils.js";
|
||||||
import { Event } from "./Event.js";
|
import { Event } from "./Event.js";
|
||||||
import { HasTextureRegion } from "./attachments/HasTextureRegion.js";
|
import { HasTextureRegion } from "./attachments/HasTextureRegion.js";
|
||||||
import { SequenceMode, SequenceModeValues } from "./attachments/Sequence.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. */
|
/** A simple container for a list of timelines and a name. */
|
||||||
export class Animation {
|
export class Animation {
|
||||||
@ -150,7 +152,16 @@ const Property = {
|
|||||||
pathConstraintSpacing: 17,
|
pathConstraintSpacing: 17,
|
||||||
pathConstraintMix: 18,
|
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. */
|
/** 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*/);
|
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. */
|
/** 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) {
|
apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event> | null, alpha: number, blend: MixBlend, direction: MixDirection) {
|
||||||
let bone = skeleton.bones[this.boneIndex];
|
let bone = skeleton.bones[this.boneIndex];
|
||||||
if (!bone.active) return;
|
if (bone.active) bone.rotation = this.getRelativeValue(time, alpha, blend, bone.rotation, bone.data.rotation);
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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) {
|
apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
|
||||||
let bone = skeleton.bones[this.boneIndex];
|
let bone = skeleton.bones[this.boneIndex];
|
||||||
if (!bone.active) return;
|
if (bone.active) bone.x = this.getRelativeValue(time, alpha, blend, bone.x, bone.data.x);
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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) {
|
apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
|
||||||
let bone = skeleton.bones[this.boneIndex];
|
let bone = skeleton.bones[this.boneIndex];
|
||||||
if (!bone.active) return;
|
if (bone.active) bone.y = this.getRelativeValue(time, alpha, blend, bone.y, bone.data.y);
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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) {
|
apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
|
||||||
let bone = skeleton.bones[this.boneIndex];
|
let bone = skeleton.bones[this.boneIndex];
|
||||||
if (!bone.active) return;
|
if (bone.active) bone.scaleX = this.getScaleValue(time, alpha, blend, direction, bone.scaleX, bone.data.scaleX);
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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) {
|
apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
|
||||||
let bone = skeleton.bones[this.boneIndex];
|
let bone = skeleton.bones[this.boneIndex];
|
||||||
if (!bone.active) return;
|
if (bone.active) bone.scaleY = this.getScaleValue(time, alpha, blend, direction, bone.scaleX, bone.data.scaleY);
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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) {
|
apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
|
||||||
let bone = skeleton.bones[this.boneIndex];
|
let bone = skeleton.bones[this.boneIndex];
|
||||||
if (!bone.active) return;
|
if (bone.active) bone.shearX = this.getRelativeValue(time, alpha, blend, bone.shearX, bone.data.shearX);
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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) {
|
apply (skeleton: Skeleton, lastTime: number, time: number, events: Array<Event>, alpha: number, blend: MixBlend, direction: MixDirection) {
|
||||||
let bone = skeleton.bones[this.boneIndex];
|
let bone = skeleton.bones[this.boneIndex];
|
||||||
if (!bone.active) return;
|
if (bone.active) bone.shearY = this.getRelativeValue(time, alpha, blend, bone.shearX, bone.data.shearY);
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1119,7 +992,7 @@ export class AlphaTimeline extends CurveTimeline1 implements SlotTimeline {
|
|||||||
if (!slot.bone.active) return;
|
if (!slot.bone.active) return;
|
||||||
|
|
||||||
let color = slot.color;
|
let color = slot.color;
|
||||||
if (time < this.frames[0]) { // Time is before first frame.
|
if (time < this.frames[0]) {
|
||||||
let setup = slot.data.color;
|
let setup = slot.data.color;
|
||||||
switch (blend) {
|
switch (blend) {
|
||||||
case MixBlend.setup:
|
case MixBlend.setup:
|
||||||
@ -1547,7 +1420,7 @@ export class DeformTimeline extends CurveTimeline implements SlotTimeline {
|
|||||||
}
|
}
|
||||||
|
|
||||||
deform.length = vertexCount;
|
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];
|
let lastVertices = vertices[frames.length - 1];
|
||||||
if (alpha == 1) {
|
if (alpha == 1) {
|
||||||
if (blend == MixBlend.add) {
|
if (blend == MixBlend.add) {
|
||||||
@ -1711,12 +1584,12 @@ export class EventTimeline extends Timeline {
|
|||||||
let frames = this.frames;
|
let frames = this.frames;
|
||||||
let frameCount = this.frames.length;
|
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);
|
this.apply(skeleton, lastTime, Number.MAX_VALUE, firedEvents, alpha, blend, direction);
|
||||||
lastTime = -1;
|
lastTime = -1;
|
||||||
} else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame.
|
} else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame.
|
||||||
return;
|
return;
|
||||||
if (time < frames[0]) return; // Time is before first frame.
|
if (time < frames[0]) return;
|
||||||
|
|
||||||
let i = 0;
|
let i = 0;
|
||||||
if (lastTime < frames[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},
|
/** Changes an IK constraint's {@link IkConstraint#mix}, {@link IkConstraint#softness},
|
||||||
* {@link IkConstraint#bendDirection}, {@link IkConstraint#stretch}, and {@link IkConstraint#compress}. */
|
* {@link IkConstraint#bendDirection}, {@link IkConstraint#stretch}, and {@link IkConstraint#compress}. */
|
||||||
export class IkConstraintTimeline extends CurveTimeline {
|
export class IkConstraintTimeline extends CurveTimeline {
|
||||||
/** The index of the IK constraint slot in {@link Skeleton#ikConstraints} that will be changed. */
|
/** The index of the IK constraint in {@link Skeleton#getIkConstraints()} that will be changed when this timeline is */
|
||||||
ikConstraintIndex: number = 0;
|
constraintIndex: number = 0;
|
||||||
|
|
||||||
constructor (frameCount: number, bezierCount: number, ikConstraintIndex: number) {
|
constructor (frameCount: number, bezierCount: number, ikConstraintIndex: number) {
|
||||||
super(frameCount, bezierCount, [
|
super(frameCount, bezierCount, [
|
||||||
Property.ikConstraint + "|" + ikConstraintIndex
|
Property.ikConstraint + "|" + ikConstraintIndex
|
||||||
]);
|
]);
|
||||||
this.ikConstraintIndex = ikConstraintIndex;
|
this.constraintIndex = ikConstraintIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
getFrameEntries () {
|
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) {
|
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;
|
if (!constraint.active) return;
|
||||||
|
|
||||||
let frames = this.frames;
|
let frames = this.frames;
|
||||||
@ -1884,13 +1757,13 @@ export class IkConstraintTimeline extends CurveTimeline {
|
|||||||
* {@link TransformConstraint#scaleMix}, and {@link TransformConstraint#shearMix}. */
|
* {@link TransformConstraint#scaleMix}, and {@link TransformConstraint#shearMix}. */
|
||||||
export class TransformConstraintTimeline extends CurveTimeline {
|
export class TransformConstraintTimeline extends CurveTimeline {
|
||||||
/** The index of the transform constraint slot in {@link Skeleton#transformConstraints} that will be changed. */
|
/** 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) {
|
constructor (frameCount: number, bezierCount: number, transformConstraintIndex: number) {
|
||||||
super(frameCount, bezierCount, [
|
super(frameCount, bezierCount, [
|
||||||
Property.transformConstraint + "|" + transformConstraintIndex
|
Property.transformConstraint + "|" + transformConstraintIndex
|
||||||
]);
|
]);
|
||||||
this.transformConstraintIndex = transformConstraintIndex;
|
this.constraintIndex = transformConstraintIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
getFrameEntries () {
|
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) {
|
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;
|
if (!constraint.active) return;
|
||||||
|
|
||||||
let frames = this.frames;
|
let frames = this.frames;
|
||||||
@ -1996,85 +1869,52 @@ export class TransformConstraintTimeline extends CurveTimeline {
|
|||||||
|
|
||||||
/** Changes a path constraint's {@link PathConstraint#position}. */
|
/** Changes a path constraint's {@link PathConstraint#position}. */
|
||||||
export class PathConstraintPositionTimeline extends CurveTimeline1 {
|
export class PathConstraintPositionTimeline extends CurveTimeline1 {
|
||||||
/** The index of the path constraint slot in {@link Skeleton#pathConstraints} that will be changed. */
|
/** The index of the path constraint in {@link Skeleton#getPathConstraints()} that will be changed when this timeline is
|
||||||
pathConstraintIndex: number = 0;
|
* applied. */
|
||||||
|
constraintIndex: number = 0;
|
||||||
|
|
||||||
constructor (frameCount: number, bezierCount: number, pathConstraintIndex: number) {
|
constructor (frameCount: number, bezierCount: number, pathConstraintIndex: number) {
|
||||||
super(frameCount, bezierCount, Property.pathConstraintPosition + "|" + pathConstraintIndex);
|
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) {
|
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;
|
if (constraint.active)
|
||||||
|
constraint.position = this.getAbsoluteValue(time, alpha, blend, constraint.position, constraint.data.position);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Changes a path constraint's {@link PathConstraint#spacing}. */
|
/** Changes a path constraint's {@link PathConstraint#spacing}. */
|
||||||
export class PathConstraintSpacingTimeline extends CurveTimeline1 {
|
export class PathConstraintSpacingTimeline extends CurveTimeline1 {
|
||||||
/** The index of the path constraint slot in {@link Skeleton#getPathConstraints()} that will be changed. */
|
/** The index of the path constraint in {@link Skeleton#getPathConstraints()} that will be changed when this timeline is
|
||||||
pathConstraintIndex = 0;
|
* applied. */
|
||||||
|
constraintIndex = 0;
|
||||||
|
|
||||||
constructor (frameCount: number, bezierCount: number, pathConstraintIndex: number) {
|
constructor (frameCount: number, bezierCount: number, pathConstraintIndex: number) {
|
||||||
super(frameCount, bezierCount, Property.pathConstraintSpacing + "|" + pathConstraintIndex);
|
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) {
|
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;
|
if (constraint.active)
|
||||||
|
constraint.spacing = this.getAbsoluteValue(time, alpha, blend, constraint.spacing, constraint.data.spacing);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Changes a transform constraint's {@link PathConstraint#getMixRotate()}, {@link PathConstraint#getMixX()}, and
|
/** Changes a transform constraint's {@link PathConstraint#getMixRotate()}, {@link PathConstraint#getMixX()}, and
|
||||||
* {@link PathConstraint#getMixY()}. */
|
* {@link PathConstraint#getMixY()}. */
|
||||||
export class PathConstraintMixTimeline extends CurveTimeline {
|
export class PathConstraintMixTimeline extends CurveTimeline {
|
||||||
/** The index of the path constraint slot in {@link Skeleton#getPathConstraints()} that will be changed. */
|
/** The index of the path constraint in {@link Skeleton#getPathConstraints()} that will be changed when this timeline is
|
||||||
pathConstraintIndex = 0;
|
* applied. */
|
||||||
|
constraintIndex = 0;
|
||||||
|
|
||||||
constructor (frameCount: number, bezierCount: number, pathConstraintIndex: number) {
|
constructor (frameCount: number, bezierCount: number, pathConstraintIndex: number) {
|
||||||
super(frameCount, bezierCount, [
|
super(frameCount, bezierCount, [
|
||||||
Property.pathConstraintMix + "|" + pathConstraintIndex
|
Property.pathConstraintMix + "|" + pathConstraintIndex
|
||||||
]);
|
]);
|
||||||
this.pathConstraintIndex = pathConstraintIndex;
|
this.constraintIndex = pathConstraintIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
getFrameEntries () {
|
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) {
|
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;
|
if (!constraint.active) return;
|
||||||
|
|
||||||
let frames = this.frames;
|
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}. */
|
/** Changes a slot's {@link Slot#getSequenceIndex()} for an attachment's {@link Sequence}. */
|
||||||
export class SequenceTimeline extends Timeline implements SlotTimeline {
|
export class SequenceTimeline extends Timeline implements SlotTimeline {
|
||||||
static ENTRIES = 3;
|
static ENTRIES = 3;
|
||||||
@ -2199,7 +2290,7 @@ export class SequenceTimeline extends Timeline implements SlotTimeline {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let frames = this.frames;
|
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;
|
if (blend == MixBlend.setup || blend == MixBlend.first) slot.sequenceIndex = -1;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -173,11 +173,13 @@ export class AnimationState {
|
|||||||
let blend: MixBlend = i == 0 ? MixBlend.first : current.mixBlend;
|
let blend: MixBlend = i == 0 ? MixBlend.first : current.mixBlend;
|
||||||
|
|
||||||
// Apply mixing from entries first.
|
// Apply mixing from entries first.
|
||||||
let mix = current.alpha;
|
let alpha = current.alpha;
|
||||||
if (current.mixingFrom)
|
if (current.mixingFrom)
|
||||||
mix *= this.applyMixingFrom(current, skeleton, blend);
|
alpha *= this.applyMixingFrom(current, skeleton, blend);
|
||||||
else if (current.trackTime >= current.trackEnd && !current.next)
|
else if (current.trackTime >= current.trackEnd && !current.next)
|
||||||
mix = 0;
|
alpha = 0;
|
||||||
|
let attachments = alpha >= current.alphaAttachmentThreshold;
|
||||||
|
|
||||||
|
|
||||||
// Apply current entry.
|
// Apply current entry.
|
||||||
let animationLast = current.animationLast, animationTime = current.getAnimationTime(), applyTime = animationTime;
|
let animationLast = current.animationLast, animationTime = current.getAnimationTime(), applyTime = animationTime;
|
||||||
@ -188,17 +190,18 @@ export class AnimationState {
|
|||||||
}
|
}
|
||||||
let timelines = current.animation!.timelines;
|
let timelines = current.animation!.timelines;
|
||||||
let timelineCount = timelines.length;
|
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++) {
|
for (let ii = 0; ii < timelineCount; ii++) {
|
||||||
// Fixes issue #302 on IOS9 where mix, blend sometimes became undefined and caused assets
|
// 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.
|
// to sometimes stop rendering when using color correction, as their RGBA values become NaN.
|
||||||
// (https://github.com/pixijs/pixi-spine/issues/302)
|
// (https://github.com/pixijs/pixi-spine/issues/302)
|
||||||
Utils.webkit602BugfixHelper(mix, blend);
|
Utils.webkit602BugfixHelper(alpha, blend);
|
||||||
var timeline = timelines[ii];
|
var timeline = timelines[ii];
|
||||||
if (timeline instanceof AttachmentTimeline)
|
if (timeline instanceof AttachmentTimeline)
|
||||||
this.applyAttachmentTimeline(timeline, skeleton, applyTime, blend, true);
|
this.applyAttachmentTimeline(timeline, skeleton, applyTime, blend, attachments);
|
||||||
else
|
else
|
||||||
timeline.apply(skeleton, animationLast, applyTime, applyEvents, mix, blend, MixDirection.mixIn);
|
timeline.apply(skeleton, animationLast, applyTime, applyEvents, alpha, blend, MixDirection.mixIn);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let timelineMode = current.timelineMode;
|
let timelineMode = current.timelineMode;
|
||||||
@ -211,13 +214,13 @@ export class AnimationState {
|
|||||||
let timeline = timelines[ii];
|
let timeline = timelines[ii];
|
||||||
let timelineBlend = timelineMode[ii] == SUBSEQUENT ? blend : MixBlend.setup;
|
let timelineBlend = timelineMode[ii] == SUBSEQUENT ? blend : MixBlend.setup;
|
||||||
if (!shortestRotation && timeline instanceof RotateTimeline) {
|
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) {
|
} else if (timeline instanceof AttachmentTimeline) {
|
||||||
this.applyAttachmentTimeline(timeline, skeleton, applyTime, blend, true);
|
this.applyAttachmentTimeline(timeline, skeleton, applyTime, blend, attachments);
|
||||||
} else {
|
} else {
|
||||||
// This fixes the WebKit 602 specific issue described at http://esotericsoftware.com/forum/iOS-10-disappearing-graphics-10109
|
// This fixes the WebKit 602 specific issue described at http://esotericsoftware.com/forum/iOS-10-disappearing-graphics-10109
|
||||||
Utils.webkit602BugfixHelper(mix, blend);
|
Utils.webkit602BugfixHelper(alpha, blend);
|
||||||
timeline.apply(skeleton, animationLast, applyTime, applyEvents, mix, timelineBlend, MixDirection.mixIn);
|
timeline.apply(skeleton, animationLast, applyTime, applyEvents, alpha, timelineBlend, MixDirection.mixIn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -259,7 +262,7 @@ export class AnimationState {
|
|||||||
if (blend != MixBlend.first) blend = from.mixBlend;
|
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 timelines = from.animation!.timelines;
|
||||||
let timelineCount = timelines.length;
|
let timelineCount = timelines.length;
|
||||||
let alphaHold = from.alpha * to.interruptAlpha, alphaMix = alphaHold * (1 - mix);
|
let alphaHold = from.alpha * to.interruptAlpha, alphaMix = alphaHold * (1 - mix);
|
||||||
@ -316,7 +319,7 @@ export class AnimationState {
|
|||||||
if (!shortestRotation && timeline instanceof RotateTimeline)
|
if (!shortestRotation && timeline instanceof RotateTimeline)
|
||||||
this.applyRotateTimeline(timeline, skeleton, applyTime, alpha, timelineBlend, from.timelinesRotation, i << 1, firstFrame);
|
this.applyRotateTimeline(timeline, skeleton, applyTime, alpha, timelineBlend, from.timelinesRotation, i << 1, firstFrame);
|
||||||
else if (timeline instanceof AttachmentTimeline)
|
else if (timeline instanceof AttachmentTimeline)
|
||||||
this.applyAttachmentTimeline(timeline, skeleton, applyTime, timelineBlend, attachments);
|
this.applyAttachmentTimeline(timeline, skeleton, applyTime, timelineBlend, attachments && alpha >= from.alphaAttachmentThreshold);
|
||||||
else {
|
else {
|
||||||
// This fixes the WebKit 602 specific issue described at http://esotericsoftware.com/forum/iOS-10-disappearing-graphics-10109
|
// This fixes the WebKit 602 specific issue described at http://esotericsoftware.com/forum/iOS-10-disappearing-graphics-10109
|
||||||
Utils.webkit602BugfixHelper(alpha, blend);
|
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.
|
// Mix between rotations using the direction of the shortest route on the first frame while detecting crosses.
|
||||||
let total = 0, diff = r2 - r1;
|
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) {
|
if (diff == 0) {
|
||||||
total = timelinesRotation[i];
|
total = timelinesRotation[i];
|
||||||
} else {
|
} else {
|
||||||
@ -661,8 +664,9 @@ export class AnimationState {
|
|||||||
entry.shortestRotation = false;
|
entry.shortestRotation = false;
|
||||||
|
|
||||||
entry.eventThreshold = 0;
|
entry.eventThreshold = 0;
|
||||||
entry.attachmentThreshold = 0;
|
entry.alphaAttachmentThreshold = 0;
|
||||||
entry.drawOrderThreshold = 0;
|
entry.mixAttachmentThreshold = 0;
|
||||||
|
entry.mixDrawOrderThreshold = 0;
|
||||||
|
|
||||||
entry.animationStart = 0;
|
entry.animationStart = 0;
|
||||||
entry.animationEnd = animation.duration;
|
entry.animationEnd = animation.duration;
|
||||||
@ -843,12 +847,16 @@ export class TrackEntry {
|
|||||||
/** When the mix percentage ({@link #mixtime} / {@link #mixDuration}) is less than the
|
/** 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
|
* `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. */
|
* 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
|
/** When {@link #getAlpha()} is greater than <code>alphaAttachmentThreshold</code>, attachment timelines are applied.
|
||||||
* `drawOrderThreshold`, draw order timelines are applied while this animation is being mixed out. Defaults to 0,
|
* Defaults to 0, so attachment timelines are always applied. */
|
||||||
* so draw order timelines are not applied while this animation is being mixed out. */
|
alphaAttachmentThreshold: number = 0;
|
||||||
drawOrderThreshold: 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.
|
/** 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
|
* 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
|
* {@link #delay} is set using the mix duration from the {@link AnimationStateData}, not a mix duration set
|
||||||
* afterward. */
|
* 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
|
/** 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
|
* 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.
|
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 {
|
export class EventQueue {
|
||||||
|
|||||||
@ -28,7 +28,7 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import { BoneData, TransformMode } from "./BoneData.js";
|
import { BoneData, TransformMode } from "./BoneData.js";
|
||||||
import { Skeleton } from "./Skeleton.js";
|
import { Physics, Skeleton } from "./Skeleton.js";
|
||||||
import { Updatable } from "./Updatable.js";
|
import { Updatable } from "./Updatable.js";
|
||||||
import { MathUtils, Vector2 } from "./Utils.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. */
|
/** 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);
|
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;
|
let parent = this.parent;
|
||||||
if (!parent) { // Root bone.
|
if (!parent) { // Root bone.
|
||||||
let skeleton = this.skeleton;
|
let skeleton = this.skeleton;
|
||||||
let rotationY = rotation + 90 + shearY;
|
const sx = skeleton.scaleX, sy = skeleton.scaleY;
|
||||||
let sx = skeleton.scaleX;
|
const rx = (rotation + shearX) * MathUtils.degRad;
|
||||||
let sy = skeleton.scaleY;
|
const ry = (rotation + 90 + shearY) * MathUtils.degRad;
|
||||||
this.a = MathUtils.cosDeg(rotation + shearX) * scaleX * sx;
|
this.a = Math.cos(rx) * scaleX * sx;
|
||||||
this.b = MathUtils.cosDeg(rotationY) * scaleY * sx;
|
this.b = Math.cos(ry) * scaleY * sx;
|
||||||
this.c = MathUtils.sinDeg(rotation + shearX) * scaleX * sy;
|
this.c = Math.sin(rx) * scaleX * sy;
|
||||||
this.d = MathUtils.sinDeg(rotationY) * scaleY * sy;
|
this.d = Math.sin(ry) * scaleY * sy;
|
||||||
this.worldX = x * sx + skeleton.x;
|
this.worldX = x * sx + skeleton.x;
|
||||||
this.worldY = y * sy + skeleton.y;
|
this.worldY = y * sy + skeleton.y;
|
||||||
return;
|
return;
|
||||||
@ -176,11 +176,12 @@ export class Bone implements Updatable {
|
|||||||
|
|
||||||
switch (this.data.transformMode) {
|
switch (this.data.transformMode) {
|
||||||
case TransformMode.Normal: {
|
case TransformMode.Normal: {
|
||||||
let rotationY = rotation + 90 + shearY;
|
const rx = (rotation + shearX) * MathUtils.degRad;
|
||||||
let la = MathUtils.cosDeg(rotation + shearX) * scaleX;
|
const ry = (rotation + 90 + shearY) * MathUtils.degRad;
|
||||||
let lb = MathUtils.cosDeg(rotationY) * scaleY;
|
const la = Math.cos(rx) * scaleX;
|
||||||
let lc = MathUtils.sinDeg(rotation + shearX) * scaleX;
|
const lb = Math.cos(ry) * scaleY;
|
||||||
let ld = MathUtils.sinDeg(rotationY) * scaleY;
|
const lc = Math.sin(rx) * scaleX;
|
||||||
|
const ld = Math.sin(ry) * scaleY;
|
||||||
this.a = pa * la + pb * lc;
|
this.a = pa * la + pb * lc;
|
||||||
this.b = pa * lb + pb * ld;
|
this.b = pa * lb + pb * ld;
|
||||||
this.c = pc * la + pd * lc;
|
this.c = pc * la + pd * lc;
|
||||||
@ -188,11 +189,12 @@ export class Bone implements Updatable {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case TransformMode.OnlyTranslation: {
|
case TransformMode.OnlyTranslation: {
|
||||||
let rotationY = rotation + 90 + shearY;
|
const rx = (rotation + shearX) * MathUtils.degRad;
|
||||||
this.a = MathUtils.cosDeg(rotation + shearX) * scaleX;
|
const ry = (rotation + 90 + shearY) * MathUtils.degRad;
|
||||||
this.b = MathUtils.cosDeg(rotationY) * scaleY;
|
this.a = Math.cos(rx) * scaleX;
|
||||||
this.c = MathUtils.sinDeg(rotation + shearX) * scaleX;
|
this.b = Math.cos(ry) * scaleY;
|
||||||
this.d = MathUtils.sinDeg(rotationY) * scaleY;
|
this.c = Math.sin(rx) * scaleX;
|
||||||
|
this.d = Math.sin(ry) * scaleY;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case TransformMode.NoRotationOrReflection: {
|
case TransformMode.NoRotationOrReflection: {
|
||||||
@ -210,12 +212,12 @@ export class Bone implements Updatable {
|
|||||||
pc = 0;
|
pc = 0;
|
||||||
prx = 90 - Math.atan2(pd, pb) * MathUtils.radDeg;
|
prx = 90 - Math.atan2(pd, pb) * MathUtils.radDeg;
|
||||||
}
|
}
|
||||||
let rx = rotation + shearX - prx;
|
const rx = (rotation + shearX - prx) * MathUtils.degRad;
|
||||||
let ry = rotation + shearY - prx + 90;
|
const ry = (rotation + shearY - prx + 90) * MathUtils.degRad;
|
||||||
let la = MathUtils.cosDeg(rx) * scaleX;
|
const la = Math.cos(rx) * scaleX;
|
||||||
let lb = MathUtils.cosDeg(ry) * scaleY;
|
const lb = Math.cos(ry) * scaleY;
|
||||||
let lc = MathUtils.sinDeg(rx) * scaleX;
|
const lc = Math.sin(rx) * scaleX;
|
||||||
let ld = MathUtils.sinDeg(ry) * scaleY;
|
const ld = Math.sin(ry) * scaleY;
|
||||||
this.a = pa * la - pb * lc;
|
this.a = pa * la - pb * lc;
|
||||||
this.b = pa * lb - pb * ld;
|
this.b = pa * lb - pb * ld;
|
||||||
this.c = pc * la + pd * lc;
|
this.c = pc * la + pd * lc;
|
||||||
@ -224,8 +226,8 @@ export class Bone implements Updatable {
|
|||||||
}
|
}
|
||||||
case TransformMode.NoScale:
|
case TransformMode.NoScale:
|
||||||
case TransformMode.NoScaleOrReflection: {
|
case TransformMode.NoScaleOrReflection: {
|
||||||
let cos = MathUtils.cosDeg(rotation);
|
rotation *= MathUtils.degRad;
|
||||||
let sin = MathUtils.sinDeg(rotation);
|
const cos = Math.cos(rotation), sin = Math.sin(rotation);
|
||||||
let za = (pa * cos + pb * sin) / this.skeleton.scaleX;
|
let za = (pa * cos + pb * sin) / this.skeleton.scaleX;
|
||||||
let zc = (pc * cos + pd * sin) / this.skeleton.scaleY;
|
let zc = (pc * cos + pd * sin) / this.skeleton.scaleY;
|
||||||
let s = Math.sqrt(za * za + zc * zc);
|
let s = Math.sqrt(za * za + zc * zc);
|
||||||
@ -235,13 +237,15 @@ export class Bone implements Updatable {
|
|||||||
s = Math.sqrt(za * za + zc * zc);
|
s = Math.sqrt(za * za + zc * zc);
|
||||||
if (this.data.transformMode == TransformMode.NoScale
|
if (this.data.transformMode == TransformMode.NoScale
|
||||||
&& (pa * pd - pb * pc < 0) != (this.skeleton.scaleX < 0 != this.skeleton.scaleY < 0)) s = -s;
|
&& (pa * pd - pb * pc < 0) != (this.skeleton.scaleX < 0 != this.skeleton.scaleY < 0)) s = -s;
|
||||||
let r = Math.PI / 2 + Math.atan2(zc, za);
|
rotation = Math.PI / 2 + Math.atan2(zc, za);
|
||||||
let zb = Math.cos(r) * s;
|
const zb = Math.cos(rotation) * s;
|
||||||
let zd = Math.sin(r) * s;
|
const zd = Math.sin(rotation) * s;
|
||||||
let la = MathUtils.cosDeg(shearX) * scaleX;
|
shearX *= MathUtils.degRad;
|
||||||
let lb = MathUtils.cosDeg(90 + shearY) * scaleY;
|
shearY = (90 + shearY) * MathUtils.degRad;
|
||||||
let lc = MathUtils.sinDeg(shearX) * scaleX;
|
const la = Math.cos(shearX) * scaleX;
|
||||||
let ld = MathUtils.sinDeg(90 + shearY) * scaleY;
|
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.a = za * la + zb * lc;
|
||||||
this.b = za * lb + zb * ld;
|
this.b = za * lb + zb * ld;
|
||||||
this.c = zc * la + zd * lc;
|
this.c = zc * la + zd * lc;
|
||||||
@ -267,26 +271,6 @@ export class Bone implements Updatable {
|
|||||||
this.shearY = data.shearY;
|
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.
|
/** 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
|
* 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. */
|
/** Transforms a point from world coordinates to the bone's local coordinates. */
|
||||||
worldToLocal (world: Vector2) {
|
worldToLocal (world: Vector2) {
|
||||||
let invDet = 1 / (this.a * this.d - this.b * this.c);
|
let invDet = 1 / (this.a * this.d - this.b * this.c);
|
||||||
@ -391,6 +396,18 @@ export class Bone implements Updatable {
|
|||||||
return local;
|
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. */
|
/** Transforms a world rotation to a local rotation. */
|
||||||
worldToLocalRotation (worldRotation: number) {
|
worldToLocalRotation (worldRotation: number) {
|
||||||
let sin = MathUtils.sinDeg(worldRotation), cos = MathUtils.cosDeg(worldRotation);
|
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.
|
/** Rotates the world transform the specified amount.
|
||||||
* <p>
|
* <p>
|
||||||
* After changes are made to the world transform, {@link #updateAppliedTransform()} should be called and {@link #update()} will
|
* After changes are made to the world transform, {@link #updateAppliedTransform()} should be called and
|
||||||
* need to be called on any child bones, recursively. */
|
* {@link #update(Physics)} will need to be called on any child bones, recursively. */
|
||||||
rotateWorld (degrees: number) {
|
rotateWorld (degrees: number) {
|
||||||
let a = this.a, b = this.b, c = this.c, d = this.d;
|
degrees *= MathUtils.degRad;
|
||||||
let cos = MathUtils.cosDeg(degrees), sin = MathUtils.sinDeg(degrees);
|
const sin = Math.sin(degrees), cos = Math.cos(degrees);
|
||||||
this.a = cos * a - sin * c;
|
const ra = this.a, rb = this.b;
|
||||||
this.b = cos * b - sin * d;
|
this.a = cos * ra - sin * this.c;
|
||||||
this.c = sin * a + cos * c;
|
this.b = cos * rb - sin * this.d;
|
||||||
this.d = sin * b + cos * 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. */
|
/** The local y translation. */
|
||||||
y = 0;
|
y = 0;
|
||||||
|
|
||||||
/** The local rotation. */
|
/** The local rotation in degrees, counter clockwise. */
|
||||||
rotation = 0;
|
rotation = 0;
|
||||||
|
|
||||||
/** The local scaleX. */
|
/** The local scaleX. */
|
||||||
@ -76,6 +76,12 @@ export class BoneData {
|
|||||||
* rendered at runtime. */
|
* rendered at runtime. */
|
||||||
color = new Color();
|
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) {
|
constructor (index: number, name: string, parent: BoneData | null) {
|
||||||
if (index < 0) throw new Error("index must be >= 0.");
|
if (index < 0) throw new Error("index must be >= 0.");
|
||||||
if (!name) throw new Error("name cannot be null.");
|
if (!name) throw new Error("name cannot be null.");
|
||||||
|
|||||||
@ -30,7 +30,7 @@
|
|||||||
import { Bone } from "./Bone.js";
|
import { Bone } from "./Bone.js";
|
||||||
import { TransformMode } from "./BoneData.js";
|
import { TransformMode } from "./BoneData.js";
|
||||||
import { IkConstraintData } from "./IkConstraintData.js";
|
import { IkConstraintData } from "./IkConstraintData.js";
|
||||||
import { Skeleton } from "./Skeleton.js";
|
import { Physics, Skeleton } from "./Skeleton.js";
|
||||||
import { Updatable } from "./Updatable.js";
|
import { Updatable } from "./Updatable.js";
|
||||||
import { MathUtils } from "./Utils.js";
|
import { MathUtils } from "./Utils.js";
|
||||||
|
|
||||||
@ -90,7 +90,16 @@ export class IkConstraint implements Updatable {
|
|||||||
return this.active;
|
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;
|
if (this.mix == 0) return;
|
||||||
let target = this.target;
|
let target = this.target;
|
||||||
let bones = this.bones;
|
let bones = this.bones;
|
||||||
@ -149,11 +158,14 @@ export class IkConstraint implements Updatable {
|
|||||||
tx = targetX - bone.worldX;
|
tx = targetX - bone.worldX;
|
||||||
ty = targetY - bone.worldY;
|
ty = targetY - bone.worldY;
|
||||||
}
|
}
|
||||||
let b = bone.data.length * sx, dd = Math.sqrt(tx * tx + ty * ty);
|
const b = bone.data.length * sx;
|
||||||
if ((compress && dd < b) || (stretch && dd > b) && b > 0.0001) {
|
if (b > 0.0001) {
|
||||||
let s = (dd / b - 1) * alpha + 1;
|
const dd = tx * tx + ty * ty;
|
||||||
sx *= s;
|
if ((compress && dd < b * b) || (stretch && dd > b * b)) {
|
||||||
if (uniform) sy *= s;
|
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,
|
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 { PathAttachment } from "./attachments/PathAttachment.js";
|
||||||
import { Bone } from "./Bone.js";
|
import { Bone } from "./Bone.js";
|
||||||
import { PathConstraintData, RotateMode, SpacingMode, PositionMode } from "./PathConstraintData.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 { Slot } from "./Slot.js";
|
||||||
import { Updatable } from "./Updatable.js";
|
import { Updatable } from "./Updatable.js";
|
||||||
import { Utils, MathUtils } from "./Utils.js";
|
import { Utils, MathUtils } from "./Utils.js";
|
||||||
@ -95,7 +95,16 @@ export class PathConstraint implements Updatable {
|
|||||||
return this.active;
|
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();
|
let attachment = this.target.getAttachment();
|
||||||
if (!(attachment instanceof PathAttachment)) return;
|
if (!(attachment instanceof PathAttachment)) return;
|
||||||
|
|
||||||
@ -116,12 +125,8 @@ export class PathConstraint implements Updatable {
|
|||||||
for (let i = 0, n = spacesCount - 1; i < n; i++) {
|
for (let i = 0, n = spacesCount - 1; i < n; i++) {
|
||||||
let bone = bones[i];
|
let bone = bones[i];
|
||||||
let setupLength = bone.data.length;
|
let setupLength = bone.data.length;
|
||||||
if (setupLength < PathConstraint.epsilon)
|
let x = setupLength * bone.a, y = setupLength * bone.c;
|
||||||
lengths[i] = 0;
|
lengths[i] = Math.sqrt(x * x + y * y);
|
||||||
else {
|
|
||||||
let x = setupLength * bone.a, y = setupLength * bone.c;
|
|
||||||
lengths[i] = Math.sqrt(x * x + y * y);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Utils.arrayFill(spaces, 1, spacesCount, spacing);
|
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 { Bone } from "./Bone.js";
|
||||||
import { IkConstraint } from "./IkConstraint.js";
|
import { IkConstraint } from "./IkConstraint.js";
|
||||||
import { PathConstraint } from "./PathConstraint.js";
|
import { PathConstraint } from "./PathConstraint.js";
|
||||||
|
import { PhysicsConstraint } from "./PhysicsConstraint.js";
|
||||||
import { SkeletonData } from "./SkeletonData.js";
|
import { SkeletonData } from "./SkeletonData.js";
|
||||||
import { Skin } from "./Skin.js";
|
import { Skin } from "./Skin.js";
|
||||||
import { Slot } from "./Slot.js";
|
import { Slot } from "./Slot.js";
|
||||||
@ -68,6 +69,10 @@ export class Skeleton {
|
|||||||
/** The skeleton's path constraints. */
|
/** The skeleton's path constraints. */
|
||||||
pathConstraints: Array<PathConstraint>;
|
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()}. */
|
/** The list of bones and constraints, sorted in the order they should be updated, as computed by {@link #updateCache()}. */
|
||||||
_updateCache = new Array<Updatable>();
|
_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. */
|
/** Sets the skeleton Y position, which is added to the root bone worldY position. */
|
||||||
y = 0;
|
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) {
|
constructor (data: SkeletonData) {
|
||||||
if (!data) throw new Error("data cannot be null.");
|
if (!data) throw new Error("data cannot be null.");
|
||||||
this.data = data;
|
this.data = data;
|
||||||
@ -145,6 +155,12 @@ export class Skeleton {
|
|||||||
this.pathConstraints.push(new PathConstraint(pathConstraintData, this));
|
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.color = new Color(1, 1, 1, 1);
|
||||||
this.updateCache();
|
this.updateCache();
|
||||||
}
|
}
|
||||||
@ -178,8 +194,9 @@ export class Skeleton {
|
|||||||
let ikConstraints = this.ikConstraints;
|
let ikConstraints = this.ikConstraints;
|
||||||
let transformConstraints = this.transformConstraints;
|
let transformConstraints = this.transformConstraints;
|
||||||
let pathConstraints = this.pathConstraints;
|
let pathConstraints = this.pathConstraints;
|
||||||
let ikCount = ikConstraints.length, transformCount = transformConstraints.length, pathCount = pathConstraints.length;
|
let physicsConstraints = this.physicsConstraints;
|
||||||
let constraintCount = ikCount + transformCount + pathCount;
|
let ikCount = ikConstraints.length, transformCount = transformConstraints.length, pathCount = pathConstraints.length, physicsCount = this.physicsConstraints.length;
|
||||||
|
let constraintCount = ikCount + transformCount + pathCount + physicsCount;
|
||||||
|
|
||||||
outer:
|
outer:
|
||||||
for (let i = 0; i < constraintCount; i++) {
|
for (let i = 0; i < constraintCount; i++) {
|
||||||
@ -204,6 +221,13 @@ export class Skeleton {
|
|||||||
continue outer;
|
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++)
|
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) {
|
sortBone (bone: Bone) {
|
||||||
if (!bone) return;
|
if (!bone) return;
|
||||||
if (bone.sorted) 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
|
* See [World transforms](http://esotericsoftware.com/spine-runtime-skeletons#World-transforms) in the Spine
|
||||||
* Runtimes Guide. */
|
* Runtimes Guide. */
|
||||||
updateWorldTransform () {
|
updateWorldTransform (physics: Physics) {
|
||||||
let bones = this.bones;
|
let bones = this.bones;
|
||||||
for (let i = 0, n = bones.length; i < n; i++) {
|
for (let i = 0, n = bones.length; i < n; i++) {
|
||||||
let bone = bones[i];
|
let bone = bones[i];
|
||||||
@ -353,10 +393,10 @@ export class Skeleton {
|
|||||||
|
|
||||||
let updateCache = this._updateCache;
|
let updateCache = this._updateCache;
|
||||||
for (let i = 0, n = updateCache.length; i < n; i++)
|
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.
|
// Apply the parent bone transform to the root bone. The root bone always inherits scale, rotation and reflection.
|
||||||
let rootBone = this.getRootBone();
|
let rootBone = this.getRootBone();
|
||||||
if (!rootBone) throw new Error("Root bone must not be null.");
|
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.worldX = pa * this.x + pb * this.y + parent.worldX;
|
||||||
rootBone.worldY = pc * this.x + pd * this.y + parent.worldY;
|
rootBone.worldY = pc * this.x + pd * this.y + parent.worldY;
|
||||||
|
|
||||||
let rotationY = rootBone.rotation + 90 + rootBone.shearY;
|
const rx = (rootBone.rotation + rootBone.shearX) * MathUtils.degRad;
|
||||||
let la = MathUtils.cosDeg(rootBone.rotation + rootBone.shearX) * rootBone.scaleX;
|
const ry = (rootBone.rotation + 90 + rootBone.shearY) * MathUtils.degRad;
|
||||||
let lb = MathUtils.cosDeg(rotationY) * rootBone.scaleY;
|
const la = Math.cos(rx) * rootBone.scaleX;
|
||||||
let lc = MathUtils.sinDeg(rootBone.rotation + rootBone.shearX) * rootBone.scaleX;
|
const lb = Math.cos(ry) * rootBone.scaleY;
|
||||||
let ld = MathUtils.sinDeg(rotationY) * 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.a = (pa * la + pb * lc) * this.scaleX;
|
||||||
rootBone.b = (pa * lb + pb * ld) * this.scaleX;
|
rootBone.b = (pa * lb + pb * ld) * this.scaleX;
|
||||||
rootBone.c = (pc * la + pd * lc) * this.scaleY;
|
rootBone.c = (pc * la + pd * lc) * this.scaleY;
|
||||||
@ -378,7 +419,7 @@ export class Skeleton {
|
|||||||
let updateCache = this._updateCache;
|
let updateCache = this._updateCache;
|
||||||
for (let i = 0, n = updateCache.length; i < n; i++) {
|
for (let i = 0, n = updateCache.length; i < n; i++) {
|
||||||
let updatable = updateCache[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. */
|
/** Sets the bones and constraints to their setup pose values. */
|
||||||
setBonesToSetupPose () {
|
setBonesToSetupPose () {
|
||||||
let bones = this.bones;
|
for (const bone of this.bones) bone.setToSetupPose();
|
||||||
for (let i = 0, n = bones.length; i < n; i++)
|
for (const constraint of this.ikConstraints) constraint.setToSetupPose();
|
||||||
bones[i].setToSetupPose();
|
for (const constraint of this.transformConstraints) constraint.setToSetupPose();
|
||||||
|
for (const constraint of this.pathConstraints) constraint.setToSetupPose();
|
||||||
let ikConstraints = this.ikConstraints;
|
for (const constraint of this.physicsConstraints) constraint.setToSetupPose();
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sets the slots and draw order to their setup pose values. */
|
/** Sets the slots and draw order to their setup pose values. */
|
||||||
@ -560,12 +570,7 @@ export class Skeleton {
|
|||||||
* @return May be null. */
|
* @return May be null. */
|
||||||
findIkConstraint (constraintName: string) {
|
findIkConstraint (constraintName: string) {
|
||||||
if (!constraintName) throw new Error("constraintName cannot be null.");
|
if (!constraintName) throw new Error("constraintName cannot be null.");
|
||||||
let ikConstraints = this.ikConstraints;
|
return this.ikConstraints.find((constraint) => constraint.data.name == constraintName) ?? null;
|
||||||
for (let i = 0, n = ikConstraints.length; i < n; i++) {
|
|
||||||
let ikConstraint = ikConstraints[i];
|
|
||||||
if (ikConstraint.data.name == constraintName) return ikConstraint;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Finds a transform constraint by comparing each transform constraint's name. It is more efficient to cache the results of
|
/** 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. */
|
* @return May be null. */
|
||||||
findTransformConstraint (constraintName: string) {
|
findTransformConstraint (constraintName: string) {
|
||||||
if (!constraintName) throw new Error("constraintName cannot be null.");
|
if (!constraintName) throw new Error("constraintName cannot be null.");
|
||||||
let transformConstraints = this.transformConstraints;
|
return this.transformConstraints.find((constraint) => constraint.data.name == constraintName) ?? null;
|
||||||
for (let i = 0, n = transformConstraints.length; i < n; i++) {
|
|
||||||
let constraint = transformConstraints[i];
|
|
||||||
if (constraint.data.name == constraintName) return constraint;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Finds a path constraint by comparing each path constraint's name. It is more efficient to cache the results of this method
|
/** 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. */
|
* @return May be null. */
|
||||||
findPathConstraint (constraintName: string) {
|
findPathConstraint (constraintName: string) {
|
||||||
if (!constraintName) throw new Error("constraintName cannot be null.");
|
if (!constraintName) throw new Error("constraintName cannot be null.");
|
||||||
let pathConstraints = this.pathConstraints;
|
return this.pathConstraints.find((constraint) => constraint.data.name == constraintName) ?? null;
|
||||||
for (let i = 0, n = pathConstraints.length; i < n; i++) {
|
}
|
||||||
let constraint = pathConstraints[i];
|
|
||||||
if (constraint.data.name == constraintName) return constraint;
|
/** 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. */
|
||||||
return null;
|
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 }`.
|
/** 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);
|
offset.set(minX, minY);
|
||||||
size.set(maxX - minX, maxY - 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 { EventData } from "./EventData.js";
|
||||||
import { IkConstraintData } from "./IkConstraintData.js";
|
import { IkConstraintData } from "./IkConstraintData.js";
|
||||||
import { PathConstraintData } from "./PathConstraintData.js";
|
import { PathConstraintData } from "./PathConstraintData.js";
|
||||||
|
import { PhysicsConstraintData } from "./PhysicsConstraintData.js";
|
||||||
import { Skin } from "./Skin.js";
|
import { Skin } from "./Skin.js";
|
||||||
import { SlotData } from "./SlotData.js";
|
import { SlotData } from "./SlotData.js";
|
||||||
import { TransformConstraintData } from "./TransformConstraintData.js";
|
import { TransformConstraintData } from "./TransformConstraintData.js";
|
||||||
@ -73,6 +74,9 @@ export class SkeletonData {
|
|||||||
/** The skeleton's path constraints. */
|
/** The skeleton's path constraints. */
|
||||||
pathConstraints = new Array<PathConstraintData>();
|
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. */
|
/** The X coordinate of the skeleton's axis aligned bounding box in the setup pose. */
|
||||||
x: number = 0;
|
x: number = 0;
|
||||||
|
|
||||||
@ -171,9 +175,9 @@ export class SkeletonData {
|
|||||||
* @return May be null. */
|
* @return May be null. */
|
||||||
findIkConstraint (constraintName: string) {
|
findIkConstraint (constraintName: string) {
|
||||||
if (!constraintName) throw new Error("constraintName cannot be null.");
|
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++) {
|
for (let i = 0, n = ikConstraints.length; i < n; i++) {
|
||||||
let constraint = ikConstraints[i];
|
const constraint = ikConstraints[i];
|
||||||
if (constraint.name == constraintName) return constraint;
|
if (constraint.name == constraintName) return constraint;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@ -184,9 +188,9 @@ export class SkeletonData {
|
|||||||
* @return May be null. */
|
* @return May be null. */
|
||||||
findTransformConstraint (constraintName: string) {
|
findTransformConstraint (constraintName: string) {
|
||||||
if (!constraintName) throw new Error("constraintName cannot be null.");
|
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++) {
|
for (let i = 0, n = transformConstraints.length; i < n; i++) {
|
||||||
let constraint = transformConstraints[i];
|
const constraint = transformConstraints[i];
|
||||||
if (constraint.name == constraintName) return constraint;
|
if (constraint.name == constraintName) return constraint;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@ -197,9 +201,22 @@ export class SkeletonData {
|
|||||||
* @return May be null. */
|
* @return May be null. */
|
||||||
findPathConstraint (constraintName: string) {
|
findPathConstraint (constraintName: string) {
|
||||||
if (!constraintName) throw new Error("constraintName cannot be null.");
|
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++) {
|
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;
|
if (constraint.name == constraintName) return constraint;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@ -32,7 +32,7 @@ import { MeshAttachment } from "./attachments/MeshAttachment.js";
|
|||||||
import { BoneData } from "./BoneData.js";
|
import { BoneData } from "./BoneData.js";
|
||||||
import { ConstraintData } from "./ConstraintData.js";
|
import { ConstraintData } from "./ConstraintData.js";
|
||||||
import { Skeleton } from "./Skeleton.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 **/
|
/** Stores an entry in the skin consisting of the slot index, name, and attachment **/
|
||||||
export class SkinEntry {
|
export class SkinEntry {
|
||||||
@ -51,6 +51,9 @@ export class Skin {
|
|||||||
bones = Array<BoneData>();
|
bones = Array<BoneData>();
|
||||||
constraints = new Array<ConstraintData>();
|
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) {
|
constructor (name: string) {
|
||||||
if (!name) throw new Error("name cannot be null.");
|
if (!name) throw new Error("name cannot be null.");
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
|||||||
@ -55,6 +55,9 @@ export class SlotData {
|
|||||||
/** The blend mode for drawing the slot's attachment. */
|
/** The blend mode for drawing the slot's attachment. */
|
||||||
blendMode: BlendMode = BlendMode.Normal;
|
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) {
|
constructor (index: number, name: string, boneData: BoneData) {
|
||||||
if (index < 0) throw new Error("index must be >= 0.");
|
if (index < 0) throw new Error("index must be >= 0.");
|
||||||
if (!name) throw new Error("name cannot be null.");
|
if (!name) throw new Error("name cannot be null.");
|
||||||
|
|||||||
@ -28,7 +28,7 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import { Bone } from "./Bone.js";
|
import { Bone } from "./Bone.js";
|
||||||
import { Skeleton } from "./Skeleton.js";
|
import { Physics, Skeleton } from "./Skeleton.js";
|
||||||
import { TransformConstraintData } from "./TransformConstraintData.js";
|
import { TransformConstraintData } from "./TransformConstraintData.js";
|
||||||
import { Updatable } from "./Updatable.js";
|
import { Updatable } from "./Updatable.js";
|
||||||
import { Vector2, MathUtils } from "./Utils.js";
|
import { Vector2, MathUtils } from "./Utils.js";
|
||||||
@ -79,7 +79,17 @@ export class TransformConstraint implements Updatable {
|
|||||||
return this.active;
|
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.mixRotate == 0 && this.mixX == 0 && this.mixY == 0 && this.mixScaleX == 0 && this.mixScaleY == 0 && this.mixShearY == 0) return;
|
||||||
|
|
||||||
if (this.data.local) {
|
if (this.data.local) {
|
||||||
@ -240,7 +250,7 @@ export class TransformConstraint implements Updatable {
|
|||||||
let rotation = bone.arotation;
|
let rotation = bone.arotation;
|
||||||
if (mixRotate != 0) {
|
if (mixRotate != 0) {
|
||||||
let r = target.arotation - rotation + this.data.offsetRotation;
|
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;
|
rotation += r * mixRotate;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -257,7 +267,7 @@ export class TransformConstraint implements Updatable {
|
|||||||
let shearY = bone.ashearY;
|
let shearY = bone.ashearY;
|
||||||
if (mixShearY != 0) {
|
if (mixShearY != 0) {
|
||||||
let r = target.ashearY - shearY + this.data.offsetShearY;
|
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;
|
shearY += r * mixShearY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -27,13 +27,19 @@
|
|||||||
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* 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()}. */
|
/** The interface for items updated by {@link Skeleton#updateWorldTransform()}. */
|
||||||
export interface Updatable {
|
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}
|
/** Returns false when this item won't be updated by
|
||||||
* does not contain this item.
|
* {@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#getBones()
|
||||||
* @see Skin#getConstraints() */
|
* @see Skin#getConstraints()
|
||||||
|
* @see BoneData#getSkinRequired()
|
||||||
|
* @see ConstraintData#getSkinRequired() */
|
||||||
isActive (): boolean;
|
isActive (): boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -179,6 +179,7 @@ export class Color {
|
|||||||
export class MathUtils {
|
export class MathUtils {
|
||||||
static PI = 3.1415927;
|
static PI = 3.1415927;
|
||||||
static PI2 = MathUtils.PI * 2;
|
static PI2 = MathUtils.PI * 2;
|
||||||
|
static invPI2 = 1 / MathUtils.PI2;
|
||||||
static radiansToDegrees = 180 / MathUtils.PI;
|
static radiansToDegrees = 180 / MathUtils.PI;
|
||||||
static radDeg = MathUtils.radiansToDegrees;
|
static radDeg = MathUtils.radiansToDegrees;
|
||||||
static degreesToRadians = MathUtils.PI / 180;
|
static degreesToRadians = MathUtils.PI / 180;
|
||||||
@ -198,6 +199,10 @@ export class MathUtils {
|
|||||||
return Math.sin(degrees * MathUtils.degRad);
|
return Math.sin(degrees * MathUtils.degRad);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static atan2Deg(y: number, x: number) {
|
||||||
|
return Math.atan2(y, x) * MathUtils.degRad;
|
||||||
|
}
|
||||||
|
|
||||||
static signum (value: number): number {
|
static signum (value: number): number {
|
||||||
return value > 0 ? 1 : value < 0 ? -1 : 0;
|
return value > 0 ? 1 : value < 0 ? -1 : 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user