[ts] Port latest physics changes.

This commit is contained in:
Mario Zechner 2024-03-19 09:57:13 +01:00
parent 86a320afc2
commit be767b21ea
8 changed files with 128 additions and 85 deletions

View File

@ -47,7 +47,7 @@ export class IkConstraintData extends ConstraintData {
}
/** Controls the bend direction of the IK bones, either 1 or -1. */
bendDirection = 1;
bendDirection = 0;
/** When true and only a single bone is being constrained, if the target is too close, the bone is scaled to reach it. */
compress = false;
@ -61,7 +61,7 @@ export class IkConstraintData extends ConstraintData {
uniform = false;
/** A percentage (0-1) that controls the mix between the constrained and unconstrained rotations. */
mix = 1;
mix = 0;
/** For two bone IK, the distance from the maximum reach of the bones that rotation will slow. */
softness = 0;

View File

@ -134,7 +134,8 @@ export class PhysicsConstraint implements Updatable {
this.reset();
// Fall through.
case Physics.update:
this.remaining += Math.max(this.skeleton.time - this.lastTime, 0);
const delta = Math.max(this.skeleton.time - this.lastTime, 0);
this.remaining += delta;
this.lastTime = this.skeleton.time;
const bx = bone.worldX, by = bone.worldY;
@ -143,41 +144,52 @@ export class PhysicsConstraint implements Updatable {
this.ux = bx;
this.uy = by;
} else {
let remaining = this.remaining, i = this.inertia, step = this.data.step;
let a = this.remaining, i = this.inertia, q = this.data.limit * delta, t = this.data.step, f = this.skeleton.data.referenceScale, d = -1;
if (x || y) {
if (x) {
this.xOffset += (this.ux - bx) * i;
const u = (this.ux - bx) * i;
this.xOffset += u > q ? q : u < -q ? -q : u;
this.ux = bx;
}
if (y) {
this.yOffset += (this.uy - by) * i;
const u = (this.uy - by) * i;
this.yOffset += u > q ? q : u < -q ? -q : u;
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);
if (a >= t) {
d = Math.pow(this.damping, 60 * t);
const m = this.massInverse * t, e = this.strength, w = this.wind * f, g = this.gravity * f;
do {
if (x) {
this.xVelocity += (w - this.xOffset * e) * m;
this.xOffset += this.xVelocity * step;
this.xOffset += this.xVelocity * t;
this.xVelocity *= d;
}
if (y) {
this.yVelocity += (g - this.yOffset * e) * m;
this.yOffset += this.yVelocity * step;
this.yVelocity -= (g + this.yOffset * e) * m;
this.yOffset += this.yVelocity * t;
this.yVelocity *= d;
}
remaining -= step;
} while (remaining >= step);
a -= t;
} while (a >= t);
}
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;
let dx = this.cx - bone.worldX, dy = this.cy - bone.worldY;
if (dx > q)
dx = q;
else if (dx < -q) //
dx = -q;
if (dy > q)
dy = q;
else if (dy < -q) //
dy = -q;
if (rotateOrShearX) {
mr = (this.data.rotate + this.data.shearX) * mix;
let dx = this.cx - bone.worldX, dy = this.cy - bone.worldY, r = Math.atan2(dy + this.ty, dx + this.tx) - ca - this.rotateOffset * mr;
let 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);
@ -190,33 +202,33 @@ export class PhysicsConstraint implements Updatable {
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;
if (r > 0) this.scaleOffset += (dx * c + dy * 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);
a = this.remaining;
if (a >= t) {
if (d == -1) d = Math.pow(this.damping, 60 * t);
const m = this.massInverse * t, e = this.strength, w = this.wind, g = this.gravity, h = l / f;
while (true) {
remaining -= step;
a -= t;
if (scaleX) {
this.scaleVelocity += (w * c - g * s - this.scaleOffset * e) * m;
this.scaleOffset += this.scaleVelocity * step;
this.scaleOffset += this.scaleVelocity * t;
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 -= ((w * s + g * c) * h + this.rotateOffset * e) * m;
this.rotateOffset += this.rotateVelocity * t;
this.rotateVelocity *= d;
if (remaining < step) break;
if (a < t) break;
const r = this.rotateOffset * mr + ca;
c = Math.cos(r);
s = Math.sin(r);
} else if (remaining < step) //
} else if (a < t) //
break;
}
}
}
this.remaining = remaining;
this.remaining = a;
}
this.cx = bone.worldX;
this.cy = bone.worldY;
@ -268,6 +280,8 @@ export class PhysicsConstraint implements Updatable {
bone.updateAppliedTransform();
}
/** Translates the physics constraint so next {@link #update(Physics)} forces are applied as if the bone moved an additional
* amount in world space. */
translate (x: number, y: number) {
this.ux -= x;
this.uy -= y;
@ -278,10 +292,7 @@ export class PhysicsConstraint implements Updatable {
/** Rotates the physics constraint so next {@link #update(Physics)} forces are applied as if the bone rotated around the
* specified point in world space. */
rotate (x: number, y: number, degrees: number) {
let r = degrees * MathUtils.degRad, cos = Math.cos(r), sin = Math.sin(r);
r = this.tx * cos - this.ty * sin;
this.ty = this.tx * sin + this.ty * cos;
this.tx = r;
const r = degrees * MathUtils.degRad, cos = Math.cos(r), sin = Math.sin(r);
const dx = this.cx - x, dy = this.cy - y;
this.translate(dx * cos - dy * sin - dx, dx * sin + dy * cos - dy);
}

View File

@ -48,6 +48,7 @@ export class PhysicsConstraintData extends ConstraintData {
rotate = 0;
scaleX = 0;
shearX = 0;
limit = 0;
step = 0;
inertia = 0;
strength = 0;

View File

@ -54,7 +54,7 @@ export class Skeleton {
/** The skeleton's bones, sorted parent first. The root bone is always the first bone. */
bones: Array<Bone>;
/** The skeleton's slots. */
/** The skeleton's slots in the setup pose draw order. */
slots: Array<Slot>;
/** The skeleton's slots in the order they should be drawn. The returned array may be modified to change the draw order. */

View File

@ -80,11 +80,11 @@ export class SkeletonBinary {
skeletonData.y = input.readFloat();
skeletonData.width = input.readFloat();
skeletonData.height = input.readFloat();
skeletonData.referenceScale = input.readFloat() * scale;
let nonessential = input.readBoolean();
if (nonessential) {
skeletonData.fps = input.readFloat();
skeletonData.imagesPath = input.readString();
skeletonData.audioPath = input.readString();
}
@ -128,6 +128,14 @@ export class SkeletonBinary {
for (let i = 0; i < n; i++) {
let slotName = input.readString();
if (!slotName) throw new Error("Slot name must not be null.");
let path: string | null = null;
if (nonessential) {
const slash = slotName!.lastIndexOf('/');
if (slash != -1) {
path = slotName.substring(0, slash);
slotName = slotName.substring(slash + 1);
}
}
let boneData = skeletonData.bones[input.readInt(true)];
let data = new SlotData(i, slotName, boneData);
Color.rgba8888ToColor(data.color, input.readInt32());
@ -137,7 +145,10 @@ export class SkeletonBinary {
data.attachmentName = input.readStringRef();
data.blendMode = input.readInt(true);
if (nonessential) data.visible = input.readBoolean();
if (nonessential) {
data.visible = input.readBoolean();
data.path = path;
}
skeletonData.slots.push(data);
}
@ -152,14 +163,14 @@ export class SkeletonBinary {
for (let ii = 0; ii < nn; ii++)
data.bones.push(skeletonData.bones[input.readInt(true)]);
data.target = skeletonData.bones[input.readInt(true)];
data.mix = input.readFloat();
data.softness = input.readFloat() * scale;
let flags = input.readByte();
data.skinRequired = (flags & 1) != 0;
data.bendDirection = (flags & 2) != 0 ? 1 : -1;
data.compress = (flags & 4) != 0;
data.stretch = (flags & 8) != 0;
data.uniform = (flags & 16) != 0;
if ((flags & 32) != 0) data.mix = (flags & 64) != 0 ? input.readFloat() : 1;
if ((flags & 128) != 0) data.softness = input.readFloat() * scale;
skeletonData.ikConstraints.push(data);
}
@ -174,22 +185,23 @@ export class SkeletonBinary {
for (let ii = 0; ii < nn; ii++)
data.bones.push(skeletonData.bones[input.readInt(true)]);
data.target = skeletonData.bones[input.readInt(true)];
const flags = input.readByte();
let flags = input.readByte();
data.skinRequired = (flags & 1) != 0;
data.local = (flags & 2) != 0;
data.relative = (flags & 4) != 0;
data.offsetRotation = input.readFloat();
data.offsetX = input.readFloat() * scale;
data.offsetY = input.readFloat() * scale;
data.offsetScaleX = input.readFloat();
data.offsetScaleY = input.readFloat();
data.offsetShearY = input.readFloat();
data.mixRotate = input.readFloat();
data.mixX = input.readFloat();
data.mixY = input.readFloat();
data.mixScaleX = input.readFloat();
data.mixScaleY = input.readFloat();
data.mixShearY = input.readFloat();
if ((flags & 8) != 0) data.offsetRotation = input.readFloat();
if ((flags & 16) != 0) data.offsetX = input.readFloat() * scale;
if ((flags & 32) != 0) data.offsetY = input.readFloat() * scale;
if ((flags & 64) != 0) data.offsetScaleX = input.readFloat();
if ((flags & 128) != 0) data.offsetScaleY = input.readFloat();
flags = input.readByte();
if ((flags & 1) != 0) data.offsetShearY = input.readFloat();
if ((flags & 2) != 0) data.mixRotate = input.readFloat();
if ((flags & 4) != 0) data.mixX = input.readFloat();
if ((flags & 8) != 0) data.mixY = input.readFloat();
if ((flags & 16) != 0) data.mixScaleX = input.readFloat();
if ((flags & 32) != 0) data.mixScaleY = input.readFloat();
if ((flags & 64) != 0) data.mixShearY = input.readFloat();
skeletonData.transformConstraints.push(data);
}
@ -205,10 +217,11 @@ export class SkeletonBinary {
for (let ii = 0; ii < nn; ii++)
data.bones.push(skeletonData.bones[input.readInt(true)]);
data.target = skeletonData.slots[input.readInt(true)];
data.positionMode = input.readInt(true);
data.spacingMode = input.readInt(true);
data.rotateMode = input.readInt(true);
data.offsetRotation = input.readFloat();
const flags = input.readByte();
data.positionMode = flags & 1;
data.spacingMode = (flags >> 1) & 3;
data.rotateMode = (flags >> 3) & 3;
if ((flags & 128) != 0) data.offsetRotation = input.readFloat();
data.position = input.readFloat();
if (data.positionMode == PositionMode.Fixed) data.position *= scale;
data.spacing = input.readFloat();
@ -234,14 +247,14 @@ export class SkeletonBinary {
if ((flags & 8) != 0) data.rotate = input.readFloat();
if ((flags & 16) != 0) data.scaleX = input.readFloat();
if ((flags & 32) != 0) data.shearX = input.readFloat();
data.limit = ((flags & 64) != 0 ? input.readFloat() : 5000) * scale;
data.step = 1 / input.readByte();
data.inertia = input.readFloat();
data.strength = input.readFloat();
data.damping = input.readFloat();
data.massInverse = input.readFloat();
data.wind = input.readFloat() * scale;
data.gravity = input.readFloat() * scale;
data.mix = input.readFloat();
data.massInverse = (flags & 128) != 0 ? input.readFloat() : 1;
data.wind = input.readFloat();
data.gravity = input.readFloat();
flags = input.readByte();
if ((flags & 1) != 0) data.inertiaGlobal = true;
if ((flags & 2) != 0) data.strengthGlobal = true;
@ -250,6 +263,7 @@ export class SkeletonBinary {
if ((flags & 16) != 0) data.windGlobal = true;
if ((flags & 32) != 0) data.gravityGlobal = true;
if ((flags & 64) != 0) data.mixGlobal = true;
data.mix = (flags & 128) != 0 ? input.readFloat() : 1;
skeletonData.physicsConstraints.push(data);
}
@ -365,7 +379,7 @@ export class SkeletonBinary {
let path = (flags & 16) != 0 ? input.readStringRef() : null;
const color = (flags & 32) != 0 ? input.readInt32() : 0xffffffff;
const sequence = (flags & 64) != 0 ? this.readSequence(input) : null;
let rotation = input.readFloat();
let rotation = (flags & 128) != 0 ? input.readFloat() : 0;
let x = input.readFloat();
let y = input.readFloat();
let scaleX = input.readFloat();
@ -827,17 +841,18 @@ export class SkeletonBinary {
for (let i = 0, n = input.readInt(true); i < n; i++) {
let index = input.readInt(true), frameCount = input.readInt(true), frameLast = frameCount - 1;
let timeline = new IkConstraintTimeline(frameCount, input.readInt(true), index);
let time = input.readFloat(), mix = input.readFloat(), softness = input.readFloat() * scale;
let flags = input.readByte();
let time = input.readFloat(), mix = (flags & 1) != 0 ? ((flags & 2) != 0 ? input.readFloat() : 1) : 0;
let softness = (flags & 4) != 0 ? input.readFloat() * scale : 0;
for (let frame = 0, bezier = 0; ; frame++) {
const flags = input.readByte();
timeline.setFrame(frame, time, mix, softness, input.readByte(), (flags & 1) != 0, (flags & 2) != 0);
timeline.setFrame(frame, time, mix, softness, (flags & 8) != 0 ? 1 : -1, (flags & 16) != 0, (flags & 32) != 0);
if (frame == frameLast) break;
let time2 = input.readFloat(), mix2 = input.readFloat(), softness2 = input.readFloat() * scale;
switch (input.readByte()) {
case CURVE_STEPPED:
flags = input.readByte();
const time2 = input.readFloat(), mix2 = (flags & 1) != 0 ? ((flags & 2) != 0 ? input.readFloat() : 1) : 0;
const softness2 = (flags & 4) != 0 ? input.readFloat() * scale : 0;
if ((flags & 64) != 0) {
timeline.setStepped(frame);
break;
case CURVE_BEZIER:
} else if ((flags & 128) != 0) {
setBezier(input, timeline, bezier++, frame, 0, time, time2, mix, mix2, 1);
setBezier(input, timeline, bezier++, frame, 1, time, time2, softness, softness2, scale);
}
@ -953,10 +968,10 @@ export class SkeletonBinary {
timelines.push(readTimeline1(input, new PhysicsConstraintMassTimeline(frameCount, bezierCount, index), 1));
break;
case PHYSICS_WIND:
timelines.push(readTimeline1(input, new PhysicsConstraintWindTimeline(frameCount, bezierCount, index), scale));
timelines.push(readTimeline1(input, new PhysicsConstraintWindTimeline(frameCount, bezierCount, index), 1));
break;
case PHYSICS_GRAVITY:
timelines.push(readTimeline1(input, new PhysicsConstraintGravityTimeline(frameCount, bezierCount, index), scale));
timelines.push(readTimeline1(input, new PhysicsConstraintGravityTimeline(frameCount, bezierCount, index), 1));
break;
case PHYSICS_MIX:
timelines.push(readTimeline1(input, new PhysicsConstraintMixTimeline(frameCount, bezierCount, index), 1));

View File

@ -49,8 +49,9 @@ export class SkeletonData {
/** The skeleton's bones, sorted parent first. The root bone is always the first bone. */
bones = new Array<BoneData>(); // Ordered parents first.
/** The skeleton's slots. */
/** The skeleton's slots in the setup pose draw order. */
slots = new Array<SlotData>(); // Setup pose draw order.
skins = new Array<Skin>();
/** The skeleton's default skin. By default this skin contains all attachments that were not in a skin in Spine.
@ -89,6 +90,10 @@ export class SkeletonData {
/** The height of the skeleton's axis aligned bounding box in the setup pose. */
height: number = 0;
/** Baseline scale factor for applying distance-dependent effects on non-scalable properties, such as angle or scale. Default
* is 100. */
referenceScale = 100;
/** The Spine version used to export the skeleton data, or null. */
version: string | null = null;

View File

@ -79,8 +79,10 @@ export class SkeletonJson {
skeletonData.y = skeletonMap.y;
skeletonData.width = skeletonMap.width;
skeletonData.height = skeletonMap.height;
skeletonData.referenceScale = getValue(skeletonMap, "referenceScale", 100) * scale;
skeletonData.fps = skeletonMap.fps;
skeletonData.imagesPath = skeletonMap.images;
skeletonData.imagesPath = skeletonMap.images ?? null;
skeletonData.audioPath = skeletonMap.audio ?? null;
}
// Bones
@ -114,9 +116,16 @@ export class SkeletonJson {
if (root.slots) {
for (let i = 0; i < root.slots.length; i++) {
let slotMap = root.slots[i];
let path: string | null = null;
let slotName = slotMap.name;
const slash = slotName.lastIndexOf('/');
if (slash != -1) {
path = slotName.substring(0, slash);
slotName = slotName.substring(slash + 1);
}
let boneData = skeletonData.findBone(slotMap.bone);
if (!boneData) throw new Error(`Couldn't find bone ${slotMap.bone} for slot ${slotMap.name}`);
let data = new SlotData(skeletonData.slots.length, slotMap.name, boneData);
if (!boneData) throw new Error(`Couldn't find bone ${slotMap.bone} for slot ${slotName}`);
let data = new SlotData(skeletonData.slots.length, slotName, boneData);
let color: string = getValue(slotMap, "color", null);
if (color) data.color.setFromString(color);
@ -126,6 +135,8 @@ export class SkeletonJson {
data.attachmentName = getValue(slotMap, "attachment", null);
data.blendMode = Utils.enumValue(BlendMode, getValue(slotMap, "blend", "normal"));
data.visible = getValue(slotMap, "visible", true);
data.path = path;
skeletonData.slots.push(data);
}
}
@ -253,13 +264,14 @@ export class SkeletonJson {
data.rotate = getValue(constraintMap, "rotate", 0);
data.scaleX = getValue(constraintMap, "scaleX", 0);
data.shearX = getValue(constraintMap, "shearX", 0);
data.limit = getValue(constraintMap, "limit", 5000) * scale;
data.step = 1 / getValue(constraintMap, "fps", 60);
data.inertia = getValue(constraintMap, "inertia", 1);
data.strength = getValue(constraintMap, "strength", 100);
data.damping = getValue(constraintMap, "damping", 1);
data.massInverse = 1 / getValue(constraintMap, "mass", 1);
data.wind = getValue(constraintMap, "wind", 0) * scale;
data.gravity = getValue(constraintMap, "gravity", 0) * scale;
data.wind = getValue(constraintMap, "wind", 0);
data.gravity = getValue(constraintMap, "gravity", 0);
data.mix = getValue(constraintMap, "mix", 1);
data.inertiaGlobal = getValue(constraintMap, "inertiaGlobal", false);
data.strengthGlobal = getValue(constraintMap, "strengthGlobal", false);
@ -911,7 +923,6 @@ export class SkeletonJson {
}
let timeline;
let timelineScale = 1;
if (timelineName == "inertia")
timeline = new PhysicsConstraintInertiaTimeline(frames, frames, constraintIndex);
else if (timelineName == "strength")
@ -920,19 +931,15 @@ export class SkeletonJson {
timeline = new PhysicsConstraintDampingTimeline(frames, frames, constraintIndex);
else if (timelineName == "mass")
timeline = new PhysicsConstraintMassTimeline(frames, frames, constraintIndex);
else if (timelineName == "wind") {
else if (timelineName == "wind")
timeline = new PhysicsConstraintWindTimeline(frames, frames, constraintIndex);
timelineScale = scale;
}
else if (timelineName == "gravity") {
else if (timelineName == "gravity")
timeline = new PhysicsConstraintGravityTimeline(frames, frames, constraintIndex);
timelineScale = scale;
}
else if (timelineName == "mix") //
timeline = new PhysicsConstraintMixTimeline(frames, frames, constraintIndex);
else
continue;
timelines.push(readTimeline1(timelineMap, timeline, 0, timelineScale));
timelines.push(readTimeline1(timelineMap, timeline, 0, 1));
}
}
}

View File

@ -58,6 +58,10 @@ export class SlotData {
/** False if the slot was hidden in Spine and nonessential data was exported. Does not affect runtime rendering. */
visible = true;
/** The folders for this slot in the draw order, delimited by <code>/</code>, or null if nonessential data was not exported. */
path: string | null = null;
constructor (index: number, name: string, boneData: BoneData) {
if (index < 0) throw new Error("index must be >= 0.");
if (!name) throw new Error("name cannot be null.");