[ts] Processed spine-core with biome.

This commit is contained in:
Davide Tantillo 2025-10-23 16:09:57 +02:00
parent 2dfcc8d045
commit 9ea694b16f
55 changed files with 1732 additions and 1627 deletions

View File

@ -6,9 +6,15 @@
"enabled": true, "enabled": true,
"rules": { "rules": {
"recommended": true, "recommended": true,
"complexity": {
"noUselessConstructor": "off"
},
"correctness": { "correctness": {
"noUnusedFunctionParameters": "off" "noUnusedFunctionParameters": "off"
}, },
"style": {
"useExponentiationOperator": "off"
},
"suspicious": { "suspicious": {
"noAssignInExpressions": "off" "noAssignInExpressions": "off"
} }

File diff suppressed because it is too large Load Diff

View File

@ -27,12 +27,14 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { Animation, MixBlend, AttachmentTimeline, MixDirection, RotateTimeline, DrawOrderTimeline, Timeline, EventTimeline } from "./Animation.js"; /** biome-ignore-all lint/style/noNonNullAssertion: reference runtime expects some nullable to not be null */
import { AnimationStateData } from "./AnimationStateData.js";
import { Skeleton } from "./Skeleton.js"; import { Animation, AttachmentTimeline, DrawOrderTimeline, EventTimeline, MixBlend, MixDirection, RotateTimeline, Timeline } from "./Animation.js";
import { Slot } from "./Slot.js"; import type { AnimationStateData } from "./AnimationStateData.js";
import { StringSet, Pool, Utils, MathUtils } from "./Utils.js"; import type { Event } from "./Event.js";
import { Event } from "./Event.js"; import type { Skeleton } from "./Skeleton.js";
import type { Slot } from "./Slot.js";
import { MathUtils, Pool, StringSet, Utils } from "./Utils.js";
/** Applies animations over time, queues animations for later playback, mixes (crossfading) between animations, and applies /** Applies animations over time, queues animations for later playback, mixes (crossfading) between animations, and applies
@ -46,7 +48,7 @@ export class AnimationState {
data: AnimationStateData; data: AnimationStateData;
/** The list of tracks that currently have animations, which may contain null entries. */ /** The list of tracks that currently have animations, which may contain null entries. */
readonly tracks = new Array<TrackEntry | null>(); readonly tracks = [] as (TrackEntry | null)[];
/** Multiplier for the delta time when the animation state is updated, causing time for all animations and mixes to play slower /** Multiplier for the delta time when the animation state is updated, causing time for all animations and mixes to play slower
* or faster. Defaults to 1. * or faster. Defaults to 1.
@ -55,8 +57,8 @@ export class AnimationState {
timeScale = 1; timeScale = 1;
unkeyedState = 0; unkeyedState = 0;
readonly events = new Array<Event>(); readonly events = [] as Event[];
readonly listeners = new Array<AnimationStateListener>(); readonly listeners = [] as AnimationStateListener[];
queue = new EventQueue(this); queue = new EventQueue(this);
propertyIDs = new StringSet(); propertyIDs = new StringSet();
animationsChanged = false; animationsChanged = false;
@ -70,9 +72,9 @@ export class AnimationState {
/** Increments each track entry {@link TrackEntry#trackTime()}, setting queued animations as current if needed. */ /** Increments each track entry {@link TrackEntry#trackTime()}, setting queued animations as current if needed. */
update (delta: number) { update (delta: number) {
delta *= this.timeScale; delta *= this.timeScale;
let tracks = this.tracks; const tracks = this.tracks;
for (let i = 0, n = tracks.length; i < n; i++) { for (let i = 0, n = tracks.length; i < n; i++) {
let current = tracks[i]; const current = tracks[i];
if (!current) continue; if (!current) continue;
current.animationLast = current.nextAnimationLast; current.animationLast = current.nextAnimationLast;
@ -90,10 +92,10 @@ export class AnimationState {
let next = current.next; let next = current.next;
if (next) { if (next) {
// When the next entry's delay is passed, change to the next entry, preserving leftover time. // When the next entry's delay is passed, change to the next entry, preserving leftover time.
let nextTime = current.trackLast - next.delay; const nextTime = current.trackLast - next.delay;
if (nextTime >= 0) { if (nextTime >= 0) {
next.delay = 0; next.delay = 0;
next.trackTime += current.timeScale == 0 ? 0 : (nextTime / current.timeScale + delta) * next.timeScale; next.trackTime += current.timeScale === 0 ? 0 : (nextTime / current.timeScale + delta) * next.timeScale;
current.trackTime += currentDelta; current.trackTime += currentDelta;
this.setCurrent(i, next, true); this.setCurrent(i, next, true);
while (next.mixingFrom) { while (next.mixingFrom) {
@ -127,18 +129,18 @@ export class AnimationState {
/** Returns true when all mixing from entries are complete. */ /** Returns true when all mixing from entries are complete. */
updateMixingFrom (to: TrackEntry, delta: number): boolean { updateMixingFrom (to: TrackEntry, delta: number): boolean {
let from = to.mixingFrom; const from = to.mixingFrom;
if (!from) return true; if (!from) return true;
let finished = this.updateMixingFrom(from, delta); const finished = this.updateMixingFrom(from, delta);
from.animationLast = from.nextAnimationLast; from.animationLast = from.nextAnimationLast;
from.trackLast = from.nextTrackLast; from.trackLast = from.nextTrackLast;
// The from entry was applied at least once and the mix is complete. // The from entry was applied at least once and the mix is complete.
if (to.nextTrackLast != -1 && to.mixTime >= to.mixDuration) { if (to.nextTrackLast !== -1 && to.mixTime >= to.mixDuration) {
// Mixing is complete for all entries before the from entry or the mix is instantaneous. // Mixing is complete for all entries before the from entry or the mix is instantaneous.
if (from.totalAlpha == 0 || to.mixDuration == 0) { if (from.totalAlpha === 0 || to.mixDuration === 0) {
to.mixingFrom = from.mixingFrom; to.mixingFrom = from.mixingFrom;
if (from.mixingFrom != null) from.mixingFrom.mixingTo = to; if (from.mixingFrom != null) from.mixingFrom.mixingTo = to;
to.interruptAlpha = from.interruptAlpha; to.interruptAlpha = from.interruptAlpha;
@ -159,15 +161,15 @@ export class AnimationState {
if (!skeleton) throw new Error("skeleton cannot be null."); if (!skeleton) throw new Error("skeleton cannot be null.");
if (this.animationsChanged) this._animationsChanged(); if (this.animationsChanged) this._animationsChanged();
let events = this.events; const events = this.events;
let tracks = this.tracks; const tracks = this.tracks;
let applied = false; let applied = false;
for (let i = 0, n = tracks.length; i < n; i++) { for (let i = 0, n = tracks.length; i < n; i++) {
let current = tracks[i]; const current = tracks[i];
if (!current || current.delay > 0) continue; if (!current || current.delay > 0) continue;
applied = true; applied = true;
let blend: MixBlend = i == 0 ? MixBlend.first : current.mixBlend; const blend: MixBlend = i === 0 ? MixBlend.first : current.mixBlend;
// Apply mixing from entries first. // Apply mixing from entries first.
let alpha = current.alpha; let alpha = current.alpha;
@ -185,10 +187,10 @@ export class AnimationState {
applyTime = current.animation!.duration - applyTime; applyTime = current.animation!.duration - applyTime;
applyEvents = null; applyEvents = null;
} }
let timelines = current.animation!.timelines; const timelines = current.animation!.timelines;
let timelineCount = timelines.length; const timelineCount = timelines.length;
if ((i == 0 && alpha == 1) || blend == MixBlend.add) { if ((i === 0 && alpha === 1) || blend === MixBlend.add) {
if (i == 0) attachments = true; 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.
@ -201,15 +203,15 @@ export class AnimationState {
timeline.apply(skeleton, animationLast, applyTime, applyEvents, alpha, blend, MixDirection.in, false); timeline.apply(skeleton, animationLast, applyTime, applyEvents, alpha, blend, MixDirection.in, false);
} }
} else { } else {
let timelineMode = current.timelineMode; const timelineMode = current.timelineMode;
let shortestRotation = current.shortestRotation; const shortestRotation = current.shortestRotation;
let firstFrame = !shortestRotation && current.timelinesRotation.length != timelineCount << 1; const firstFrame = !shortestRotation && current.timelinesRotation.length !== timelineCount << 1;
if (firstFrame) current.timelinesRotation.length = timelineCount << 1; if (firstFrame) current.timelinesRotation.length = timelineCount << 1;
for (let ii = 0; ii < timelineCount; ii++) { for (let ii = 0; ii < timelineCount; ii++) {
let timeline = timelines[ii]; const timeline = timelines[ii];
let timelineBlend = timelineMode[ii] == SUBSEQUENT ? blend : MixBlend.setup; const timelineBlend = timelineMode[ii] === SUBSEQUENT ? blend : MixBlend.setup;
if (!shortestRotation && timeline instanceof RotateTimeline) { if (!shortestRotation && timeline instanceof RotateTimeline) {
this.applyRotateTimeline(timeline, skeleton, applyTime, alpha, 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) {
@ -234,7 +236,7 @@ export class AnimationState {
const slots = skeleton.slots; const slots = skeleton.slots;
for (let i = 0, n = skeleton.slots.length; i < n; i++) { for (let i = 0, n = skeleton.slots.length; i < n; i++) {
const slot = slots[i]; const slot = slots[i];
if (slot.attachmentState == setupState) { if (slot.attachmentState === setupState) {
const attachmentName = slot.data.attachmentName; const attachmentName = slot.data.attachmentName;
slot.pose.setAttachment(!attachmentName ? null : skeleton.getAttachment(slot.data.index, attachmentName)); slot.pose.setAttachment(!attachmentName ? null : skeleton.getAttachment(slot.data.index, attachmentName));
} }
@ -246,23 +248,23 @@ export class AnimationState {
} }
applyMixingFrom (to: TrackEntry, skeleton: Skeleton, blend: MixBlend) { applyMixingFrom (to: TrackEntry, skeleton: Skeleton, blend: MixBlend) {
let from = to.mixingFrom!; const from = to.mixingFrom!;
if (from.mixingFrom) this.applyMixingFrom(from, skeleton, blend); if (from.mixingFrom) this.applyMixingFrom(from, skeleton, blend);
let mix = 0; let mix = 0;
if (to.mixDuration == 0) { // Single frame mix to undo mixingFrom changes. if (to.mixDuration === 0) { // Single frame mix to undo mixingFrom changes.
mix = 1; mix = 1;
if (blend == MixBlend.first) blend = MixBlend.setup; if (blend === MixBlend.first) blend = MixBlend.setup;
} else { } else {
mix = to.mixTime / to.mixDuration; mix = to.mixTime / to.mixDuration;
if (mix > 1) mix = 1; if (mix > 1) mix = 1;
if (blend != MixBlend.first) blend = from.mixBlend; if (blend !== MixBlend.first) blend = from.mixBlend;
} }
let attachments = mix < from.mixAttachmentThreshold, drawOrder = mix < from.mixDrawOrderThreshold; const attachments = mix < from.mixAttachmentThreshold, drawOrder = mix < from.mixDrawOrderThreshold;
let timelines = from.animation!.timelines; const timelines = from.animation!.timelines;
let timelineCount = timelines.length; const timelineCount = timelines.length;
let alphaHold = from.alpha * to.interruptAlpha, alphaMix = alphaHold * (1 - mix); const alphaHold = from.alpha * to.interruptAlpha, alphaMix = alphaHold * (1 - mix);
let animationLast = from.animationLast, animationTime = from.getAnimationTime(), applyTime = animationTime; let animationLast = from.animationLast, animationTime = from.getAnimationTime(), applyTime = animationTime;
let events = null; let events = null;
if (from.reverse) if (from.reverse)
@ -270,20 +272,20 @@ export class AnimationState {
else if (mix < from.eventThreshold) else if (mix < from.eventThreshold)
events = this.events; events = this.events;
if (blend == MixBlend.add) { if (blend === MixBlend.add) {
for (let i = 0; i < timelineCount; i++) for (let i = 0; i < timelineCount; i++)
timelines[i].apply(skeleton, animationLast, applyTime, events, alphaMix, blend, MixDirection.out, false); timelines[i].apply(skeleton, animationLast, applyTime, events, alphaMix, blend, MixDirection.out, false);
} else { } else {
let timelineMode = from.timelineMode; const timelineMode = from.timelineMode;
let timelineHoldMix = from.timelineHoldMix; const timelineHoldMix = from.timelineHoldMix;
let shortestRotation = from.shortestRotation; const shortestRotation = from.shortestRotation;
let firstFrame = !shortestRotation && from.timelinesRotation.length != timelineCount << 1; const firstFrame = !shortestRotation && from.timelinesRotation.length !== timelineCount << 1;
if (firstFrame) from.timelinesRotation.length = timelineCount << 1; if (firstFrame) from.timelinesRotation.length = timelineCount << 1;
from.totalAlpha = 0; from.totalAlpha = 0;
for (let i = 0; i < timelineCount; i++) { for (let i = 0; i < timelineCount; i++) {
let timeline = timelines[i]; const timeline = timelines[i];
let direction = MixDirection.out; let direction = MixDirection.out;
let timelineBlend: MixBlend; let timelineBlend: MixBlend;
let alpha = 0; let alpha = 0;
@ -305,11 +307,12 @@ export class AnimationState {
timelineBlend = MixBlend.setup; timelineBlend = MixBlend.setup;
alpha = alphaHold; alpha = alphaHold;
break; break;
default: // HOLD_MIX default: { // HOLD_MIX
timelineBlend = MixBlend.setup; timelineBlend = MixBlend.setup;
let holdMix = timelineHoldMix[i]; const holdMix = timelineHoldMix[i];
alpha = alphaHold * Math.max(0, 1 - holdMix.mixTime / holdMix.mixDuration); alpha = alphaHold * Math.max(0, 1 - holdMix.mixTime / holdMix.mixDuration);
break; break;
}
} }
from.totalAlpha += alpha; from.totalAlpha += alpha;
@ -320,7 +323,7 @@ export class AnimationState {
else { else {
// This fixes the WebKit 602 specific issue described at https://esotericsoftware.com/forum/d/10109-ios-10-disappearing-graphics // This fixes the WebKit 602 specific issue described at https://esotericsoftware.com/forum/d/10109-ios-10-disappearing-graphics
Utils.webkit602BugfixHelper(alpha, blend); Utils.webkit602BugfixHelper(alpha, blend);
if (drawOrder && timeline instanceof DrawOrderTimeline && timelineBlend == MixBlend.setup) if (drawOrder && timeline instanceof DrawOrderTimeline && timelineBlend === MixBlend.setup)
direction = MixDirection.in; direction = MixDirection.in;
timeline.apply(skeleton, animationLast, applyTime, events, alpha, timelineBlend, direction, false); timeline.apply(skeleton, animationLast, applyTime, events, alpha, timelineBlend, direction, false);
} }
@ -340,7 +343,7 @@ export class AnimationState {
if (!slot.bone.active) return; if (!slot.bone.active) return;
if (time < timeline.frames[0]) { // Time is before first frame. if (time < timeline.frames[0]) { // Time is before first frame.
if (blend == MixBlend.setup || blend == MixBlend.first) if (blend === MixBlend.setup || blend === MixBlend.first)
this.setAttachment(skeleton, slot, slot.data.attachmentName, attachments); this.setAttachment(skeleton, slot, slot.data.attachmentName, attachments);
} else } else
this.setAttachment(skeleton, slot, timeline.attachmentNames[Timeline.search(timeline.frames, time)], attachments); this.setAttachment(skeleton, slot, timeline.attachmentNames[Timeline.search(timeline.frames, time)], attachments);
@ -359,20 +362,22 @@ export class AnimationState {
if (firstFrame) timelinesRotation[i] = 0; if (firstFrame) timelinesRotation[i] = 0;
if (alpha == 1) { if (alpha === 1) {
timeline.apply(skeleton, 0, time, null, 1, blend, MixDirection.in, false); timeline.apply(skeleton, 0, time, null, 1, blend, MixDirection.in, false);
return; return;
} }
let bone = skeleton.bones[timeline.boneIndex]; const bone = skeleton.bones[timeline.boneIndex];
if (!bone.active) return; if (!bone.active) return;
const pose = bone.pose, setup = bone.data.setup; const pose = bone.pose, setup = bone.data.setup;
let frames = timeline.frames; const frames = timeline.frames;
let r1 = 0, r2 = 0; let r1 = 0, r2 = 0;
if (time < frames[0]) { if (time < frames[0]) {
switch (blend) { switch (blend) {
// biome-ignore lint/suspicious/noFallthroughSwitchClause: reference runtime does fall through
case MixBlend.setup: case MixBlend.setup:
pose.rotation = setup.rotation; pose.rotation = setup.rotation;
// biome-ignore lint/suspicious/useDefaultSwitchClauseLast: needed for fall through
default: default:
return; return;
case MixBlend.first: case MixBlend.first:
@ -380,14 +385,14 @@ export class AnimationState {
r2 = setup.rotation; r2 = setup.rotation;
} }
} else { } else {
r1 = blend == MixBlend.setup ? setup.rotation : pose.rotation; r1 = blend === MixBlend.setup ? setup.rotation : pose.rotation;
r2 = setup.rotation + timeline.getCurveValue(time); r2 = setup.rotation + timeline.getCurveValue(time);
} }
// 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 -= Math.ceil(diff / 360 - 0.5) * 360; diff -= Math.ceil(diff / 360 - 0.5) * 360;
if (diff == 0) { if (diff === 0) {
total = timelinesRotation[i]; total = timelinesRotation[i];
} else { } else {
let lastTotal = 0, lastDiff = 0; let lastTotal = 0, lastDiff = 0;
@ -398,19 +403,19 @@ export class AnimationState {
lastTotal = timelinesRotation[i]; lastTotal = timelinesRotation[i];
lastDiff = timelinesRotation[i + 1]; lastDiff = timelinesRotation[i + 1];
} }
let loops = lastTotal - lastTotal % 360; const loops = lastTotal - lastTotal % 360;
total = diff + loops; total = diff + loops;
let current = diff >= 0, dir = lastTotal >= 0; let current = diff >= 0, dir = lastTotal >= 0;
if (Math.abs(lastDiff) <= 90 && MathUtils.signum(lastDiff) != MathUtils.signum(diff)) { if (Math.abs(lastDiff) <= 90 && MathUtils.signum(lastDiff) !== MathUtils.signum(diff)) {
if (Math.abs(lastTotal - loops) > 180) { if (Math.abs(lastTotal - loops) > 180) {
total += 360 * MathUtils.signum(lastTotal); total += 360 * MathUtils.signum(lastTotal);
dir = current; dir = current;
} else if (loops != 0) } else if (loops !== 0)
total -= 360 * MathUtils.signum(lastTotal); total -= 360 * MathUtils.signum(lastTotal);
else else
dir = current; dir = current;
} }
if (dir != current) total += 360 * MathUtils.signum(lastTotal); if (dir !== current) total += 360 * MathUtils.signum(lastTotal);
timelinesRotation[i] = total; timelinesRotation[i] = total;
} }
timelinesRotation[i + 1] = diff; timelinesRotation[i + 1] = diff;
@ -418,15 +423,15 @@ export class AnimationState {
} }
queueEvents (entry: TrackEntry, animationTime: number) { queueEvents (entry: TrackEntry, animationTime: number) {
let animationStart = entry.animationStart, animationEnd = entry.animationEnd; const animationStart = entry.animationStart, animationEnd = entry.animationEnd;
let duration = animationEnd - animationStart; const duration = animationEnd - animationStart;
let trackLastWrapped = entry.trackLast % duration; const trackLastWrapped = entry.trackLast % duration;
// Queue events before complete. // Queue events before complete.
let events = this.events; const events = this.events;
let i = 0, n = events.length; let i = 0, n = events.length;
for (; i < n; i++) { for (; i < n; i++) {
let event = events[i]; const event = events[i];
if (event.time < trackLastWrapped) break; if (event.time < trackLastWrapped) break;
if (event.time > animationEnd) continue; // Discard events outside animation start/end. if (event.time > animationEnd) continue; // Discard events outside animation start/end.
this.queue.event(entry, event); this.queue.event(entry, event);
@ -435,7 +440,7 @@ export class AnimationState {
// Queue complete if completed a loop iteration or the animation. // Queue complete if completed a loop iteration or the animation.
let complete = false; let complete = false;
if (entry.loop) { if (entry.loop) {
if (duration == 0) if (duration === 0)
complete = true; complete = true;
else { else {
const cycles = Math.floor(entry.trackTime / duration); const cycles = Math.floor(entry.trackTime / duration);
@ -447,7 +452,7 @@ export class AnimationState {
// Queue events after complete. // Queue events after complete.
for (; i < n; i++) { for (; i < n; i++) {
let event = events[i]; const event = events[i];
if (event.time < animationStart) continue; // Discard events outside animation start/end. if (event.time < animationStart) continue; // Discard events outside animation start/end.
this.queue.event(entry, event); this.queue.event(entry, event);
} }
@ -458,7 +463,7 @@ export class AnimationState {
* It may be desired to use {@link AnimationState#setEmptyAnimation()} to mix the skeletons back to the setup pose, * It may be desired to use {@link AnimationState#setEmptyAnimation()} to mix the skeletons back to the setup pose,
* rather than leaving them in their current pose. */ * rather than leaving them in their current pose. */
clearTracks () { clearTracks () {
let oldDrainDisabled = this.queue.drainDisabled; const oldDrainDisabled = this.queue.drainDisabled;
this.queue.drainDisabled = true; this.queue.drainDisabled = true;
for (let i = 0, n = this.tracks.length; i < n; i++) for (let i = 0, n = this.tracks.length; i < n; i++)
this.clearTrack(i); this.clearTrack(i);
@ -473,7 +478,7 @@ export class AnimationState {
* rather than leaving them in their current pose. */ * rather than leaving them in their current pose. */
clearTrack (trackIndex: number) { clearTrack (trackIndex: number) {
if (trackIndex >= this.tracks.length) return; if (trackIndex >= this.tracks.length) return;
let current = this.tracks[trackIndex]; const current = this.tracks[trackIndex];
if (!current) return; if (!current) return;
this.queue.end(current); this.queue.end(current);
@ -482,7 +487,7 @@ export class AnimationState {
let entry = current; let entry = current;
while (true) { while (true) {
let from = entry.mixingFrom; const from = entry.mixingFrom;
if (!from) break; if (!from) break;
this.queue.end(from); this.queue.end(from);
entry.mixingFrom = null; entry.mixingFrom = null;
@ -496,7 +501,7 @@ export class AnimationState {
} }
setCurrent (index: number, current: TrackEntry, interrupt: boolean) { setCurrent (index: number, current: TrackEntry, interrupt: boolean) {
let from = this.expandToIndex(index); const from = this.expandToIndex(index);
this.tracks[index] = current; this.tracks[index] = current;
current.previous = null; current.previous = null;
@ -538,8 +543,8 @@ export class AnimationState {
} }
private setAnimation1 (trackIndex: number, animationName: string, loop: boolean = false) { private setAnimation1 (trackIndex: number, animationName: string, loop: boolean = false) {
let animation = this.data.skeletonData.findAnimation(animationName); const animation = this.data.skeletonData.findAnimation(animationName);
if (!animation) throw new Error("Animation not found: " + animationName); if (!animation) throw new Error(`Animation not found: ${animationName}`);
return this.setAnimation2(trackIndex, animation, loop); return this.setAnimation2(trackIndex, animation, loop);
} }
@ -568,7 +573,7 @@ export class AnimationState {
} else } else
this.clearNext(current); this.clearNext(current);
} }
let entry = this.trackEntry(trackIndex, animation, loop, current); const entry = this.trackEntry(trackIndex, animation, loop, current);
this.setCurrent(trackIndex, entry, interrupt); this.setCurrent(trackIndex, entry, interrupt);
this.queue.drain(); this.queue.drain();
return entry; return entry;
@ -596,8 +601,8 @@ export class AnimationState {
} }
private addAnimation1 (trackIndex: number, animationName: string, loop: boolean = false, delay: number = 0) { private addAnimation1 (trackIndex: number, animationName: string, loop: boolean = false, delay: number = 0) {
let animation = this.data.skeletonData.findAnimation(animationName); const animation = this.data.skeletonData.findAnimation(animationName);
if (!animation) throw new Error("Animation not found: " + animationName); if (!animation) throw new Error(`Animation not found: ${animationName}`);
return this.addAnimation2(trackIndex, animation, loop, delay); return this.addAnimation2(trackIndex, animation, loop, delay);
} }
@ -610,7 +615,7 @@ export class AnimationState {
last = last.next; last = last.next;
} }
let entry = this.trackEntry(trackIndex, animation, loop, last); const entry = this.trackEntry(trackIndex, animation, loop, last);
if (!last) { if (!last) {
this.setCurrent(trackIndex, entry, true); this.setCurrent(trackIndex, entry, true);
@ -644,7 +649,7 @@ export class AnimationState {
* See <a href='https://esotericsoftware.com/spine-applying-animations/#Empty-animations'>Empty animations</a> in the Spine * See <a href='https://esotericsoftware.com/spine-applying-animations/#Empty-animations'>Empty animations</a> in the Spine
* Runtimes Guide. */ * Runtimes Guide. */
setEmptyAnimation (trackIndex: number, mixDuration: number = 0) { setEmptyAnimation (trackIndex: number, mixDuration: number = 0) {
let entry = this.setAnimation(trackIndex, AnimationState.emptyAnimation, false); const entry = this.setAnimation(trackIndex, AnimationState.emptyAnimation, false);
entry.mixDuration = mixDuration; entry.mixDuration = mixDuration;
entry.trackEnd = mixDuration; entry.trackEnd = mixDuration;
return entry; return entry;
@ -664,7 +669,7 @@ export class AnimationState {
* @return A track entry to allow further customization of animation playback. References to the track entry must not be kept * @return A track entry to allow further customization of animation playback. References to the track entry must not be kept
* after the {@link AnimationStateListener#dispose(TrackEntry)} event occurs. */ * after the {@link AnimationStateListener#dispose(TrackEntry)} event occurs. */
addEmptyAnimation (trackIndex: number, mixDuration: number = 0, delay: number = 0) { addEmptyAnimation (trackIndex: number, mixDuration: number = 0, delay: number = 0) {
let entry = this.addAnimation(trackIndex, AnimationState.emptyAnimation, false, delay); const entry = this.addAnimation(trackIndex, AnimationState.emptyAnimation, false, delay);
if (delay <= 0) entry.delay = Math.max(entry.delay + entry.mixDuration - mixDuration, 0); if (delay <= 0) entry.delay = Math.max(entry.delay + entry.mixDuration - mixDuration, 0);
entry.mixDuration = mixDuration; entry.mixDuration = mixDuration;
entry.trackEnd = mixDuration; entry.trackEnd = mixDuration;
@ -676,10 +681,10 @@ export class AnimationState {
* See <a href='https://esotericsoftware.com/spine-applying-animations/#Empty-animations'>Empty animations</a> in the Spine * See <a href='https://esotericsoftware.com/spine-applying-animations/#Empty-animations'>Empty animations</a> in the Spine
* Runtimes Guide. */ * Runtimes Guide. */
setEmptyAnimations (mixDuration: number = 0) { setEmptyAnimations (mixDuration: number = 0) {
let oldDrainDisabled = this.queue.drainDisabled; const oldDrainDisabled = this.queue.drainDisabled;
this.queue.drainDisabled = true; this.queue.drainDisabled = true;
for (let i = 0, n = this.tracks.length; i < n; i++) { for (let i = 0, n = this.tracks.length; i < n; i++) {
let current = this.tracks[i]; const current = this.tracks[i];
if (current) this.setEmptyAnimation(current.trackIndex, mixDuration); if (current) this.setEmptyAnimation(current.trackIndex, mixDuration);
} }
this.queue.drainDisabled = oldDrainDisabled; this.queue.drainDisabled = oldDrainDisabled;
@ -695,7 +700,7 @@ export class AnimationState {
/** @param last May be null. */ /** @param last May be null. */
trackEntry (trackIndex: number, animation: Animation, loop: boolean, last: TrackEntry | null) { trackEntry (trackIndex: number, animation: Animation, loop: boolean, last: TrackEntry | null) {
let entry = this.trackEntryPool.obtain(); const entry = this.trackEntryPool.obtain();
entry.reset(); entry.reset();
entry.trackIndex = trackIndex; entry.trackIndex = trackIndex;
entry.animation = animation; entry.animation = animation;
@ -745,30 +750,30 @@ export class AnimationState {
this.animationsChanged = false; this.animationsChanged = false;
this.propertyIDs.clear(); this.propertyIDs.clear();
let tracks = this.tracks; const tracks = this.tracks;
for (let i = 0, n = tracks.length; i < n; i++) { for (let i = 0, n = tracks.length; i < n; i++) {
let entry = tracks[i]; let entry = tracks[i];
if (!entry) continue; if (!entry) continue;
while (entry.mixingFrom) while (entry.mixingFrom)
entry = entry.mixingFrom; entry = entry.mixingFrom;
do { do {
if (!entry.mixingTo || entry.mixBlend != MixBlend.add) this.computeHold(entry); if (!entry.mixingTo || entry.mixBlend !== MixBlend.add) this.computeHold(entry);
entry = entry.mixingTo; entry = entry.mixingTo;
} while (entry); } while (entry);
} }
} }
computeHold (entry: TrackEntry) { computeHold (entry: TrackEntry) {
let to = entry.mixingTo; const to = entry.mixingTo;
let timelines = entry.animation!.timelines; const timelines = entry.animation!.timelines;
let timelinesCount = entry.animation!.timelines.length; const timelinesCount = entry.animation!.timelines.length;
let timelineMode = entry.timelineMode; const timelineMode = entry.timelineMode;
timelineMode.length = timelinesCount; timelineMode.length = timelinesCount;
let timelineHoldMix = entry.timelineHoldMix; const timelineHoldMix = entry.timelineHoldMix;
timelineHoldMix.length = 0; timelineHoldMix.length = 0;
let propertyIDs = this.propertyIDs; const propertyIDs = this.propertyIDs;
if (to && to.holdPrevious) { if (to?.holdPrevious) {
for (let i = 0; i < timelinesCount; i++) for (let i = 0; i < timelinesCount; i++)
timelineMode[i] = propertyIDs.addAll(timelines[i].getPropertyIds()) ? HOLD_FIRST : HOLD_SUBSEQUENT; timelineMode[i] = propertyIDs.addAll(timelines[i].getPropertyIds()) ? HOLD_FIRST : HOLD_SUBSEQUENT;
return; return;
@ -776,8 +781,8 @@ export class AnimationState {
outer: outer:
for (let i = 0; i < timelinesCount; i++) { for (let i = 0; i < timelinesCount; i++) {
let timeline = timelines[i]; const timeline = timelines[i];
let ids = timeline.getPropertyIds(); const ids = timeline.getPropertyIds();
if (!propertyIDs.addAll(ids)) if (!propertyIDs.addAll(ids))
timelineMode[i] = SUBSEQUENT; timelineMode[i] = SUBSEQUENT;
else if (!to || timeline instanceof AttachmentTimeline || timeline instanceof DrawOrderTimeline else if (!to || timeline instanceof AttachmentTimeline || timeline instanceof DrawOrderTimeline
@ -812,7 +817,7 @@ export class AnimationState {
/** Removes the listener added with {@link #addListener()}. */ /** Removes the listener added with {@link #addListener()}. */
removeListener (listener: AnimationStateListener) { removeListener (listener: AnimationStateListener) {
let index = this.listeners.indexOf(listener); const index = this.listeners.indexOf(listener);
if (index >= 0) this.listeners.splice(index, 1); if (index >= 0) this.listeners.splice(index, 1);
} }
@ -1009,9 +1014,9 @@ export class TrackEntry {
* The `mixBlend` can be set for a new track entry only before {@link AnimationState#apply()} is next * The `mixBlend` can be set for a new track entry only before {@link AnimationState#apply()} is next
* called. */ * called. */
mixBlend = MixBlend.replace; mixBlend = MixBlend.replace;
timelineMode = new Array<number>(); timelineMode = [] as number[];
timelineHoldMix = new Array<TrackEntry>(); timelineHoldMix = [] as TrackEntry[];
timelinesRotation = new Array<number>(); timelinesRotation = [] as number[];
reset () { reset () {
this.next = null; this.next = null;
@ -1030,8 +1035,8 @@ export class TrackEntry {
* `animationStart` time. */ * `animationStart` time. */
getAnimationTime () { getAnimationTime () {
if (this.loop) { if (this.loop) {
let duration = this.animationEnd - this.animationStart; const duration = this.animationEnd - this.animationStart;
if (duration == 0) return this.animationStart; if (duration === 0) return this.animationStart;
return (this.trackTime % duration) + this.animationStart; return (this.trackTime % duration) + this.animationStart;
} }
return Math.min(this.trackTime + this.animationStart, this.animationEnd); return Math.min(this.trackTime + this.animationStart, this.animationEnd);
@ -1061,8 +1066,8 @@ export class TrackEntry {
} }
getTrackComplete () { getTrackComplete () {
let duration = this.animationEnd - this.animationStart; const duration = this.animationEnd - this.animationStart;
if (duration != 0) { if (duration !== 0) {
if (this.loop) return duration * (1 + ((this.trackTime / duration) | 0)); // Completion of next loop. if (this.loop) return duration * (1 + ((this.trackTime / duration) | 0)); // Completion of next loop.
if (this.trackTime < duration) return duration; // Before duration. if (this.trackTime < duration) return duration; // Before duration.
} }
@ -1073,7 +1078,7 @@ export class TrackEntry {
* <p> * <p>
* See {@link AnimationState#apply(Skeleton)}. */ * See {@link AnimationState#apply(Skeleton)}. */
wasApplied () { wasApplied () {
return this.nextTrackLast != -1; return this.nextTrackLast !== -1;
} }
/** Returns true if there is a {@link #getNext()} track entry and it will become the current track entry during the next /** Returns true if there is a {@link #getNext()} track entry and it will become the current track entry during the next
@ -1084,7 +1089,7 @@ export class TrackEntry {
} }
export class EventQueue { export class EventQueue {
objects: Array<any> = []; objects: Array<EventType | TrackEntry | Event> = [];
drainDisabled = false; drainDisabled = false;
animState: AnimationState; animState: AnimationState;
@ -1137,28 +1142,29 @@ export class EventQueue {
const entry = objects[i + 1] as TrackEntry; const entry = objects[i + 1] as TrackEntry;
switch (type) { switch (type) {
case EventType.start: case EventType.start:
if (entry.listener && entry.listener.start) entry.listener.start(entry); if (entry.listener?.start) entry.listener.start(entry);
for (let ii = 0; ii < listeners.length; ii++) { for (let ii = 0; ii < listeners.length; ii++) {
const listener = listeners[ii]; const listener = listeners[ii];
if (listener.start) listener.start(entry); if (listener.start) listener.start(entry);
} }
break; break;
case EventType.interrupt: case EventType.interrupt:
if (entry.listener && entry.listener.interrupt) entry.listener.interrupt(entry); if (entry.listener?.interrupt) entry.listener.interrupt(entry);
for (let ii = 0; ii < listeners.length; ii++) { for (let ii = 0; ii < listeners.length; ii++) {
const listener = listeners[ii]; const listener = listeners[ii];
if (listener.interrupt) listener.interrupt(entry); if (listener.interrupt) listener.interrupt(entry);
} }
break; break;
// biome-ignore lint/suspicious/noFallthroughSwitchClause: reference runtime does fall through
case EventType.end: case EventType.end:
if (entry.listener && entry.listener.end) entry.listener.end(entry); if (entry.listener?.end) entry.listener.end(entry);
for (let ii = 0; ii < listeners.length; ii++) { for (let ii = 0; ii < listeners.length; ii++) {
const listener = listeners[ii]; const listener = listeners[ii];
if (listener.end) listener.end(entry); if (listener.end) listener.end(entry);
} }
// Fall through. // Fall through.
case EventType.dispose: case EventType.dispose:
if (entry.listener && entry.listener.dispose) entry.listener.dispose(entry); if (entry.listener?.dispose) entry.listener.dispose(entry);
for (let ii = 0; ii < listeners.length; ii++) { for (let ii = 0; ii < listeners.length; ii++) {
const listener = listeners[ii]; const listener = listeners[ii];
if (listener.dispose) listener.dispose(entry); if (listener.dispose) listener.dispose(entry);
@ -1166,20 +1172,21 @@ export class EventQueue {
this.animState.trackEntryPool.free(entry); this.animState.trackEntryPool.free(entry);
break; break;
case EventType.complete: case EventType.complete:
if (entry.listener && entry.listener.complete) entry.listener.complete(entry); if (entry.listener?.complete) entry.listener.complete(entry);
for (let ii = 0; ii < listeners.length; ii++) { for (let ii = 0; ii < listeners.length; ii++) {
const listener = listeners[ii]; const listener = listeners[ii];
if (listener.complete) listener.complete(entry); if (listener.complete) listener.complete(entry);
} }
break; break;
case EventType.event: case EventType.event: {
const event = objects[i++ + 2] as Event; const event = objects[i++ + 2] as Event;
if (entry.listener && entry.listener.event) entry.listener.event(entry, event); if (entry.listener?.event) entry.listener.event(entry, event);
for (let ii = 0; ii < listeners.length; ii++) { for (let ii = 0; ii < listeners.length; ii++) {
const listener = listeners[ii]; const listener = listeners[ii];
if (listener.event) listener.event(entry, event); if (listener.event) listener.event(entry, event);
} }
break; break;
}
} }
} }
this.clear(); this.clear();

View File

@ -27,9 +27,9 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { Animation } from "./Animation.js"; import type { Animation } from "./Animation.js";
import { SkeletonData } from "./SkeletonData.js"; import type { SkeletonData } from "./SkeletonData.js";
import { StringMap } from "./Utils.js"; import type { StringMap } from "./Utils.js";
/** Stores mix (crossfade) durations to be applied when {@link AnimationState} animations are changed. */ /** Stores mix (crossfade) durations to be applied when {@link AnimationState} animations are changed. */
@ -50,12 +50,12 @@ export class AnimationStateData {
/** Sets a mix duration by animation name. /** Sets a mix duration by animation name.
* *
* See {@link #setMix()}. */ * See {@link #setMix()}. */
setMix (fromName: string, to: string, duration: number): any; setMix (fromName: string, to: string, duration: number): void;
/** Sets the mix duration when changing from the specified animation to the other. /** Sets the mix duration when changing from the specified animation to the other.
* *
* See {@link TrackEntry#mixDuration}. */ * See {@link TrackEntry#mixDuration}. */
setMix (from: Animation, to: Animation, duration: number): any; setMix (from: Animation, to: Animation, duration: number): void;
setMix (from: string | Animation, to: string | Animation, duration: number) { setMix (from: string | Animation, to: string | Animation, duration: number) {
if (typeof from === "string") if (typeof from === "string")
@ -64,25 +64,25 @@ export class AnimationStateData {
} }
private setMix1 (fromName: string, toName: string, duration: number) { private setMix1 (fromName: string, toName: string, duration: number) {
let from = this.skeletonData.findAnimation(fromName); const from = this.skeletonData.findAnimation(fromName);
if (!from) throw new Error("Animation not found: " + fromName); if (!from) throw new Error(`Animation not found: ${fromName}`);
let to = this.skeletonData.findAnimation(toName); const to = this.skeletonData.findAnimation(toName);
if (!to) throw new Error("Animation not found: " + toName); if (!to) throw new Error(`Animation not found: ${toName}`);
this.setMix2(from, to, duration); this.setMix2(from, to, duration);
} }
private setMix2 (from: Animation, to: Animation, duration: number) { private setMix2 (from: Animation, to: Animation, duration: number) {
if (!from) throw new Error("from cannot be null."); if (!from) throw new Error("from cannot be null.");
if (!to) throw new Error("to cannot be null."); if (!to) throw new Error("to cannot be null.");
let key = from.name + "." + to.name; const key = `${from.name}.${to.name}`;
this.animationToMixTime[key] = duration; this.animationToMixTime[key] = duration;
} }
/** Returns the mix duration to use when changing from the specified animation to the other, or the {@link #defaultMix} if /** Returns the mix duration to use when changing from the specified animation to the other, or the {@link #defaultMix} if
* no mix duration has been set. */ * no mix duration has been set. */
getMix (from: Animation, to: Animation) { getMix (from: Animation, to: Animation) {
let key = from.name + "." + to.name; const key = `${from.name}.${to.name}`;
let value = this.animationToMixTime[key]; const value = this.animationToMixTime[key];
return value === undefined ? this.defaultMix : value; return value === undefined ? this.defaultMix : value;
} }
} }

View File

@ -27,9 +27,13 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { Texture } from "./Texture.js"; import type { Texture } from "./Texture.js";
import { TextureAtlas } from "./TextureAtlas.js"; import { TextureAtlas } from "./TextureAtlas.js";
import { Disposable, StringMap } from "./Utils.js"; import type { Disposable, StringMap } from "./Utils.js";
type AssetData = (Uint8Array | string | Texture | TextureAtlas | object) & Partial<Disposable>;
type AssetCallback<T extends AssetData> = (path: string, data: T) => void;
type ErrorCallback = (path: string, message: string) => void;
export class AssetManagerBase implements Disposable { export class AssetManagerBase implements Disposable {
private pathPrefix: string = ""; private pathPrefix: string = "";
@ -52,7 +56,7 @@ export class AssetManagerBase implements Disposable {
return this.pathPrefix + path; return this.pathPrefix + path;
} }
private success (callback: (path: string, data: any) => void, path: string, asset: any) { private success<T extends AssetData> (callback: AssetCallback<T>, path: string, asset: T) {
this.toLoad--; this.toLoad--;
this.loaded++; this.loaded++;
this.cache.assets[path] = asset; this.cache.assets[path] = asset;
@ -68,8 +72,8 @@ export class AssetManagerBase implements Disposable {
} }
loadAll () { loadAll () {
let promise = new Promise((resolve: (assetManager: AssetManagerBase) => void, reject: (errors: StringMap<string>) => void) => { const promise = new Promise((resolve: (assetManager: AssetManagerBase) => void, reject: (errors: StringMap<string>) => void) => {
let check = () => { const check = () => {
if (this.isLoadingComplete()) { if (this.isLoadingComplete()) {
if (this.hasErrors()) reject(this.errors); if (this.hasErrors()) reject(this.errors);
else resolve(this); else resolve(this);
@ -93,7 +97,7 @@ export class AssetManagerBase implements Disposable {
if (this.reuseAssets(path, success, error)) return; if (this.reuseAssets(path, success, error)) return;
this.cache.assetsLoaded[path] = new Promise<any>((resolve, reject) => { this.cache.assetsLoaded[path] = new Promise<Uint8Array>((resolve, reject) => {
this.downloader.downloadBinary(path, (data: Uint8Array): void => { this.downloader.downloadBinary(path, (data: Uint8Array): void => {
this.success(success, path, data); this.success(success, path, data);
resolve(data); resolve(data);
@ -124,7 +128,7 @@ export class AssetManagerBase implements Disposable {
if (this.reuseAssets(path, success, error)) return; if (this.reuseAssets(path, success, error)) return;
this.cache.assetsLoaded[path] = new Promise<any>((resolve, reject) => { this.cache.assetsLoaded[path] = new Promise<object>((resolve, reject) => {
this.downloader.downloadJson(path, (data: object): void => { this.downloader.downloadJson(path, (data: object): void => {
this.success(success, path, data); this.success(success, path, data);
resolve(data); resolve(data);
@ -136,36 +140,44 @@ export class AssetManagerBase implements Disposable {
}); });
} }
reuseAssets (path: string,
success: (path: string, data: any) => void = () => { }, reuseAssets<T extends AssetData> (
error: (path: string, message: string) => void = () => { }) { path: string,
const loadedStatus = this.cache.assetsLoaded[path]; success: AssetCallback<T> = () => { },
error: ErrorCallback = () => { }
) {
const loadedStatus = this.cache.getAsset(path);
const alreadyExistsOrLoading = loadedStatus !== undefined; const alreadyExistsOrLoading = loadedStatus !== undefined;
if (alreadyExistsOrLoading) { if (alreadyExistsOrLoading) {
this.cache.assetsLoaded[path] = loadedStatus this.cache.assetsLoaded[path] = loadedStatus
.then(data => { .then(data => {
// necessary when user preloads an image into the cache. // necessary when user preloads an image into the cache.
// texture loader is not avaiable in the cache, so we transform in GLTexture at first use // texture loader is not avaiable in the cache, so we transform in GLTexture at first use
data = (data instanceof Image || data instanceof ImageBitmap) ? this.textureLoader(data) : data; data = (data instanceof Image || data instanceof ImageBitmap) ? this.textureLoader(data) as T : data;
this.success(success, path, data); this.success(success, path, data as T);
return data; return data;
}) })
.catch(errorMsg => this.error(error, path, errorMsg)); .catch(errorMsg => {
this.error(error, path, errorMsg);
return undefined;
});
} }
return alreadyExistsOrLoading; return alreadyExistsOrLoading;
} }
loadTexture (path: string, loadTexture (
success: (path: string, texture: Texture) => void = () => { }, path: string,
error: (path: string, message: string) => void = () => { }) { success: AssetCallback<Texture> = () => { },
error: ErrorCallback = () => { }
) {
path = this.start(path); path = this.start(path);
if (this.reuseAssets(path, success, error)) return; if (this.reuseAssets(path, success, error)) return;
this.cache.assetsLoaded[path] = new Promise<any>((resolve, reject) => { this.cache.assetsLoaded[path] = new Promise<Texture>((resolve, reject) => {
let isBrowser = !!(typeof window !== 'undefined' && typeof navigator !== 'undefined' && window.document); const isBrowser = !!(typeof window !== 'undefined' && typeof navigator !== 'undefined' && window.document);
let isWebWorker = !isBrowser; // && typeof importScripts !== 'undefined'; const isWebWorker = !isBrowser; // && typeof importScripts !== 'undefined';
if (isWebWorker) { if (isWebWorker) {
fetch(path, { mode: <RequestMode>"cors" }).then((response) => { fetch(path, { mode: <RequestMode>"cors" }).then((response) => {
if (response.ok) return response.blob(); if (response.ok) return response.blob();
@ -182,7 +194,7 @@ export class AssetManagerBase implements Disposable {
}; };
}); });
} else { } else {
let image = new Image(); const image = new Image();
image.crossOrigin = "anonymous"; image.crossOrigin = "anonymous";
image.onload = () => { image.onload = () => {
const texture = this.createTexture(path, image); const texture = this.createTexture(path, image);
@ -200,28 +212,29 @@ export class AssetManagerBase implements Disposable {
}); });
} }
loadTextureAtlas (path: string, loadTextureAtlas (
success: (path: string, atlas: TextureAtlas) => void = () => { }, path: string,
error: (path: string, message: string) => void = () => { }, success: AssetCallback<TextureAtlas> = () => { },
error: ErrorCallback = () => { },
fileAlias?: { [keyword: string]: string } fileAlias?: { [keyword: string]: string }
) { ) {
let index = path.lastIndexOf("/"); const index = path.lastIndexOf("/");
let parent = index >= 0 ? path.substring(0, index + 1) : ""; const parent = index >= 0 ? path.substring(0, index + 1) : "";
path = this.start(path); path = this.start(path);
if (this.reuseAssets(path, success, error)) return; if (this.reuseAssets(path, success, error)) return;
this.cache.assetsLoaded[path] = new Promise<any>((resolve, reject) => { this.cache.assetsLoaded[path] = new Promise<TextureAtlas>((resolve, reject) => {
this.downloader.downloadText(path, (atlasText: string): void => { this.downloader.downloadText(path, (atlasText: string): void => {
try { try {
const atlas = this.createTextureAtlas(path, atlasText); const atlas = this.createTextureAtlas(path, atlasText);
let toLoad = atlas.pages.length, abort = false; let toLoad = atlas.pages.length, abort = false;
for (let page of atlas.pages) { for (const page of atlas.pages) {
this.loadTexture(!fileAlias ? parent + page.name : fileAlias[page.name!], this.loadTexture(!fileAlias ? parent + page.name : fileAlias[page.name],
(imagePath: string, texture: Texture) => { (imagePath: string, texture: Texture) => {
if (!abort) { if (!abort) {
page.setTexture(texture); page.setTexture(texture);
if (--toLoad == 0) { if (--toLoad === 0) {
this.success(success, path, atlas); this.success(success, path, atlas);
resolve(atlas); resolve(atlas);
} }
@ -237,8 +250,8 @@ export class AssetManagerBase implements Disposable {
} }
); );
} }
} catch (e) { } catch (e: unknown) {
const errorMsg = `Couldn't parse texture atlas ${path}: ${(e as any).message}`; const errorMsg = `Couldn't parse texture atlas ${path}: ${(e as Error).message}`;
this.error(error, path, errorMsg); this.error(error, path, errorMsg);
reject(errorMsg); reject(errorMsg);
} }
@ -250,23 +263,23 @@ export class AssetManagerBase implements Disposable {
}); });
} }
loadTextureAtlasButNoTextures (path: string, loadTextureAtlasButNoTextures (
success: (path: string, atlas: TextureAtlas) => void = () => { }, path: string,
error: (path: string, message: string) => void = () => { }, success: AssetCallback<TextureAtlas> = () => { },
fileAlias?: { [keyword: string]: string } error: ErrorCallback = () => { },
) { ) {
path = this.start(path); path = this.start(path);
if (this.reuseAssets(path, success, error)) return; if (this.reuseAssets(path, success, error)) return;
this.cache.assetsLoaded[path] = new Promise<any>((resolve, reject) => { this.cache.assetsLoaded[path] = new Promise<TextureAtlas>((resolve, reject) => {
this.downloader.downloadText(path, (atlasText: string): void => { this.downloader.downloadText(path, (atlasText: string): void => {
try { try {
const atlas = this.createTextureAtlas(path, atlasText); const atlas = this.createTextureAtlas(path, atlasText);
this.success(success, path, atlas); this.success(success, path, atlas);
resolve(atlas); resolve(atlas);
} catch (e) { } catch (e) {
const errorMsg = `Couldn't parse texture atlas ${path}: ${(e as any).message}`; const errorMsg = `Couldn't parse texture atlas ${path}: ${(e as Error).message}`;
this.error(error, path, errorMsg); this.error(error, path, errorMsg);
reject(errorMsg); reject(errorMsg);
} }
@ -334,15 +347,15 @@ export class AssetManagerBase implements Disposable {
require (path: string) { require (path: string) {
path = this.pathPrefix + path; path = this.pathPrefix + path;
let asset = this.cache.assets[path]; const asset = this.cache.assets[path];
if (asset) return asset; if (asset) return asset;
let error = this.errors[path]; const error = this.errors[path];
throw Error("Asset not found: " + path + (error ? "\n" + error : "")); throw Error(`Asset not found: ${path}${error ? `\n${error}` : ""}`);
} }
remove (path: string) { remove (path: string) {
path = this.pathPrefix + path; path = this.pathPrefix + path;
let asset = this.cache.assets[path]; const asset = this.cache.assets[path];
if (asset.dispose) asset.dispose(); if (asset.dispose) asset.dispose();
delete this.cache.assets[path]; delete this.cache.assets[path];
delete this.cache.assetsRefCount[path]; delete this.cache.assetsRefCount[path];
@ -351,8 +364,8 @@ export class AssetManagerBase implements Disposable {
} }
removeAll () { removeAll () {
for (let path in this.cache.assets) { for (const path in this.cache.assets) {
let asset = this.cache.assets[path]; const asset = this.cache.assets[path];
if (asset.dispose) asset.dispose(); if (asset.dispose) asset.dispose();
} }
this.cache.assets = {}; this.cache.assets = {};
@ -361,7 +374,7 @@ export class AssetManagerBase implements Disposable {
} }
isLoadingComplete (): boolean { isLoadingComplete (): boolean {
return this.toLoad == 0; return this.toLoad === 0;
} }
getToLoad (): number { getToLoad (): number {
@ -423,9 +436,9 @@ export class AssetManagerBase implements Disposable {
} }
export class AssetCache { export class AssetCache {
public assets: StringMap<any> = {}; public assets: StringMap<AssetData> = {};
public assetsRefCount: StringMap<number> = {}; public assetsRefCount: StringMap<number> = {};
public assetsLoaded: StringMap<Promise<any>> = {}; public assetsLoaded: StringMap<Promise<AssetData | undefined>> = {};
static AVAILABLE_CACHES = new Map<string, AssetCache>(); static AVAILABLE_CACHES = new Map<string, AssetCache>();
static getCache (id: string) { static getCache (id: string) {
@ -437,14 +450,22 @@ export class AssetCache {
return newCache; return newCache;
} }
async addAsset (path: string, asset: any) { async addAsset<T extends AssetData> (path: string, asset: T): Promise<T> {
this.assetsLoaded[path] = Promise.resolve(asset); this.assetsLoaded[path] = Promise.resolve(asset);
this.assets[path] = await asset; this.assets[path] = asset;
return asset;
}
getAsset<T extends AssetData> (path: string): Promise<T> | undefined {
return this.assetsLoaded[path] as Promise<T> | undefined;
} }
} }
type DownloaderSuccessCallback<T extends AssetData = AssetData> = (data: T) => void;
type DownloaderErrorCallback = (status: number, responseText: string) => void;
export class Downloader { export class Downloader {
private callbacks: StringMap<Array<Function>> = {}; private callbacks: StringMap<Array<DownloaderSuccessCallback | DownloaderErrorCallback>> = {};
rawDataUris: StringMap<string> = {}; rawDataUris: StringMap<string> = {};
dataUriToString (dataUri: string) { dataUriToString (dataUri: string) {
@ -453,7 +474,7 @@ export class Downloader {
} }
let base64Idx = dataUri.indexOf("base64,"); let base64Idx = dataUri.indexOf("base64,");
if (base64Idx != -1) { if (base64Idx !== -1) {
base64Idx += "base64,".length; base64Idx += "base64,".length;
return atob(dataUri.substr(base64Idx)); return atob(dataUri.substr(base64Idx));
} else { } else {
@ -465,7 +486,7 @@ export class Downloader {
var binary_string = window.atob(base64); var binary_string = window.atob(base64);
var len = binary_string.length; var len = binary_string.length;
var bytes = new Uint8Array(len); var bytes = new Uint8Array(len);
for (var i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i); bytes[i] = binary_string.charCodeAt(i);
} }
return bytes; return bytes;
@ -477,12 +498,12 @@ export class Downloader {
} }
let base64Idx = dataUri.indexOf("base64,"); let base64Idx = dataUri.indexOf("base64,");
if (base64Idx == -1) throw new Error("Not a binary data URI."); if (base64Idx === -1) throw new Error("Not a binary data URI.");
base64Idx += "base64,".length; base64Idx += "base64,".length;
return this.base64ToUint8Array(dataUri.substr(base64Idx)); return this.base64ToUint8Array(dataUri.substr(base64Idx));
} }
downloadText (url: string, success: (data: string) => void, error: (status: number, responseText: string) => void) { downloadText (url: string, success: DownloaderSuccessCallback<string>, error: DownloaderErrorCallback) {
if (this.start(url, success, error)) return; if (this.start(url, success, error)) return;
const rawDataUri = this.rawDataUris[url]; const rawDataUri = this.rawDataUris[url];
@ -496,10 +517,10 @@ export class Downloader {
return; return;
} }
let request = new XMLHttpRequest(); const request = new XMLHttpRequest();
request.overrideMimeType("text/html"); request.overrideMimeType("text/html");
request.open("GET", rawDataUri ? rawDataUri : url, true); request.open("GET", rawDataUri ? rawDataUri : url, true);
let done = () => { const done = () => {
this.finish(url, request.status, request.responseText); this.finish(url, request.status, request.responseText);
}; };
request.onload = done; request.onload = done;
@ -507,13 +528,13 @@ export class Downloader {
request.send(); request.send();
} }
downloadJson (url: string, success: (data: object) => void, error: (status: number, responseText: string) => void) { downloadJson (url: string, success: DownloaderSuccessCallback<object>, error: DownloaderErrorCallback) {
this.downloadText(url, (data: string): void => { this.downloadText(url, (data: string): void => {
success(JSON.parse(data)); success(JSON.parse(data));
}, error); }, error);
} }
downloadBinary (url: string, success: (data: Uint8Array) => void, error: (status: number, responseText: string) => void) { downloadBinary (url: string, success: (data: Uint8Array) => void, error: DownloaderErrorCallback) {
if (this.start(url, success, error)) return; if (this.start(url, success, error)) return;
const rawDataUri = this.rawDataUris[url]; const rawDataUri = this.rawDataUris[url];
@ -527,14 +548,14 @@ export class Downloader {
return; return;
} }
let request = new XMLHttpRequest(); const request = new XMLHttpRequest();
request.open("GET", rawDataUri ? rawDataUri : url, true); request.open("GET", rawDataUri ? rawDataUri : url, true);
request.responseType = "arraybuffer"; request.responseType = "arraybuffer";
let onerror = () => { const onerror = () => {
this.finish(url, request.status, request.response); this.finish(url, request.status, request.response);
}; };
request.onload = () => { request.onload = () => {
if (request.status == 200 || request.status == 0) if (request.status === 200 || request.status === 0)
this.finish(url, 200, new Uint8Array(request.response as ArrayBuffer)); this.finish(url, 200, new Uint8Array(request.response as ArrayBuffer));
else else
onerror(); onerror();
@ -543,21 +564,25 @@ export class Downloader {
request.send(); request.send();
} }
private start (url: string, success: any, error: any) { private start<T extends AssetData> (url: string, success: DownloaderSuccessCallback<T>, error: DownloaderErrorCallback) {
let callbacks = this.callbacks[url]; let callbacks = this.callbacks[url];
try { try {
if (callbacks) return true; if (callbacks) return true;
this.callbacks[url] = callbacks = []; this.callbacks[url] = callbacks = [];
} finally { } finally {
callbacks.push(success, error); callbacks.push(success as DownloaderSuccessCallback<AssetData>, error);
} }
} }
private finish (url: string, status: number, data: any) { private finish (url: string, status: number, data: AssetData) {
let callbacks = this.callbacks[url]; const callbacks = this.callbacks[url];
delete this.callbacks[url]; delete this.callbacks[url];
let args = status == 200 || status == 0 ? [data] : [status, data]; if (status === 200 || status === 0) {
for (let i = args.length - 1, n = callbacks.length; i < n; i += 2) for (let i = 0, n = callbacks.length; i < n; i += 2)
callbacks[i].apply(null, args); (callbacks[i] as DownloaderSuccessCallback)(data);
} else {
for (let i = 1, n = callbacks.length; i < n; i += 2)
(callbacks[i] as DownloaderErrorCallback)(status, data as string);
}
} }
} }

View File

@ -27,8 +27,8 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { BoneData } from "./BoneData.js"; import type { BoneData } from "./BoneData.js";
import { BoneLocal } from "./BoneLocal.js"; import type { BoneLocal } from "./BoneLocal.js";
import { BonePose } from "./BonePose.js"; import { BonePose } from "./BonePose.js";
import { PosedActive } from "./PosedActive.js"; import { PosedActive } from "./PosedActive.js";
@ -42,7 +42,7 @@ export class Bone extends PosedActive<BoneData, BoneLocal, BonePose> {
parent: Bone | null = null; parent: Bone | null = null;
/** The immediate children of this bone. */ /** The immediate children of this bone. */
children = new Array<Bone>(); children = [] as Bone[];
sorted = false; sorted = false;

View File

@ -29,9 +29,8 @@
import { BoneLocal } from "./BoneLocal.js"; import { BoneLocal } from "./BoneLocal.js";
import { PosedData } from "./PosedData.js"; import { PosedData } from "./PosedData.js";
import { Color } from "./Utils.js";
import type { Skeleton } from "./Skeleton.js"; import type { Skeleton } from "./Skeleton.js";
import { Color } from "./Utils.js";
/** The setup pose for a bone. */ /** The setup pose for a bone. */
export class BoneData extends PosedData<BoneLocal> { export class BoneData extends PosedData<BoneLocal> {

View File

@ -28,7 +28,7 @@
*****************************************************************************/ *****************************************************************************/
import { Inherit } from "./BoneData.js"; import { Inherit } from "./BoneData.js";
import { Pose } from "./Pose.js" import type { Pose } from "./Pose.js"
/** Stores a bone's local pose. */ /** Stores a bone's local pose. */
export class BoneLocal implements Pose<BoneLocal> { export class BoneLocal implements Pose<BoneLocal> {

View File

@ -27,13 +27,13 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { Bone } from "./Bone.js"; import type { Bone } from "./Bone.js";
import { Inherit } from "./BoneData.js"; import { Inherit } from "./BoneData.js";
import { BoneLocal } from "./BoneLocal.js"; import { BoneLocal } from "./BoneLocal.js";
import { Physics } from "./Physics.js"; import type { Physics } from "./Physics.js";
import { Skeleton } from "./Skeleton.js"; import type { Skeleton } from "./Skeleton.js";
import { Update } from "./Update.js"; import type { Update } from "./Update.js";
import { MathUtils, Vector2 } from "./Utils.js"; import { MathUtils, type Vector2 } from "./Utils.js";
/** The applied pose for a bone. This is the {@link Bone} pose with constraints applied and the world transform computed by /** The applied pose for a bone. This is the {@link Bone} pose with constraints applied and the world transform computed by
* {@link Skeleton#updateWorldTransform()}. */ * {@link Skeleton#updateWorldTransform()}. */
@ -76,7 +76,7 @@ export class BonePose extends BoneLocal implements Update {
else else
this.world = skeleton._update; this.world = skeleton._update;
let rotation = this.rotation; const rotation = this.rotation;
const scaleX = this.scaleX; const scaleX = this.scaleX;
const scaleY = this.scaleY; const scaleY = this.scaleY;
const shearX = this.shearX; const shearX = this.shearX;
@ -123,7 +123,7 @@ export class BonePose extends BoneLocal implements Update {
break; break;
} }
case Inherit.NoRotationOrReflection: { case Inherit.NoRotationOrReflection: {
let sx = 1 / skeleton.scaleX, sy = 1 / skeleton.scaleY; const sx = 1 / skeleton.scaleX, sy = 1 / skeleton.scaleY;
pa *= sx; pa *= sx;
pc *= sy; pc *= sy;
let s = pa * pa + pc * pc; let s = pa * pa + pc * pc;
@ -160,7 +160,7 @@ export class BonePose extends BoneLocal implements Update {
za *= s; za *= s;
zc *= s; zc *= s;
s = Math.sqrt(za * za + zc * zc); s = Math.sqrt(za * za + zc * zc);
if (this.inherit == Inherit.NoScale && (pa * pd - pb * pc < 0) != (skeleton.scaleX < 0 != skeleton.scaleY < 0)) s = -s; if (this.inherit === Inherit.NoScale && (pa * pd - pb * pc < 0) !== (skeleton.scaleX < 0 !== skeleton.scaleY < 0)) s = -s;
r = Math.PI / 2 + Math.atan2(zc, za); r = Math.PI / 2 + Math.atan2(zc, za);
const zb = Math.cos(r) * s; const zb = Math.cos(r) * s;
const zd = Math.sin(r) * s; const zd = Math.sin(r) * s;
@ -198,7 +198,7 @@ export class BonePose extends BoneLocal implements Update {
if (!this.bone.parent) { if (!this.bone.parent) {
this.x = this.worldX - skeleton.x; this.x = this.worldX - skeleton.x;
this.y = this.worldY - skeleton.y; this.y = this.worldY - skeleton.y;
let a = this.a, b = this.b, c = this.c, d = this.d; const a = this.a, b = this.b, c = this.c, d = this.d;
this.rotation = MathUtils.atan2Deg(c, a); this.rotation = MathUtils.atan2Deg(c, a);
this.scaleX = Math.sqrt(a * a + c * c); this.scaleX = Math.sqrt(a * a + c * c);
this.scaleY = Math.sqrt(b * b + d * d); this.scaleY = Math.sqrt(b * b + d * d);
@ -211,12 +211,12 @@ export class BonePose extends BoneLocal implements Update {
let pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d; let pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
let pid = 1 / (pa * pd - pb * pc); let pid = 1 / (pa * pd - pb * pc);
let ia = pd * pid, ib = pb * pid, ic = pc * pid, id = pa * pid; let ia = pd * pid, ib = pb * pid, ic = pc * pid, id = pa * pid;
let dx = this.worldX - parent.worldX, dy = this.worldY - parent.worldY; const dx = this.worldX - parent.worldX, dy = this.worldY - parent.worldY;
this.x = (dx * ia - dy * ib); this.x = (dx * ia - dy * ib);
this.y = (dy * id - dx * ic); this.y = (dy * id - dx * ic);
let ra, rb, rc, rd; let ra: number, rb: number, rc: number, rd: number;
if (this.inherit == Inherit.OnlyTranslation) { if (this.inherit === Inherit.OnlyTranslation) {
ra = this.a; ra = this.a;
rb = this.b; rb = this.b;
rc = this.c; rc = this.c;
@ -224,7 +224,7 @@ export class BonePose extends BoneLocal implements Update {
} else { } else {
switch (this.inherit) { switch (this.inherit) {
case Inherit.NoRotationOrReflection: { case Inherit.NoRotationOrReflection: {
let s = Math.abs(pa * pd - pb * pc) / (pa * pa + pc * pc); const s = Math.abs(pa * pd - pb * pc) / (pa * pa + pc * pc);
pb = -pc * skeleton.scaleX * s / skeleton.scaleY; pb = -pc * skeleton.scaleX * s / skeleton.scaleY;
pd = pa * skeleton.scaleY * s / skeleton.scaleX; pd = pa * skeleton.scaleY * s / skeleton.scaleX;
pid = 1 / (pa * pd - pb * pc); pid = 1 / (pa * pd - pb * pc);
@ -233,7 +233,7 @@ export class BonePose extends BoneLocal implements Update {
break; break;
} }
case Inherit.NoScale: case Inherit.NoScale:
case Inherit.NoScaleOrReflection: case Inherit.NoScaleOrReflection: {
let r = this.rotation * MathUtils.degRad, cos = Math.cos(r), sin = Math.sin(r); let r = this.rotation * MathUtils.degRad, cos = Math.cos(r), sin = Math.sin(r);
pa = (pa * cos + pb * sin) / skeleton.scaleX; pa = (pa * cos + pb * sin) / skeleton.scaleX;
pc = (pc * cos + pd * sin) / skeleton.scaleY; pc = (pc * cos + pd * sin) / skeleton.scaleY;
@ -242,7 +242,7 @@ export class BonePose extends BoneLocal implements Update {
pa *= s; pa *= s;
pc *= s; pc *= s;
s = Math.sqrt(pa * pa + pc * pc); s = Math.sqrt(pa * pa + pc * pc);
if (this.inherit == Inherit.NoScale && pid < 0 != (skeleton.scaleX < 0 != skeleton.scaleY < 0)) s = -s; if (this.inherit === Inherit.NoScale && pid < 0 !== (skeleton.scaleX < 0 !== skeleton.scaleY < 0)) s = -s;
r = MathUtils.PI / 2 + Math.atan2(pc, pa); r = MathUtils.PI / 2 + Math.atan2(pc, pa);
pb = Math.cos(r) * s; pb = Math.cos(r) * s;
pd = Math.sin(r) * s; pd = Math.sin(r) * s;
@ -251,6 +251,7 @@ export class BonePose extends BoneLocal implements Update {
ib = pb * pid; ib = pb * pid;
ic = pc * pid; ic = pc * pid;
id = pa * pid; id = pa * pid;
}
} }
ra = ia * this.a - ib * this.c; ra = ia * this.a - ib * this.c;
rb = ia * this.b - ib * this.d; rb = ia * this.b - ib * this.d;
@ -261,7 +262,7 @@ export class BonePose extends BoneLocal implements Update {
this.shearX = 0; this.shearX = 0;
this.scaleX = Math.sqrt(ra * ra + rc * rc); this.scaleX = Math.sqrt(ra * ra + rc * rc);
if (this.scaleX > 0.0001) { if (this.scaleX > 0.0001) {
let det = ra * rd - rb * rc; const det = ra * rd - rb * rc;
this.scaleY = det / this.scaleX; this.scaleY = det / this.scaleX;
this.shearY = -MathUtils.atan2Deg(ra * rb + rc * rd, det); this.shearY = -MathUtils.atan2Deg(ra * rb + rc * rd, det);
this.rotation = MathUtils.atan2Deg(rc, ra); this.rotation = MathUtils.atan2Deg(rc, ra);
@ -341,8 +342,8 @@ export class BonePose extends BoneLocal implements Update {
/** Transforms a point from world coordinates to the bone's local coordinates. */ /** Transforms a point from world coordinates to the bone's local coordinates. */
public worldToLocal (world: Vector2): Vector2 { public worldToLocal (world: Vector2): Vector2 {
if (world == null) throw new Error("world cannot be null."); if (world == null) throw new Error("world cannot be null.");
let det = this.a * this.d - this.b * this.c; const det = this.a * this.d - this.b * this.c;
let x = world.x - this.worldX, y = world.y - this.worldY; const x = world.x - this.worldX, y = world.y - this.worldY;
world.x = (x * this.d - y * this.b) / det; world.x = (x * this.d - y * this.b) / det;
world.y = (y * this.a - x * this.c) / det; world.y = (y * this.a - x * this.c) / det;
return world; return world;
@ -351,7 +352,7 @@ export class BonePose extends BoneLocal implements Update {
/** Transforms a point from the bone's local coordinates to world coordinates. */ /** Transforms a point from the bone's local coordinates to world coordinates. */
public localToWorld (local: Vector2): Vector2 { public localToWorld (local: Vector2): Vector2 {
if (local == null) throw new Error("local cannot be null."); if (local == null) throw new Error("local cannot be null.");
let x = local.x, y = local.y; const x = local.x, y = local.y;
local.x = x * this.a + y * this.b + this.worldX; local.x = x * this.a + y * this.b + this.worldX;
local.y = x * this.c + y * this.d + this.worldY; local.y = x * this.c + y * this.d + this.worldY;
return local; return local;
@ -372,14 +373,14 @@ export class BonePose extends BoneLocal implements Update {
/** Transforms a world rotation to a local rotation. */ /** Transforms a world rotation to a local rotation. */
public worldToLocalRotation (worldRotation: number): number { public worldToLocalRotation (worldRotation: number): number {
worldRotation *= MathUtils.degRad; worldRotation *= MathUtils.degRad;
let sin = Math.sin(worldRotation), cos = Math.cos(worldRotation); const sin = Math.sin(worldRotation), cos = Math.cos(worldRotation);
return MathUtils.atan2Deg(this.a * sin - this.c * cos, this.d * cos - this.b * sin) + this.rotation - this.shearX; return MathUtils.atan2Deg(this.a * sin - this.c * cos, this.d * cos - this.b * sin) + this.rotation - this.shearX;
} }
/** Transforms a local rotation to a world rotation. */ /** Transforms a local rotation to a world rotation. */
localToWorldRotation (localRotation: number): number { localToWorldRotation (localRotation: number): number {
localRotation = (localRotation - this.rotation - this.shearX) * MathUtils.degRad; localRotation = (localRotation - this.rotation - this.shearX) * MathUtils.degRad;
let sin = Math.sin(localRotation), cos = Math.cos(localRotation); const sin = Math.sin(localRotation), cos = Math.cos(localRotation);
return MathUtils.atan2Deg(cos * this.c + sin * this.d, cos * this.a + sin * this.b); return MathUtils.atan2Deg(cos * this.c + sin * this.d, cos * this.a + sin * this.b);
} }

View File

@ -27,17 +27,17 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { ConstraintData } from "./ConstraintData.js"; import type { ConstraintData } from "./ConstraintData.js";
import { Physics } from "./Physics.js"; import type { Physics } from "./Physics.js";
import { Pose } from "./Pose.js"; import type { Pose } from "./Pose.js";
import { PosedActive } from "./PosedActive.js"; import { PosedActive } from "./PosedActive.js";
import { Skeleton } from "./Skeleton.js"; import type { Skeleton } from "./Skeleton.js";
import { Update } from "./Update.js"; import type { Update } from "./Update.js";
export abstract class Constraint< export abstract class Constraint<
T extends Constraint<T, D, P>, T extends Constraint<T, D, P>,
D extends ConstraintData<T, P>, D extends ConstraintData<T, P>,
P extends Pose<any>> P extends Pose<P>>
extends PosedActive<D, P, P> implements Update { extends PosedActive<D, P, P> implements Update {
constructor (data: D, pose: P, constrained: P) { constructor (data: D, pose: P, constrained: P) {

View File

@ -27,15 +27,15 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { Constraint } from "./Constraint.js"; import type { Constraint } from "./Constraint.js";
import { Pose } from "./Pose.js"; import type { Pose } from "./Pose.js";
import { PosedData } from "./PosedData.js"; import { PosedData } from "./PosedData.js";
import { Skeleton } from "./Skeleton.js"; import type { Skeleton } from "./Skeleton.js";
/** The base class for all constraint datas. */ /** The base class for all constraint datas. */
export abstract class ConstraintData< export abstract class ConstraintData<
T extends Constraint<any, any, any>, T extends Constraint<T, ConstraintData<T, P>, P>,
P extends Pose<any>> P extends Pose<P>>
extends PosedData<P> { extends PosedData<P> {
constructor (name: string, setup: P) { constructor (name: string, setup: P) {

View File

@ -27,7 +27,7 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { EventData } from "./EventData.js"; import type { EventData } from "./EventData.js";
import type { Timeline } from "./Animation.js"; import type { Timeline } from "./Animation.js";
import type { AnimationStateListener } from "./AnimationState.js"; import type { AnimationStateListener } from "./AnimationState.js";

View File

@ -27,14 +27,14 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { Bone } from "./Bone.js"; import type { Bone } from "./Bone.js";
import { Inherit } from "./BoneData.js"; import { Inherit } from "./BoneData.js";
import { BonePose } from "./BonePose.js"; import type { BonePose } from "./BonePose.js";
import { Constraint } from "./Constraint.js"; import { Constraint } from "./Constraint.js";
import { IkConstraintData } from "./IkConstraintData.js"; import type { IkConstraintData } from "./IkConstraintData.js";
import { IkConstraintPose } from "./IkConstraintPose.js"; import { IkConstraintPose } from "./IkConstraintPose.js";
import { Physics } from "./Physics.js"; import type { Physics } from "./Physics.js";
import { Skeleton } from "./Skeleton.js"; import type { Skeleton } from "./Skeleton.js";
import { MathUtils } from "./Utils.js"; import { MathUtils } from "./Utils.js";
/** Stores the current pose for an IK constraint. An IK constraint adjusts the rotation of 1 or 2 constrained bones so the tip of /** Stores the current pose for an IK constraint. An IK constraint adjusts the rotation of 1 or 2 constrained bones so the tip of
@ -52,7 +52,7 @@ export class IkConstraint extends Constraint<IkConstraint, IkConstraintData, IkC
super(data, new IkConstraintPose(), new IkConstraintPose()); super(data, new IkConstraintPose(), new IkConstraintPose());
if (!skeleton) throw new Error("skeleton cannot be null."); if (!skeleton) throw new Error("skeleton cannot be null.");
this.bones = new Array<BonePose>(); this.bones = [] as BonePose[];
for (const boneData of data.bones) for (const boneData of data.bones)
this.bones.push(skeleton.bones[boneData.index].constrained); this.bones.push(skeleton.bones[boneData.index].constrained);
@ -107,16 +107,17 @@ export class IkConstraint extends Constraint<IkConstraint, IkConstraintData, IkC
stretchOrBendDir: boolean | number, uniformOrStretch: boolean, mixOrUniform: number | boolean, softness?: number, mix?: number) { stretchOrBendDir: boolean | number, uniformOrStretch: boolean, mixOrUniform: number | boolean, softness?: number, mix?: number) {
if (typeof targetXorChild === "number") if (typeof targetXorChild === "number")
this.apply1(skeleton, boneOrParent, targetXorChild, targetYOrTargetX, compressOrTargetY as boolean, stretchOrBendDir as boolean, uniformOrStretch, mixOrUniform as number); IkConstraint.apply1(skeleton, boneOrParent, targetXorChild, targetYOrTargetX, compressOrTargetY as boolean, stretchOrBendDir as boolean, uniformOrStretch, mixOrUniform as number);
else else
this.apply2(skeleton, boneOrParent, targetXorChild as BonePose, targetYOrTargetX, compressOrTargetY as number, stretchOrBendDir as number, IkConstraint.apply2(skeleton, boneOrParent, targetXorChild as BonePose, targetYOrTargetX, compressOrTargetY as number, stretchOrBendDir as number,
uniformOrStretch, mixOrUniform as boolean, softness as number, mix as number); uniformOrStretch, mixOrUniform as boolean, softness as number, mix as number);
} }
private static apply1 (skeleton: Skeleton, bone: BonePose, targetX: number, targetY: number, compress: boolean, stretch: boolean, uniform: boolean, mix: number) { private static apply1 (skeleton: Skeleton, bone: BonePose, targetX: number, targetY: number, compress: boolean, stretch: boolean, uniform: boolean, mix: number) {
bone.modifyLocal(skeleton); bone.modifyLocal(skeleton);
let p = bone.bone.parent!.applied; // biome-ignore lint/style/noNonNullAssertion: reference runtime
const p = bone.bone.parent!.applied;
let pa = p.a, pb = p.b, pc = p.c, pd = p.d; let pa = p.a, pb = p.b, pc = p.c, pd = p.d;
let rotationIK = -bone.shearX - bone.rotation, tx = 0, ty = 0; let rotationIK = -bone.shearX - bone.rotation, tx = 0, ty = 0;
@ -126,17 +127,19 @@ export class IkConstraint extends Constraint<IkConstraint, IkConstraintData, IkC
tx = (targetX - bone.worldX) * MathUtils.signum(skeleton.scaleX); tx = (targetX - bone.worldX) * MathUtils.signum(skeleton.scaleX);
ty = (targetY - bone.worldY) * MathUtils.signum(skeleton.scaleY); ty = (targetY - bone.worldY) * MathUtils.signum(skeleton.scaleY);
break; break;
case Inherit.NoRotationOrReflection: // biome-ignore lint/suspicious/noFallthroughSwitchClause: reference runtime
let s = Math.abs(pa * pd - pb * pc) / Math.max(0.0001, pa * pa + pc * pc); case Inherit.NoRotationOrReflection: {
let sa = pa / skeleton.scaleX; const s = Math.abs(pa * pd - pb * pc) / Math.max(0.0001, pa * pa + pc * pc);
let sc = pc / skeleton.scaleY; const sa = pa / skeleton.scaleX;
const sc = pc / skeleton.scaleY;
pb = -sc * s * skeleton.scaleX; pb = -sc * s * skeleton.scaleX;
pd = sa * s * skeleton.scaleY; pd = sa * s * skeleton.scaleY;
rotationIK += Math.atan2(sc, sa) * MathUtils.radDeg; rotationIK += Math.atan2(sc, sa) * MathUtils.radDeg;
}
// Fall through // Fall through
default: default: {
let x = targetX - p.worldX, y = targetY - p.worldY; const x = targetX - p.worldX, y = targetY - p.worldY;
let d = pa * pd - pb * pc; const d = pa * pd - pb * pc;
if (Math.abs(d) <= 0.0001) { if (Math.abs(d) <= 0.0001) {
tx = 0; tx = 0;
ty = 0; ty = 0;
@ -144,6 +147,7 @@ export class IkConstraint extends Constraint<IkConstraint, IkConstraintData, IkC
tx = (x * pd - y * pb) / d - bone.x; tx = (x * pd - y * pb) / d - bone.x;
ty = (y * pa - x * pc) / d - bone.y; ty = (y * pa - x * pc) / d - bone.y;
} }
}
} }
rotationIK += MathUtils.atan2Deg(ty, tx); rotationIK += MathUtils.atan2Deg(ty, tx);
if (bone.scaleX < 0) rotationIK += 180; if (bone.scaleX < 0) rotationIK += 180;
@ -174,7 +178,7 @@ export class IkConstraint extends Constraint<IkConstraint, IkConstraintData, IkC
/** Applies 2 bone IK. The target is specified in the world coordinate system. /** Applies 2 bone IK. The target is specified in the world coordinate system.
* @param child A direct descendant of the parent bone. */ * @param child A direct descendant of the parent bone. */
private static apply2 (skeleton: Skeleton, parent: BonePose, child: BonePose, targetX: number, targetY: number, bendDir: number, stretch: boolean, uniform: boolean, softness: number, mix: number) { private static apply2 (skeleton: Skeleton, parent: BonePose, child: BonePose, targetX: number, targetY: number, bendDir: number, stretch: boolean, uniform: boolean, softness: number, mix: number) {
if (parent.inherit != Inherit.Normal || child.inherit != Inherit.Normal) return; if (parent.inherit !== Inherit.Normal || child.inherit !== Inherit.Normal) return;
parent.modifyLocal(skeleton); parent.modifyLocal(skeleton);
child.modifyLocal(skeleton); child.modifyLocal(skeleton);
let px = parent.x, py = parent.y, psx = parent.scaleX, psy = parent.scaleY, csx = child.scaleX; let px = parent.x, py = parent.y, psx = parent.scaleX, psy = parent.scaleY, csx = child.scaleX;
@ -197,7 +201,7 @@ export class IkConstraint extends Constraint<IkConstraint, IkConstraintData, IkC
} else } else
os2 = 0; os2 = 0;
let cwx = 0, cwy = 0, a = parent.a, b = parent.b, c = parent.c, d = parent.d; let cwx = 0, cwy = 0, a = parent.a, b = parent.b, c = parent.c, d = parent.d;
let u = Math.abs(psx - psy) <= 0.0001; const u = Math.abs(psx - psy) <= 0.0001;
if (!u || stretch) { if (!u || stretch) {
child.y = 0; child.y = 0;
cwx = a * child.x + parent.worldX; cwx = a * child.x + parent.worldX;
@ -206,15 +210,16 @@ export class IkConstraint extends Constraint<IkConstraint, IkConstraintData, IkC
cwx = a * child.x + b * child.y + parent.worldX; cwx = a * child.x + b * child.y + parent.worldX;
cwy = c * child.x + d * child.y + parent.worldY; cwy = c * child.x + d * child.y + parent.worldY;
} }
let pp = parent.bone.parent!.applied; // biome-ignore lint/style/noNonNullAssertion: reference-runtime
const pp = parent.bone.parent!.applied;
a = pp.a; a = pp.a;
b = pp.b; b = pp.b;
c = pp.c; c = pp.c;
d = pp.d; d = pp.d;
let id = a * d - b * c, x = cwx - pp.worldX, y = cwy - pp.worldY; let id = a * d - b * c, x = cwx - pp.worldX, y = cwy - pp.worldY;
id = Math.abs(id) <= 0.0001 ? 0 : 1 / id; id = Math.abs(id) <= 0.0001 ? 0 : 1 / id;
let dx = (x * d - y * b) * id - px, dy = (y * a - x * c) * id - py; const dx = (x * d - y * b) * id - px, dy = (y * a - x * c) * id - py;
let l1 = Math.sqrt(dx * dx + dy * dy), l2 = child.bone.data.length * csx, a1, a2; let l1 = Math.sqrt(dx * dx + dy * dy), l2 = child.bone.data.length * csx, a1: number, a2: number;
if (l1 < 0.0001) { if (l1 < 0.0001) {
IkConstraint.apply(skeleton, parent, targetX, targetY, false, stretch, false, mix); IkConstraint.apply(skeleton, parent, targetX, targetY, false, stretch, false, mix);
child.rotation = 0; child.rotation = 0;
@ -224,9 +229,9 @@ export class IkConstraint extends Constraint<IkConstraint, IkConstraintData, IkC
y = targetY - pp.worldY; y = targetY - pp.worldY;
let tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py; let tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py;
let dd = tx * tx + ty * ty; let dd = tx * tx + ty * ty;
if (softness != 0) { if (softness !== 0) {
softness *= psx * (csx + 1) * 0.5; softness *= psx * (csx + 1) * 0.5;
let td = Math.sqrt(dd), sd = td - l1 - l2 * psx + softness; const td = Math.sqrt(dd), sd = td - l1 - l2 * psx + softness;
if (sd > 0) { if (sd > 0) {
let p = Math.min(1, sd / (softness * 2)) - 1; let p = Math.min(1, sd / (softness * 2)) - 1;
p = (sd - softness * (1 - p * p)) / td; p = (sd - softness * (1 - p * p)) / td;
@ -235,6 +240,7 @@ export class IkConstraint extends Constraint<IkConstraint, IkConstraintData, IkC
dd = tx * tx + ty * ty; dd = tx * tx + ty * ty;
} }
} }
// biome-ignore lint/suspicious/noConfusingLabels: reference runtime
outer: outer:
if (u) { if (u) {
l2 *= psx; l2 *= psx;
@ -258,16 +264,16 @@ export class IkConstraint extends Constraint<IkConstraint, IkConstraintData, IkC
} else { } else {
a = psx * l2; a = psx * l2;
b = psy * l2; b = psy * l2;
let aa = a * a, bb = b * b, ta = Math.atan2(ty, tx); const aa = a * a, bb = b * b, ta = Math.atan2(ty, tx);
c = bb * l1 * l1 + aa * dd - aa * bb; c = bb * l1 * l1 + aa * dd - aa * bb;
let c1 = -2 * bb * l1, c2 = bb - aa; const c1 = -2 * bb * l1, c2 = bb - aa;
d = c1 * c1 - 4 * c2 * c; d = c1 * c1 - 4 * c2 * c;
if (d >= 0) { if (d >= 0) {
let q = Math.sqrt(d); let q = Math.sqrt(d);
if (c1 < 0) q = -q; if (c1 < 0) q = -q;
q = -(c1 + q) * 0.5; q = -(c1 + q) * 0.5;
let r0 = q / c2, r1 = c / q; let r0 = q / c2, r1 = c / q;
let r = Math.abs(r0) < Math.abs(r1) ? r0 : r1; const r = Math.abs(r0) < Math.abs(r1) ? r0 : r1;
r0 = dd - r * r; r0 = dd - r * r;
if (r0 >= 0) { if (r0 >= 0) {
y = Math.sqrt(r0) * bendDir; y = Math.sqrt(r0) * bendDir;
@ -305,7 +311,7 @@ export class IkConstraint extends Constraint<IkConstraint, IkConstraintData, IkC
a2 = maxAngle * bendDir; a2 = maxAngle * bendDir;
} }
} }
let os = Math.atan2(child.y, child.x) * s2; const os = Math.atan2(child.y, child.x) * s2;
a1 = (a1 - os) * MathUtils.radDeg + os1 - parent.rotation; a1 = (a1 - os) * MathUtils.radDeg + os1 - parent.rotation;
if (a1 > 180) if (a1 > 180)
a1 -= 360; a1 -= 360;

View File

@ -27,18 +27,18 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { BoneData } from "./BoneData.js"; import type { BoneData } from "./BoneData.js";
import { ConstraintData } from "./ConstraintData.js"; import { ConstraintData } from "./ConstraintData.js";
import { IkConstraint } from "./IkConstraint.js"; import { IkConstraint } from "./IkConstraint.js";
import { IkConstraintPose } from "./IkConstraintPose.js"; import { IkConstraintPose } from "./IkConstraintPose.js";
import { Skeleton } from "./Skeleton.js"; import type { Skeleton } from "./Skeleton.js";
/** Stores the setup pose for an {@link IkConstraint}. /** Stores the setup pose for an {@link IkConstraint}.
* *
* See [IK constraints](http://esotericsoftware.com/spine-ik-constraints) in the Spine User Guide. */ * See [IK constraints](http://esotericsoftware.com/spine-ik-constraints) in the Spine User Guide. */
export class IkConstraintData extends ConstraintData<IkConstraint, IkConstraintPose> { export class IkConstraintData extends ConstraintData<IkConstraint, IkConstraintPose> {
/** The bones that are constrained by this IK constraint. */ /** The bones that are constrained by this IK constraint. */
bones = new Array<BoneData>(); bones = [] as BoneData[];
private _target: BoneData | null = null; private _target: BoneData | null = null;
/** The bone that is the IK target. */ /** The bone that is the IK target. */

View File

@ -27,7 +27,7 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { Pose } from "./Pose"; import type { Pose } from "./Pose";
/** Stores the current pose for an IK constraint. */ /** Stores the current pose for an IK constraint. */
export class IkConstraintPose implements Pose<IkConstraintPose> { export class IkConstraintPose implements Pose<IkConstraintPose> {

View File

@ -27,18 +27,18 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { Attachment } from "./attachments/Attachment.js"; import type { Attachment } from "./attachments/Attachment.js";
import { PathAttachment } from "./attachments/PathAttachment.js"; import { PathAttachment } from "./attachments/PathAttachment.js";
import { Bone } from "./Bone.js"; import type { Bone } from "./Bone.js";
import { BonePose } from "./BonePose.js"; import type { BonePose } from "./BonePose.js";
import { Constraint } from "./Constraint.js"; import { Constraint } from "./Constraint.js";
import { PathConstraintData, RotateMode, SpacingMode, PositionMode } from "./PathConstraintData.js"; import { type PathConstraintData, PositionMode, RotateMode, SpacingMode } from "./PathConstraintData.js";
import { PathConstraintPose } from "./PathConstraintPose.js"; import { PathConstraintPose } from "./PathConstraintPose.js";
import { Physics } from "./Physics.js"; import type { Physics } from "./Physics.js";
import { Skeleton } from "./Skeleton.js"; import type { Skeleton } from "./Skeleton.js";
import { Skin, SkinEntry } from "./Skin.js"; import type { Skin } from "./Skin.js";
import { Slot } from "./Slot.js"; import type { Slot } from "./Slot.js";
import { Utils, MathUtils } from "./Utils.js"; import { MathUtils, Utils } from "./Utils.js";
/** Stores the current pose for a path constraint. A path constraint adjusts the rotation, translation, and scale of the /** Stores the current pose for a path constraint. A path constraint adjusts the rotation, translation, and scale of the
@ -58,16 +58,16 @@ export class PathConstraint extends Constraint<PathConstraint, PathConstraintDat
/** The slot whose path attachment will be used to constrained the bones. */ /** The slot whose path attachment will be used to constrained the bones. */
slot: Slot; slot: Slot;
spaces = new Array<number>(); positions = new Array<number>(); spaces = [] as number[]; positions = [] as number[];
world = new Array<number>(); curves = new Array<number>(); lengths = new Array<number>(); world = [] as number[]; curves = [] as number[]; lengths = [] as number[];
segments = new Array<number>(); segments = [] as number[];
constructor (data: PathConstraintData, skeleton: Skeleton) { constructor (data: PathConstraintData, skeleton: Skeleton) {
super(data, new PathConstraintPose(), new PathConstraintPose()); super(data, new PathConstraintPose(), new PathConstraintPose());
if (!skeleton) throw new Error("skeleton cannot be null."); if (!skeleton) throw new Error("skeleton cannot be null.");
this.data = data; this.data = data;
this.bones = new Array<BonePose>(); this.bones = [] as BonePose[];
for (const boneData of this.data.bones) for (const boneData of this.data.bones)
this.bones.push(skeleton.bones[boneData.index].constrained); this.bones.push(skeleton.bones[boneData.index].constrained);
@ -81,44 +81,44 @@ export class PathConstraint extends Constraint<PathConstraint, PathConstraintDat
} }
update (skeleton: Skeleton, physics: Physics) { update (skeleton: Skeleton, physics: Physics) {
let attachment = this.slot.applied.attachment; const attachment = this.slot.applied.attachment;
if (!(attachment instanceof PathAttachment)) return; if (!(attachment instanceof PathAttachment)) return;
const p = this.applied; const p = this.applied;
let mixRotate = p.mixRotate, mixX = p.mixX, mixY = p.mixY; const mixRotate = p.mixRotate, mixX = p.mixX, mixY = p.mixY;
if (mixRotate === 0 && mixX === 0 && mixY === 0) return; if (mixRotate === 0 && mixX === 0 && mixY === 0) return;
let data = this.data; const data = this.data;
let tangents = data.rotateMode == RotateMode.Tangent, scale = data.rotateMode == RotateMode.ChainScale; const tangents = data.rotateMode === RotateMode.Tangent, scale = data.rotateMode === RotateMode.ChainScale;
let bones = this.bones; const bones = this.bones;
let boneCount = bones.length, spacesCount = tangents ? boneCount : boneCount + 1; const boneCount = bones.length, spacesCount = tangents ? boneCount : boneCount + 1;
let spaces = Utils.setArraySize(this.spaces, spacesCount), lengths: Array<number> = scale ? this.lengths = Utils.setArraySize(this.lengths, boneCount) : []; const spaces = Utils.setArraySize(this.spaces, spacesCount), lengths: Array<number> = scale ? this.lengths = Utils.setArraySize(this.lengths, boneCount) : [];
let spacing = p.spacing; const spacing = p.spacing;
switch (data.spacingMode) { switch (data.spacingMode) {
case SpacingMode.Percent: case SpacingMode.Percent:
if (scale) { if (scale) {
for (let i = 0, n = spacesCount - 1; i < n; i++) { for (let i = 0, n = spacesCount - 1; i < n; i++) {
let bone = bones[i]; const bone = bones[i];
let setupLength = bone.bone.data.length; const setupLength = bone.bone.data.length;
let x = setupLength * bone.a, y = setupLength * bone.c; const x = setupLength * bone.a, y = setupLength * bone.c;
lengths[i] = Math.sqrt(x * x + y * y); lengths[i] = Math.sqrt(x * x + y * y);
} }
} }
Utils.arrayFill(spaces, 1, spacesCount, spacing); Utils.arrayFill(spaces, 1, spacesCount, spacing);
break; break;
case SpacingMode.Proportional: case SpacingMode.Proportional: {
let sum = 0; let sum = 0;
for (let i = 0, n = spacesCount - 1; i < n;) { for (let i = 0, n = spacesCount - 1; i < n;) {
let bone = bones[i]; const bone = bones[i];
let setupLength = bone.bone.data.length; const setupLength = bone.bone.data.length;
if (setupLength < PathConstraint.epsilon) { if (setupLength < PathConstraint.epsilon) {
if (scale) lengths[i] = 0; if (scale) lengths[i] = 0;
spaces[++i] = spacing; spaces[++i] = spacing;
} else { } else {
let x = setupLength * bone.a, y = setupLength * bone.c; const x = setupLength * bone.a, y = setupLength * bone.c;
let length = Math.sqrt(x * x + y * y); const length = Math.sqrt(x * x + y * y);
if (scale) lengths[i] = length; if (scale) lengths[i] = length;
spaces[++i] = length; spaces[++i] = length;
sum += length; sum += length;
@ -130,42 +130,44 @@ export class PathConstraint extends Constraint<PathConstraint, PathConstraintDat
spaces[i] *= sum; spaces[i] *= sum;
} }
break; break;
default: }
let lengthSpacing = data.spacingMode == SpacingMode.Length; default: {
const lengthSpacing = data.spacingMode === SpacingMode.Length;
for (let i = 0, n = spacesCount - 1; i < n;) { for (let i = 0, n = spacesCount - 1; i < n;) {
let bone = bones[i]; const bone = bones[i];
let setupLength = bone.bone.data.length; const setupLength = bone.bone.data.length;
if (setupLength < PathConstraint.epsilon) { if (setupLength < PathConstraint.epsilon) {
if (scale) lengths[i] = 0; if (scale) lengths[i] = 0;
spaces[++i] = spacing; spaces[++i] = spacing;
} else { } else {
let x = setupLength * bone.a, y = setupLength * bone.c; const x = setupLength * bone.a, y = setupLength * bone.c;
let length = Math.sqrt(x * x + y * y); const length = Math.sqrt(x * x + y * y);
if (scale) lengths[i] = length; if (scale) lengths[i] = length;
spaces[++i] = (lengthSpacing ? Math.max(0, setupLength + spacing) : spacing) * length / setupLength; spaces[++i] = (lengthSpacing ? Math.max(0, setupLength + spacing) : spacing) * length / setupLength;
} }
} }
}
} }
let positions = this.computeWorldPositions(skeleton, attachment, spacesCount, tangents); const positions = this.computeWorldPositions(skeleton, attachment, spacesCount, tangents);
let boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation; let boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation;
let tip = false; let tip = false;
if (offsetRotation == 0) if (offsetRotation === 0)
tip = data.rotateMode == RotateMode.Chain; tip = data.rotateMode === RotateMode.Chain;
else { else {
tip = false; tip = false;
let bone = this.slot.bone.applied; const bone = this.slot.bone.applied;
offsetRotation *= bone.a * bone.d - bone.b * bone.c > 0 ? MathUtils.degRad : -MathUtils.degRad; offsetRotation *= bone.a * bone.d - bone.b * bone.c > 0 ? MathUtils.degRad : -MathUtils.degRad;
} }
for (let i = 0, ip = 3, u = skeleton._update; i < boneCount; i++, ip += 3) { for (let i = 0, ip = 3, u = skeleton._update; i < boneCount; i++, ip += 3) {
let bone = bones[i]; const bone = bones[i];
bone.worldX += (boneX - bone.worldX) * mixX; bone.worldX += (boneX - bone.worldX) * mixX;
bone.worldY += (boneY - bone.worldY) * mixY; bone.worldY += (boneY - bone.worldY) * mixY;
let x = positions[ip], y = positions[ip + 1], dx = x - boneX, dy = y - boneY; const x = positions[ip], y = positions[ip + 1], dx = x - boneX, dy = y - boneY;
if (scale) { if (scale) {
let length = lengths[i]; const length = lengths[i];
if (length != 0) { if (length !== 0) {
let s = (Math.sqrt(dx * dx + dy * dy) / length - 1) * mixRotate + 1; const s = (Math.sqrt(dx * dx + dy * dy) / length - 1) * mixRotate + 1;
bone.a *= s; bone.a *= s;
bone.c *= s; bone.c *= s;
} }
@ -176,7 +178,7 @@ export class PathConstraint extends Constraint<PathConstraint, PathConstraintDat
let a = bone.a, b = bone.b, c = bone.c, d = bone.d, r = 0, cos = 0, sin = 0; let a = bone.a, b = bone.b, c = bone.c, d = bone.d, r = 0, cos = 0, sin = 0;
if (tangents) if (tangents)
r = positions[ip - 1]; r = positions[ip - 1];
else if (spaces[i + 1] == 0) else if (spaces[i + 1] === 0)
r = positions[ip + 2]; r = positions[ip + 2];
else else
r = Math.atan2(dy, dx); r = Math.atan2(dy, dx);
@ -184,7 +186,7 @@ export class PathConstraint extends Constraint<PathConstraint, PathConstraintDat
if (tip) { if (tip) {
cos = Math.cos(r); cos = Math.cos(r);
sin = Math.sin(r); sin = Math.sin(r);
let length = bone.bone.data.length; const length = bone.bone.data.length;
boneX += (length * (cos * a - sin * c) - dx) * mixRotate; boneX += (length * (cos * a - sin * c) - dx) * mixRotate;
boneY += (length * (sin * a + cos * c) - dy) * mixRotate; boneY += (length * (sin * a + cos * c) - dy) * mixRotate;
} else { } else {
@ -207,19 +209,19 @@ export class PathConstraint extends Constraint<PathConstraint, PathConstraintDat
} }
computeWorldPositions (skeleton: Skeleton, path: PathAttachment, spacesCount: number, tangents: boolean) { computeWorldPositions (skeleton: Skeleton, path: PathAttachment, spacesCount: number, tangents: boolean) {
let slot = this.slot; const slot = this.slot;
let position = this.applied.position; let position = this.applied.position;
let spaces = this.spaces, out = Utils.setArraySize(this.positions, spacesCount * 3 + 2), world: Array<number> = this.world; let spaces = this.spaces, out = Utils.setArraySize(this.positions, spacesCount * 3 + 2), world: Array<number> = this.world;
let closed = path.closed; const closed = path.closed;
let verticesLength = path.worldVerticesLength, curveCount = verticesLength / 6, prevCurve = PathConstraint.NONE; let verticesLength = path.worldVerticesLength, curveCount = verticesLength / 6, prevCurve = PathConstraint.NONE;
if (!path.constantSpeed) { if (!path.constantSpeed) {
let lengths = path.lengths; const lengths = path.lengths;
curveCount -= closed ? 1 : 2; curveCount -= closed ? 1 : 2;
let pathLength = lengths[curveCount]; const pathLength = lengths[curveCount];
if (this.data.positionMode == PositionMode.Percent) position *= pathLength; if (this.data.positionMode === PositionMode.Percent) position *= pathLength;
let multiplier; let multiplier: number;
switch (this.data.spacingMode) { switch (this.data.spacingMode) {
case SpacingMode.Percent: multiplier = pathLength; break; case SpacingMode.Percent: multiplier = pathLength; break;
case SpacingMode.Proportional: multiplier = pathLength / spacesCount; break; case SpacingMode.Proportional: multiplier = pathLength / spacesCount; break;
@ -228,7 +230,7 @@ export class PathConstraint extends Constraint<PathConstraint, PathConstraintDat
world = Utils.setArraySize(this.world, 8); world = Utils.setArraySize(this.world, 8);
for (let i = 0, o = 0, curve = 0; i < spacesCount; i++, o += 3) { for (let i = 0, o = 0, curve = 0; i < spacesCount; i++, o += 3) {
let space = spaces[i] * multiplier; const space = spaces[i] * multiplier;
position += space; position += space;
let p = position; let p = position;
@ -237,14 +239,14 @@ export class PathConstraint extends Constraint<PathConstraint, PathConstraintDat
if (p < 0) p += pathLength; if (p < 0) p += pathLength;
curve = 0; curve = 0;
} else if (p < 0) { } else if (p < 0) {
if (prevCurve != PathConstraint.BEFORE) { if (prevCurve !== PathConstraint.BEFORE) {
prevCurve = PathConstraint.BEFORE; prevCurve = PathConstraint.BEFORE;
path.computeWorldVertices(skeleton, slot, 2, 4, world, 0, 2); path.computeWorldVertices(skeleton, slot, 2, 4, world, 0, 2);
} }
this.addBeforePosition(p, world, 0, out, o); this.addBeforePosition(p, world, 0, out, o);
continue; continue;
} else if (p > pathLength) { } else if (p > pathLength) {
if (prevCurve != PathConstraint.AFTER) { if (prevCurve !== PathConstraint.AFTER) {
prevCurve = PathConstraint.AFTER; prevCurve = PathConstraint.AFTER;
path.computeWorldVertices(skeleton, slot, verticesLength - 6, 4, world, 0, 2); path.computeWorldVertices(skeleton, slot, verticesLength - 6, 4, world, 0, 2);
} }
@ -254,26 +256,26 @@ export class PathConstraint extends Constraint<PathConstraint, PathConstraintDat
// Determine curve containing position. // Determine curve containing position.
for (; ; curve++) { for (; ; curve++) {
let length = lengths[curve]; const length = lengths[curve];
if (p > length) continue; if (p > length) continue;
if (curve == 0) if (curve === 0)
p /= length; p /= length;
else { else {
let prev = lengths[curve - 1]; const prev = lengths[curve - 1];
p = (p - prev) / (length - prev); p = (p - prev) / (length - prev);
} }
break; break;
} }
if (curve != prevCurve) { if (curve !== prevCurve) {
prevCurve = curve; prevCurve = curve;
if (closed && curve == curveCount) { if (closed && curve === curveCount) {
path.computeWorldVertices(skeleton, slot, verticesLength - 4, 4, world, 0, 2); path.computeWorldVertices(skeleton, slot, verticesLength - 4, 4, world, 0, 2);
path.computeWorldVertices(skeleton, slot, 0, 4, world, 4, 2); path.computeWorldVertices(skeleton, slot, 0, 4, world, 4, 2);
} else } else
path.computeWorldVertices(skeleton, slot, curve * 6 + 2, 8, world, 0, 2); path.computeWorldVertices(skeleton, slot, curve * 6 + 2, 8, world, 0, 2);
} }
this.addCurvePosition(p, world[0], world[1], world[2], world[3], world[4], world[5], world[6], world[7], out, o, this.addCurvePosition(p, world[0], world[1], world[2], world[3], world[4], world[5], world[6], world[7], out, o,
tangents || (i > 0 && space == 0)); tangents || (i > 0 && space === 0));
} }
return out; return out;
} }
@ -294,7 +296,7 @@ export class PathConstraint extends Constraint<PathConstraint, PathConstraintDat
} }
// Curve lengths. // Curve lengths.
let curves = Utils.setArraySize(this.curves, curveCount); const curves = Utils.setArraySize(this.curves, curveCount);
let pathLength = 0; let pathLength = 0;
let x1 = world[0], y1 = world[1], cx1 = 0, cy1 = 0, cx2 = 0, cy2 = 0, x2 = 0, y2 = 0; let x1 = world[0], y1 = world[1], cx1 = 0, cy1 = 0, cx2 = 0, cy2 = 0, x2 = 0, y2 = 0;
let tmpx = 0, tmpy = 0, dddfx = 0, dddfy = 0, ddfx = 0, ddfy = 0, dfx = 0, dfy = 0; let tmpx = 0, tmpy = 0, dddfx = 0, dddfy = 0, ddfx = 0, ddfy = 0, dfx = 0, dfy = 0;
@ -330,19 +332,19 @@ export class PathConstraint extends Constraint<PathConstraint, PathConstraintDat
y1 = y2; y1 = y2;
} }
if (this.data.positionMode == PositionMode.Percent) position *= pathLength; if (this.data.positionMode === PositionMode.Percent) position *= pathLength;
let multiplier; let multiplier: number;
switch (this.data.spacingMode) { switch (this.data.spacingMode) {
case SpacingMode.Percent: multiplier = pathLength; break; case SpacingMode.Percent: multiplier = pathLength; break;
case SpacingMode.Proportional: multiplier = pathLength / spacesCount; break; case SpacingMode.Proportional: multiplier = pathLength / spacesCount; break;
default: multiplier = 1; default: multiplier = 1;
} }
let segments = this.segments; const segments = this.segments;
let curveLength = 0; let curveLength = 0;
for (let i = 0, o = 0, curve = 0, segment = 0; i < spacesCount; i++, o += 3) { for (let i = 0, o = 0, curve = 0, segment = 0; i < spacesCount; i++, o += 3) {
let space = spaces[i] * multiplier; const space = spaces[i] * multiplier;
position += space; position += space;
let p = position; let p = position;
@ -361,19 +363,19 @@ export class PathConstraint extends Constraint<PathConstraint, PathConstraintDat
// Determine curve containing position. // Determine curve containing position.
for (; ; curve++) { for (; ; curve++) {
let length = curves[curve]; const length = curves[curve];
if (p > length) continue; if (p > length) continue;
if (curve == 0) if (curve === 0)
p /= length; p /= length;
else { else {
let prev = curves[curve - 1]; const prev = curves[curve - 1];
p = (p - prev) / (length - prev); p = (p - prev) / (length - prev);
} }
break; break;
} }
// Curve segment lengths. // Curve segment lengths.
if (curve != prevCurve) { if (curve !== prevCurve) {
prevCurve = curve; prevCurve = curve;
let ii = curve * 6; let ii = curve * 6;
x1 = world[ii]; x1 = world[ii];
@ -416,30 +418,30 @@ export class PathConstraint extends Constraint<PathConstraint, PathConstraintDat
// Weight by segment length. // Weight by segment length.
p *= curveLength; p *= curveLength;
for (; ; segment++) { for (; ; segment++) {
let length = segments[segment]; const length = segments[segment];
if (p > length) continue; if (p > length) continue;
if (segment == 0) if (segment === 0)
p /= length; p /= length;
else { else {
let prev = segments[segment - 1]; const prev = segments[segment - 1];
p = segment + (p - prev) / (length - prev); p = segment + (p - prev) / (length - prev);
} }
break; break;
} }
this.addCurvePosition(p * 0.1, x1, y1, cx1, cy1, cx2, cy2, x2, y2, out, o, tangents || (i > 0 && space == 0)); this.addCurvePosition(p * 0.1, x1, y1, cx1, cy1, cx2, cy2, x2, y2, out, o, tangents || (i > 0 && space === 0));
} }
return out; return out;
} }
addBeforePosition (p: number, temp: Array<number>, i: number, out: Array<number>, o: number) { addBeforePosition (p: number, temp: Array<number>, i: number, out: Array<number>, o: number) {
let x1 = temp[i], y1 = temp[i + 1], dx = temp[i + 2] - x1, dy = temp[i + 3] - y1, r = Math.atan2(dy, dx); const x1 = temp[i], y1 = temp[i + 1], dx = temp[i + 2] - x1, dy = temp[i + 3] - y1, r = Math.atan2(dy, dx);
out[o] = x1 + p * Math.cos(r); out[o] = x1 + p * Math.cos(r);
out[o + 1] = y1 + p * Math.sin(r); out[o + 1] = y1 + p * Math.sin(r);
out[o + 2] = r; out[o + 2] = r;
} }
addAfterPosition (p: number, temp: Array<number>, i: number, out: Array<number>, o: number) { addAfterPosition (p: number, temp: Array<number>, i: number, out: Array<number>, o: number) {
let x1 = temp[i + 2], y1 = temp[i + 3], dx = x1 - temp[i], dy = y1 - temp[i + 1], r = Math.atan2(dy, dx); const x1 = temp[i + 2], y1 = temp[i + 3], dx = x1 - temp[i], dy = y1 - temp[i + 1], r = Math.atan2(dy, dx);
out[o] = x1 + p * Math.cos(r); out[o] = x1 + p * Math.cos(r);
out[o + 1] = y1 + p * Math.sin(r); out[o + 1] = y1 + p * Math.sin(r);
out[o + 2] = r; out[o + 2] = r;
@ -447,15 +449,15 @@ export class PathConstraint extends Constraint<PathConstraint, PathConstraintDat
addCurvePosition (p: number, x1: number, y1: number, cx1: number, cy1: number, cx2: number, cy2: number, x2: number, y2: number, addCurvePosition (p: number, x1: number, y1: number, cx1: number, cy1: number, cx2: number, cy2: number, x2: number, y2: number,
out: Array<number>, o: number, tangents: boolean) { out: Array<number>, o: number, tangents: boolean) {
if (p == 0 || isNaN(p)) { if (p === 0 || Number.isNaN(p)) {
out[o] = x1; out[o] = x1;
out[o + 1] = y1; out[o + 1] = y1;
out[o + 2] = Math.atan2(cy1 - y1, cx1 - x1); out[o + 2] = Math.atan2(cy1 - y1, cx1 - x1);
return; return;
} }
let tt = p * p, ttt = tt * p, u = 1 - p, uu = u * u, uuu = uu * u; const tt = p * p, ttt = tt * p, u = 1 - p, uu = u * u, uuu = uu * u;
let ut = u * p, ut3 = ut * 3, uut3 = u * ut3, utt3 = ut3 * p; const ut = u * p, ut3 = ut * 3, uut3 = u * ut3, utt3 = ut3 * p;
let x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt, y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt; const x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt, y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt;
out[o] = x; out[o] = x;
out[o + 1] = y; out[o + 1] = y;
if (tangents) { if (tangents) {
@ -470,7 +472,7 @@ export class PathConstraint extends Constraint<PathConstraint, PathConstraintDat
const slotIndex = this.slot.data.index; const slotIndex = this.slot.data.index;
const slotBone = this.slot.bone; const slotBone = this.slot.bone;
if (skeleton.skin != null) this.sortPathSlot(skeleton, skeleton.skin, slotIndex, slotBone); if (skeleton.skin != null) this.sortPathSlot(skeleton, skeleton.skin, slotIndex, slotBone);
if (skeleton.data.defaultSkin != null && skeleton.data.defaultSkin != skeleton.skin) if (skeleton.data.defaultSkin != null && skeleton.data.defaultSkin !== skeleton.skin)
this.sortPathSlot(skeleton, skeleton.data.defaultSkin, slotIndex, slotBone); this.sortPathSlot(skeleton, skeleton.data.defaultSkin, slotIndex, slotBone);
this.sortPath(skeleton, this.slot.pose.attachment, slotBone); this.sortPath(skeleton, this.slot.pose.attachment, slotBone);
const bones = this.bones; const bones = this.bones;
@ -491,7 +493,7 @@ export class PathConstraint extends Constraint<PathConstraint, PathConstraintDat
const entries = skin.getAttachments(); const entries = skin.getAttachments();
for (let i = 0, n = entries.length; i < n; i++) { for (let i = 0, n = entries.length; i < n; i++) {
const entry = entries[i]; const entry = entries[i];
if (entry.slotIndex == slotIndex) this.sortPath(skeleton, entry.attachment, slotBone); if (entry.slotIndex === slotIndex) this.sortPath(skeleton, entry.attachment, slotBone);
} }
} }

View File

@ -27,12 +27,12 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { BoneData } from "./BoneData.js"; import type { BoneData } from "./BoneData.js";
import { ConstraintData } from "./ConstraintData.js"; import { ConstraintData } from "./ConstraintData.js";
import { PathConstraint } from "./PathConstraint.js"; import { PathConstraint } from "./PathConstraint.js";
import { PathConstraintPose } from "./PathConstraintPose.js"; import { PathConstraintPose } from "./PathConstraintPose.js";
import { Skeleton } from "./Skeleton.js"; import type { Skeleton } from "./Skeleton.js";
import { SlotData } from "./SlotData.js"; import type { SlotData } from "./SlotData.js";
/** Stores the setup pose for a {@link PathConstraint}. /** Stores the setup pose for a {@link PathConstraint}.
@ -40,7 +40,7 @@ import { SlotData } from "./SlotData.js";
* See [path constraints](http://esotericsoftware.com/spine-path-constraints) in the Spine User Guide. */ * See [path constraints](http://esotericsoftware.com/spine-path-constraints) in the Spine User Guide. */
export class PathConstraintData extends ConstraintData<PathConstraint, PathConstraintPose> { export class PathConstraintData extends ConstraintData<PathConstraint, PathConstraintPose> {
/** The bones that will be modified by this path constraint. */ /** The bones that will be modified by this path constraint. */
bones = new Array<BoneData>(); bones = [] as BoneData[];
/** The slot whose path attachment will be used to constrained the bones. */ /** The slot whose path attachment will be used to constrained the bones. */
public set slot (slotData: SlotData) { this._slot = slotData; } public set slot (slotData: SlotData) { this._slot = slotData; }

View File

@ -27,7 +27,7 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { Pose } from "./Pose" import type { Pose } from "./Pose"
/** Stores a pose for a path constraint. */ /** Stores a pose for a path constraint. */
export class PathConstraintPose implements Pose<PathConstraintPose> { export class PathConstraintPose implements Pose<PathConstraintPose> {

View File

@ -27,11 +27,11 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { BoneData } from "./BoneData.js"; import type { BoneData } from "./BoneData.js";
import { ConstraintData } from "./ConstraintData.js"; import { ConstraintData } from "./ConstraintData.js";
import { PhysicsConstraint } from "./PhysicsConstraint.js"; import { PhysicsConstraint } from "./PhysicsConstraint.js";
import { PhysicsConstraintPose } from "./PhysicsConstraintPose.js"; import { PhysicsConstraintPose } from "./PhysicsConstraintPose.js";
import { Skeleton } from "./Skeleton.js"; import type { Skeleton } from "./Skeleton.js";
/** Stores the setup pose for a {@link PhysicsConstraint}. /** Stores the setup pose for a {@link PhysicsConstraint}.

View File

@ -27,7 +27,7 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { Pose } from "./Pose" import type { Pose } from "./Pose"
/** Stores a pose for a physics constraint. */ /** Stores a pose for a physics constraint. */
export class PhysicsConstraintPose implements Pose<PhysicsConstraintPose> { export class PhysicsConstraintPose implements Pose<PhysicsConstraintPose> {

View File

@ -27,12 +27,12 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { Pose } from "./Pose.js"; import type { Pose } from "./Pose.js";
import { PosedData } from "./PosedData.js"; import type { PosedData } from "./PosedData.js";
export abstract class Posed< export abstract class Posed<
D extends PosedData<P>, D extends PosedData<P>,
P extends Pose<any>, P extends Pose<P>,
A extends P> { A extends P> {
/** The constraint's setup pose data. */ /** The constraint's setup pose data. */

View File

@ -27,15 +27,15 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { Pose } from "./Pose.js"; import type { Pose } from "./Pose.js";
import { Posed } from "./Posed.js"; import { Posed } from "./Posed.js";
import { PosedData } from "./PosedData.js"; import type { PosedData } from "./PosedData.js";
import type { Skeleton } from "./Skeleton"; import type { Skeleton } from "./Skeleton";
export abstract class PosedActive< export abstract class PosedActive<
D extends PosedData<P>, D extends PosedData<P>,
P extends Pose<any>, P extends Pose<P>,
A extends P> A extends P>
extends Posed<D, P, A> { extends Posed<D, P, A> {

View File

@ -27,10 +27,10 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { Pose } from "./Pose.js"; import type { Pose } from "./Pose.js";
/** The base class for all constrained datas. */ /** The base class for all constrained datas. */
export abstract class PosedData<P extends Pose<any>> { export abstract class PosedData<P extends Pose<P>> {
/** The constraint's name, which is unique across all constraints in the skeleton of the same type. */ /** The constraint's name, which is unique across all constraints in the skeleton of the same type. */
readonly name: string; readonly name: string;

View File

@ -27,20 +27,20 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { Attachment } from "./attachments/Attachment.js"; import type { Attachment } from "./attachments/Attachment.js";
import { ClippingAttachment } from "./attachments/ClippingAttachment.js"; import { ClippingAttachment } from "./attachments/ClippingAttachment.js";
import { MeshAttachment } from "./attachments/MeshAttachment.js"; import { MeshAttachment } from "./attachments/MeshAttachment.js";
import { RegionAttachment } from "./attachments/RegionAttachment.js"; import { RegionAttachment } from "./attachments/RegionAttachment.js";
import { Bone } from "./Bone.js"; import { Bone } from "./Bone.js";
import { Constraint } from "./Constraint.js"; import type { Constraint } from "./Constraint.js";
import { Physics } from "./Physics.js"; import type { Physics } from "./Physics.js";
import { PhysicsConstraint } from "./PhysicsConstraint.js"; import { PhysicsConstraint } from "./PhysicsConstraint.js";
import { Posed } from "./Posed.js"; import type { Posed } from "./Posed.js";
import { SkeletonClipping } from "./SkeletonClipping.js"; import type { SkeletonClipping } from "./SkeletonClipping.js";
import { SkeletonData } from "./SkeletonData.js"; import type { SkeletonData } from "./SkeletonData.js";
import { Skin } from "./Skin.js"; import type { Skin } from "./Skin.js";
import { Slot } from "./Slot.js"; import { Slot } from "./Slot.js";
import { Color, NumberArrayLike, Utils, Vector2 } from "./Utils.js"; import { Color, type NumberArrayLike, Utils, Vector2 } from "./Utils.js";
/** Stores the current pose for a skeleton. /** Stores the current pose for a skeleton.
* *
@ -65,15 +65,18 @@ export class Skeleton {
drawOrder: Array<Slot>; drawOrder: Array<Slot>;
/** The skeleton's constraints. */ /** The skeleton's constraints. */
// biome-ignore lint/suspicious/noExplicitAny: reference runtime does not restrict to specific types
readonly constraints: Array<Constraint<any, any, any>>; readonly constraints: Array<Constraint<any, any, any>>;
/** The skeleton's physics constraints. */ /** The skeleton's physics constraints. */
readonly physics: Array<PhysicsConstraint>; readonly physics: 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()}. */
readonly _updateCache = new Array(); // biome-ignore lint/suspicious/noExplicitAny: reference runtime does not restrict to specific types
readonly _updateCache = [] as any[];
readonly resetCache: Array<Posed<any, any, any>> = new Array(); // biome-ignore lint/suspicious/noExplicitAny: reference runtime does not restrict to specific types
readonly resetCache: Array<Posed<any, any, any>> = [];
/** The skeleton's current skin. May be null. */ /** The skeleton's current skin. May be null. */
skin: Skin | null = null; skin: Skin | null = null;
@ -125,30 +128,31 @@ export class Skeleton {
if (!data) throw new Error("data cannot be null."); if (!data) throw new Error("data cannot be null.");
this.data = data; this.data = data;
this.bones = new Array<Bone>(); this.bones = [] as Bone[];
for (let i = 0; i < data.bones.length; i++) { for (let i = 0; i < data.bones.length; i++) {
let boneData = data.bones[i]; const boneData = data.bones[i];
let bone: Bone; let bone: Bone;
if (!boneData.parent) if (!boneData.parent)
bone = new Bone(boneData, null); bone = new Bone(boneData, null);
else { else {
let parent = this.bones[boneData.parent.index]; const parent = this.bones[boneData.parent.index];
bone = new Bone(boneData, parent); bone = new Bone(boneData, parent);
parent.children.push(bone); parent.children.push(bone);
} }
this.bones.push(bone); this.bones.push(bone);
} }
this.slots = new Array<Slot>(); this.slots = [] as Slot[];
this.drawOrder = new Array<Slot>(); this.drawOrder = [] as Slot[];
for (const slotData of this.data.slots) { for (const slotData of this.data.slots) {
let slot = new Slot(slotData, this); const slot = new Slot(slotData, this);
this.slots.push(slot); this.slots.push(slot);
this.drawOrder.push(slot); this.drawOrder.push(slot);
} }
this.physics = new Array<PhysicsConstraint>(); this.physics = [] as PhysicsConstraint[];
this.constraints = new Array<Constraint<any, any, any>>(); // biome-ignore lint/suspicious/noExplicitAny: reference runtime does not restrict to specific types
this.constraints = [] as Constraint<any, any, any>[];
for (const constraintData of this.data.constraints) { for (const constraintData of this.data.constraints) {
const constraint = constraintData.create(this); const constraint = constraintData.create(this);
if (constraint instanceof PhysicsConstraint) this.physics.push(constraint); if (constraint instanceof PhysicsConstraint) this.physics.push(constraint);
@ -166,20 +170,20 @@ export class Skeleton {
this._updateCache.length = 0; this._updateCache.length = 0;
this.resetCache.length = 0; this.resetCache.length = 0;
let slots = this.slots; const slots = this.slots;
for (let i = 0, n = slots.length; i < n; i++) for (let i = 0, n = slots.length; i < n; i++)
slots[i].usePose(); slots[i].usePose();
let bones = this.bones; const bones = this.bones;
const boneCount = bones.length; const boneCount = bones.length;
for (let i = 0, n = boneCount; i < n; i++) { for (let i = 0, n = boneCount; i < n; i++) {
let bone = bones[i]; const bone = bones[i];
bone.sorted = bone.data.skinRequired; bone.sorted = bone.data.skinRequired;
bone.active = !bone.sorted; bone.active = !bone.sorted;
bone.usePose(); bone.usePose();
} }
if (this.skin) { if (this.skin) {
let skinBones = this.skin.bones; const skinBones = this.skin.bones;
for (let i = 0, n = this.skin.bones.length; i < n; i++) { for (let i = 0, n = this.skin.bones.length; i < n; i++) {
let bone: Bone | null = this.bones[skinBones[i].index]; let bone: Bone | null = this.bones[skinBones[i].index];
do { do {
@ -190,13 +194,14 @@ export class Skeleton {
} }
} }
let constraints = this.constraints; const constraints = this.constraints;
let n = this.constraints.length; let n = this.constraints.length;
for (let i = 0; i < n; i++) for (let i = 0; i < n; i++)
constraints[i].usePose(); constraints[i].usePose();
for (let i = 0; i < n; i++) { for (let i = 0; i < n; i++) {
const constraint = constraints[i]; const constraint = constraints[i];
constraint.active = constraint.isSourceActive() constraint.active = constraint.isSourceActive()
// biome-ignore lint/complexity/useOptionalChain: changing to this might return undefined
&& (!constraint.data.skinRequired || (this.skin != null && this.skin.constraints.includes(constraint.data))); && (!constraint.data.skinRequired || (this.skin != null && this.skin.constraints.includes(constraint.data)));
if (constraint.active) constraint.sort(this); if (constraint.active) constraint.sort(this);
} }
@ -212,6 +217,7 @@ export class Skeleton {
} }
// biome-ignore lint/suspicious/noExplicitAny: reference runtime does not restrict to specific types
constrained (object: Posed<any, any, any>) { constrained (object: Posed<any, any, any>) {
if (object.pose === object.applied) { if (object.pose === object.applied) {
object.useConstrained(); object.useConstrained();
@ -221,7 +227,7 @@ export class Skeleton {
sortBone (bone: Bone) { sortBone (bone: Bone) {
if (bone.sorted || !bone.active) return; if (bone.sorted || !bone.active) return;
let parent = bone.parent; const parent = bone.parent;
if (parent) this.sortBone(parent); if (parent) this.sortBone(parent);
bone.sorted = true; bone.sorted = true;
this._updateCache.push(bone); this._updateCache.push(bone);
@ -229,7 +235,7 @@ export class Skeleton {
sortReset (bones: Array<Bone>) { sortReset (bones: Array<Bone>) {
for (let i = 0, n = bones.length; i < n; i++) { for (let i = 0, n = bones.length; i < n; i++) {
let bone = bones[i]; const bone = bones[i];
if (bone.active) { if (bone.active) {
if (bone.sorted) this.sortReset(bone.children); if (bone.sorted) this.sortReset(bone.children);
bone.sorted = false; bone.sorted = false;
@ -272,7 +278,7 @@ export class Skeleton {
/** Sets the slots and draw order to their setup pose values. */ /** Sets the slots and draw order to their setup pose values. */
setupPoseSlots () { setupPoseSlots () {
let slots = this.slots; const slots = this.slots;
Utils.arrayCopy(slots, 0, this.drawOrder, 0, slots.length); Utils.arrayCopy(slots, 0, this.drawOrder, 0, slots.length);
for (let i = 0, n = slots.length; i < n; i++) for (let i = 0, n = slots.length; i < n; i++)
slots[i].setupPose(); slots[i].setupPose();
@ -280,7 +286,7 @@ export class Skeleton {
/** Returns the root bone, or null if the skeleton has no bones. */ /** Returns the root bone, or null if the skeleton has no bones. */
getRootBone () { getRootBone () {
if (this.bones.length == 0) return null; if (this.bones.length === 0) return null;
return this.bones[0]; return this.bones[0];
} }
@ -288,9 +294,9 @@ export class Skeleton {
* repeatedly. */ * repeatedly. */
findBone (boneName: string) { findBone (boneName: string) {
if (!boneName) throw new Error("boneName cannot be null."); if (!boneName) throw new Error("boneName cannot be null.");
let bones = this.bones; const bones = this.bones;
for (let i = 0, n = bones.length; i < n; i++) for (let i = 0, n = bones.length; i < n; i++)
if (bones[i].data.name == boneName) return bones[i]; if (bones[i].data.name === boneName) return bones[i];
return null; return null;
} }
@ -298,9 +304,9 @@ export class Skeleton {
* repeatedly. */ * repeatedly. */
findSlot (slotName: string) { findSlot (slotName: string) {
if (!slotName) throw new Error("slotName cannot be null."); if (!slotName) throw new Error("slotName cannot be null.");
let slots = this.slots; const slots = this.slots;
for (let i = 0, n = slots.length; i < n; i++) for (let i = 0, n = slots.length; i < n; i++)
if (slots[i].data.name == slotName) return slots[i]; if (slots[i].data.name === slotName) return slots[i];
return null; return null;
} }
@ -328,23 +334,23 @@ export class Skeleton {
}; };
private setSkinByName (skinName: string) { private setSkinByName (skinName: string) {
let skin = this.data.findSkin(skinName); const skin = this.data.findSkin(skinName);
if (!skin) throw new Error("Skin not found: " + skinName); if (!skin) throw new Error(`Skin not found: ${skinName}`);
this.setSkin(skin); this.setSkin(skin);
} }
private setSkinBySkin (newSkin: Skin | null) { private setSkinBySkin (newSkin: Skin | null) {
if (newSkin == this.skin) return; if (newSkin === this.skin) return;
if (newSkin) { if (newSkin) {
if (this.skin) if (this.skin)
newSkin.attachAll(this, this.skin); newSkin.attachAll(this, this.skin);
else { else {
let slots = this.slots; const slots = this.slots;
for (let i = 0, n = slots.length; i < n; i++) { for (let i = 0, n = slots.length; i < n; i++) {
let slot = slots[i]; const slot = slots[i];
let name = slot.data.attachmentName; const name = slot.data.attachmentName;
if (name) { if (name) {
let attachment = newSkin.getAttachment(i, name); const attachment = newSkin.getAttachment(i, name);
if (attachment) slot.pose.setAttachment(attachment); if (attachment) slot.pose.setAttachment(attachment);
} }
} }
@ -378,7 +384,7 @@ export class Skeleton {
* See {@link #getAttachment()}. * See {@link #getAttachment()}.
* @returns May be null. */ * @returns May be null. */
private getAttachmentByName (slotName: string, attachmentName: string): Attachment | null { private getAttachmentByName (slotName: string, attachmentName: string): Attachment | null {
let slot = this.data.findSlot(slotName); const slot = this.data.findSlot(slotName);
if (!slot) throw new Error(`Can't find slot with name ${slotName}`); if (!slot) throw new Error(`Can't find slot with name ${slotName}`);
return this.getAttachment(slot.index, attachmentName); return this.getAttachment(slot.index, attachmentName);
} }
@ -391,7 +397,7 @@ export class Skeleton {
private getAttachmentByIndex (slotIndex: number, attachmentName: string): Attachment | null { private getAttachmentByIndex (slotIndex: number, attachmentName: string): Attachment | null {
if (!attachmentName) throw new Error("attachmentName cannot be null."); if (!attachmentName) throw new Error("attachmentName cannot be null.");
if (this.skin) { if (this.skin) {
let attachment = this.skin.getAttachment(slotIndex, attachmentName); const attachment = this.skin.getAttachment(slotIndex, attachmentName);
if (attachment) return attachment; if (attachment) return attachment;
} }
if (this.data.defaultSkin) return this.data.defaultSkin.getAttachment(slotIndex, attachmentName); if (this.data.defaultSkin) return this.data.defaultSkin.getAttachment(slotIndex, attachmentName);
@ -404,16 +410,17 @@ export class Skeleton {
setAttachment (slotName: string, attachmentName: string) { setAttachment (slotName: string, attachmentName: string) {
if (!slotName) throw new Error("slotName cannot be null."); if (!slotName) throw new Error("slotName cannot be null.");
const slot = this.findSlot(slotName); const slot = this.findSlot(slotName);
if (!slot) throw new Error("Slot not found: " + slotName); if (!slot) throw new Error(`Slot not found: ${slotName}`);
let attachment: Attachment | null = null; let attachment: Attachment | null = null;
if (attachmentName) { if (attachmentName) {
attachment = this.getAttachment(slot.data.index, attachmentName); attachment = this.getAttachment(slot.data.index, attachmentName);
if (!attachment) if (!attachment)
throw new Error("Attachment not found: " + attachmentName + ", for slot: " + slotName); throw new Error(`Attachment not found: ${attachmentName}, for slot: ${slotName}`);
} }
slot.pose.setAttachment(attachment); slot.pose.setAttachment(attachment);
} }
// biome-ignore lint/suspicious/noExplicitAny: reference runtime does not restrict to specific types
findConstraint<T extends Constraint<any, any, any>> (constraintName: string, type: new () => T): T | null { findConstraint<T extends Constraint<any, any, any>> (constraintName: string, type: new () => T): T | null {
if (constraintName == null) throw new Error("constraintName cannot be null."); if (constraintName == null) throw new Error("constraintName cannot be null.");
if (type == null) throw new Error("type cannot be null."); if (type == null) throw new Error("type cannot be null.");
@ -428,8 +435,8 @@ export class Skeleton {
/** 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 }`.
* Note that this method will create temporary objects which can add to garbage collection pressure. Use `getBounds()` if garbage collection is a concern. */ * Note that this method will create temporary objects which can add to garbage collection pressure. Use `getBounds()` if garbage collection is a concern. */
getBoundsRect (clipper?: SkeletonClipping) { getBoundsRect (clipper?: SkeletonClipping) {
let offset = new Vector2(); const offset = new Vector2();
let size = new Vector2(); const size = new Vector2();
this.getBounds(offset, size, undefined, clipper); this.getBounds(offset, size, undefined, clipper);
return { x: offset.x, y: offset.y, width: size.x, height: size.y }; return { x: offset.x, y: offset.y, width: size.x, height: size.y };
} }
@ -442,15 +449,15 @@ export class Skeleton {
getBounds (offset: Vector2, size: Vector2, temp: Array<number> = new Array<number>(2), clipper: SkeletonClipping | null = null) { getBounds (offset: Vector2, size: Vector2, temp: Array<number> = new Array<number>(2), clipper: SkeletonClipping | null = null) {
if (!offset) throw new Error("offset cannot be null."); if (!offset) throw new Error("offset cannot be null.");
if (!size) throw new Error("size cannot be null."); if (!size) throw new Error("size cannot be null.");
let drawOrder = this.drawOrder; const drawOrder = this.drawOrder;
let minX = Number.POSITIVE_INFINITY, minY = Number.POSITIVE_INFINITY, maxX = Number.NEGATIVE_INFINITY, maxY = Number.NEGATIVE_INFINITY; let minX = Number.POSITIVE_INFINITY, minY = Number.POSITIVE_INFINITY, maxX = Number.NEGATIVE_INFINITY, maxY = Number.NEGATIVE_INFINITY;
for (let i = 0, n = drawOrder.length; i < n; i++) { for (let i = 0, n = drawOrder.length; i < n; i++) {
let slot = drawOrder[i]; const slot = drawOrder[i];
if (!slot.bone.active) continue; if (!slot.bone.active) continue;
let verticesLength = 0; let verticesLength = 0;
let vertices: NumberArrayLike | null = null; let vertices: NumberArrayLike | null = null;
let triangles: NumberArrayLike | null = null; let triangles: NumberArrayLike | null = null;
let attachment = slot.pose.attachment; const attachment = slot.pose.attachment;
if (attachment) { if (attachment) {
if (attachment instanceof RegionAttachment) { if (attachment instanceof RegionAttachment) {
verticesLength = 8; verticesLength = 8;
@ -468,12 +475,12 @@ export class Skeleton {
continue; continue;
} }
if (vertices && triangles) { if (vertices && triangles) {
if (clipper && clipper.isClipping() && clipper.clipTriangles(vertices, triangles, triangles.length)) { if (clipper?.isClipping() && clipper.clipTriangles(vertices, triangles, triangles.length)) {
vertices = clipper.clippedVertices; vertices = clipper.clippedVertices;
verticesLength = clipper.clippedVertices.length; verticesLength = clipper.clippedVertices.length;
} }
for (let ii = 0, nn = vertices.length; ii < nn; ii += 2) { for (let ii = 0, nn = vertices.length; ii < nn; ii += 2) {
let x = vertices[ii], y = vertices[ii + 1]; const x = vertices[ii], y = vertices[ii + 1];
minX = Math.min(minX, x); minX = Math.min(minX, x);
minY = Math.min(minY, y); minY = Math.min(minY, y);
maxX = Math.max(maxX, x); maxX = Math.max(maxX, x);

File diff suppressed because it is too large Load Diff

View File

@ -28,8 +28,8 @@
*****************************************************************************/ *****************************************************************************/
import { BoundingBoxAttachment } from "./attachments/BoundingBoxAttachment.js"; import { BoundingBoxAttachment } from "./attachments/BoundingBoxAttachment.js";
import { Skeleton } from "./Skeleton.js"; import type { Skeleton } from "./Skeleton.js";
import { NumberArrayLike, Pool, Utils } from "./Utils.js"; import { type NumberArrayLike, Pool, Utils } from "./Utils.js";
/** Collects each visible {@link BoundingBoxAttachment} and computes the world vertices for its polygon. The polygon vertices are /** Collects each visible {@link BoundingBoxAttachment} and computes the world vertices for its polygon. The polygon vertices are
* provided along with convenience methods for doing hit detection. */ * provided along with convenience methods for doing hit detection. */
@ -48,10 +48,10 @@ export class SkeletonBounds {
maxY = 0; maxY = 0;
/** The visible bounding boxes. */ /** The visible bounding boxes. */
boundingBoxes = new Array<BoundingBoxAttachment>(); boundingBoxes = [] as BoundingBoxAttachment[];
/** The world vertices for the bounding box polygons. */ /** The world vertices for the bounding box polygons. */
polygons = new Array<NumberArrayLike>(); polygons = [] as NumberArrayLike[];
private polygonPool = new Pool<NumberArrayLike>(() => { private polygonPool = new Pool<NumberArrayLike>(() => {
return Utils.newFloatArray(16); return Utils.newFloatArray(16);
@ -63,25 +63,25 @@ export class SkeletonBounds {
* SkeletonBounds AABB methods will always return true. */ * SkeletonBounds AABB methods will always return true. */
update (skeleton: Skeleton, updateAabb: boolean) { update (skeleton: Skeleton, updateAabb: boolean) {
if (!skeleton) throw new Error("skeleton cannot be null."); if (!skeleton) throw new Error("skeleton cannot be null.");
let boundingBoxes = this.boundingBoxes; const boundingBoxes = this.boundingBoxes;
let polygons = this.polygons; const polygons = this.polygons;
let polygonPool = this.polygonPool; const polygonPool = this.polygonPool;
let slots = skeleton.slots; const slots = skeleton.slots;
let slotCount = slots.length; const slotCount = slots.length;
boundingBoxes.length = 0; boundingBoxes.length = 0;
polygonPool.freeAll(polygons); polygonPool.freeAll(polygons);
polygons.length = 0; polygons.length = 0;
for (let i = 0; i < slotCount; i++) { for (let i = 0; i < slotCount; i++) {
let slot = slots[i]; const slot = slots[i];
if (!slot.bone.active) continue; if (!slot.bone.active) continue;
let attachment = slot.applied.attachment; const attachment = slot.applied.attachment;
if (attachment instanceof BoundingBoxAttachment) { if (attachment instanceof BoundingBoxAttachment) {
boundingBoxes.push(attachment); boundingBoxes.push(attachment);
let polygon = polygonPool.obtain(); let polygon = polygonPool.obtain();
if (polygon.length != attachment.worldVerticesLength) { if (polygon.length !== attachment.worldVerticesLength) {
polygon = Utils.newFloatArray(attachment.worldVerticesLength); polygon = Utils.newFloatArray(attachment.worldVerticesLength);
} }
polygons.push(polygon); polygons.push(polygon);
@ -101,13 +101,13 @@ export class SkeletonBounds {
aabbCompute () { aabbCompute () {
let minX = Number.POSITIVE_INFINITY, minY = Number.POSITIVE_INFINITY, maxX = Number.NEGATIVE_INFINITY, maxY = Number.NEGATIVE_INFINITY; let minX = Number.POSITIVE_INFINITY, minY = Number.POSITIVE_INFINITY, maxX = Number.NEGATIVE_INFINITY, maxY = Number.NEGATIVE_INFINITY;
let polygons = this.polygons; const polygons = this.polygons;
for (let i = 0, n = polygons.length; i < n; i++) { for (let i = 0, n = polygons.length; i < n; i++) {
let polygon = polygons[i]; const polygon = polygons[i];
let vertices = polygon; const vertices = polygon;
for (let ii = 0, nn = polygon.length; ii < nn; ii += 2) { for (let ii = 0, nn = polygon.length; ii < nn; ii += 2) {
let x = vertices[ii]; const x = vertices[ii];
let y = vertices[ii + 1]; const y = vertices[ii + 1];
minX = Math.min(minX, x); minX = Math.min(minX, x);
minY = Math.min(minY, y); minY = Math.min(minY, y);
maxX = Math.max(maxX, x); maxX = Math.max(maxX, x);
@ -127,13 +127,13 @@ export class SkeletonBounds {
/** Returns true if the axis aligned bounding box intersects the line segment. */ /** Returns true if the axis aligned bounding box intersects the line segment. */
aabbIntersectsSegment (x1: number, y1: number, x2: number, y2: number) { aabbIntersectsSegment (x1: number, y1: number, x2: number, y2: number) {
let minX = this.minX; const minX = this.minX;
let minY = this.minY; const minY = this.minY;
let maxX = this.maxX; const maxX = this.maxX;
let maxY = this.maxY; const maxY = this.maxY;
if ((x1 <= minX && x2 <= minX) || (y1 <= minY && y2 <= minY) || (x1 >= maxX && x2 >= maxX) || (y1 >= maxY && y2 >= maxY)) if ((x1 <= minX && x2 <= minX) || (y1 <= minY && y2 <= minY) || (x1 >= maxX && x2 >= maxX) || (y1 >= maxY && y2 >= maxY))
return false; return false;
let m = (y2 - y1) / (x2 - x1); const m = (y2 - y1) / (x2 - x1);
let y = m * (minX - x1) + y1; let y = m * (minX - x1) + y1;
if (y > minY && y < maxY) return true; if (y > minY && y < maxY) return true;
y = m * (maxX - x1) + y1; y = m * (maxX - x1) + y1;
@ -153,7 +153,7 @@ export class SkeletonBounds {
/** Returns the first bounding box attachment that contains the point, or null. When doing many checks, it is usually more /** Returns the first bounding box attachment that contains the point, or null. When doing many checks, it is usually more
* efficient to only call this method if {@link #aabbContainsPoint(float, float)} returns true. */ * efficient to only call this method if {@link #aabbContainsPoint(float, float)} returns true. */
containsPoint (x: number, y: number): BoundingBoxAttachment | null { containsPoint (x: number, y: number): BoundingBoxAttachment | null {
let polygons = this.polygons; const polygons = this.polygons;
for (let i = 0, n = polygons.length; i < n; i++) for (let i = 0, n = polygons.length; i < n; i++)
if (this.containsPointPolygon(polygons[i], x, y)) return this.boundingBoxes[i]; if (this.containsPointPolygon(polygons[i], x, y)) return this.boundingBoxes[i];
return null; return null;
@ -161,16 +161,16 @@ export class SkeletonBounds {
/** Returns true if the polygon contains the point. */ /** Returns true if the polygon contains the point. */
containsPointPolygon (polygon: NumberArrayLike, x: number, y: number) { containsPointPolygon (polygon: NumberArrayLike, x: number, y: number) {
let vertices = polygon; const vertices = polygon;
let nn = polygon.length; const nn = polygon.length;
let prevIndex = nn - 2; let prevIndex = nn - 2;
let inside = false; let inside = false;
for (let ii = 0; ii < nn; ii += 2) { for (let ii = 0; ii < nn; ii += 2) {
let vertexY = vertices[ii + 1]; const vertexY = vertices[ii + 1];
let prevY = vertices[prevIndex + 1]; const prevY = vertices[prevIndex + 1];
if ((vertexY < y && prevY >= y) || (prevY < y && vertexY >= y)) { if ((vertexY < y && prevY >= y) || (prevY < y && vertexY >= y)) {
let vertexX = vertices[ii]; const vertexX = vertices[ii];
if (vertexX + (y - vertexY) / (prevY - vertexY) * (vertices[prevIndex] - vertexX) < x) inside = !inside; if (vertexX + (y - vertexY) / (prevY - vertexY) * (vertices[prevIndex] - vertexX) < x) inside = !inside;
} }
prevIndex = ii; prevIndex = ii;
@ -182,7 +182,7 @@ export class SkeletonBounds {
* is usually more efficient to only call this method if {@link #aabbIntersectsSegment()} returns * is usually more efficient to only call this method if {@link #aabbIntersectsSegment()} returns
* true. */ * true. */
intersectsSegment (x1: number, y1: number, x2: number, y2: number) { intersectsSegment (x1: number, y1: number, x2: number, y2: number) {
let polygons = this.polygons; const polygons = this.polygons;
for (let i = 0, n = polygons.length; i < n; i++) for (let i = 0, n = polygons.length; i < n; i++)
if (this.intersectsSegmentPolygon(polygons[i], x1, y1, x2, y2)) return this.boundingBoxes[i]; if (this.intersectsSegmentPolygon(polygons[i], x1, y1, x2, y2)) return this.boundingBoxes[i];
return null; return null;
@ -190,20 +190,20 @@ export class SkeletonBounds {
/** Returns true if the polygon contains any part of the line segment. */ /** Returns true if the polygon contains any part of the line segment. */
intersectsSegmentPolygon (polygon: NumberArrayLike, x1: number, y1: number, x2: number, y2: number) { intersectsSegmentPolygon (polygon: NumberArrayLike, x1: number, y1: number, x2: number, y2: number) {
let vertices = polygon; const vertices = polygon;
let nn = polygon.length; const nn = polygon.length;
let width12 = x1 - x2, height12 = y1 - y2; const width12 = x1 - x2, height12 = y1 - y2;
let det1 = x1 * y2 - y1 * x2; const det1 = x1 * y2 - y1 * x2;
let x3 = vertices[nn - 2], y3 = vertices[nn - 1]; let x3 = vertices[nn - 2], y3 = vertices[nn - 1];
for (let ii = 0; ii < nn; ii += 2) { for (let ii = 0; ii < nn; ii += 2) {
let x4 = vertices[ii], y4 = vertices[ii + 1]; const x4 = vertices[ii], y4 = vertices[ii + 1];
let det2 = x3 * y4 - y3 * x4; const det2 = x3 * y4 - y3 * x4;
let width34 = x3 - x4, height34 = y3 - y4; const width34 = x3 - x4, height34 = y3 - y4;
let det3 = width12 * height34 - height12 * width34; const det3 = width12 * height34 - height12 * width34;
let x = (det1 * width34 - width12 * det2) / det3; const x = (det1 * width34 - width12 * det2) / det3;
if (((x >= x3 && x <= x4) || (x >= x4 && x <= x3)) && ((x >= x1 && x <= x2) || (x >= x2 && x <= x1))) { if (((x >= x3 && x <= x4) || (x >= x4 && x <= x3)) && ((x >= x1 && x <= x2) || (x >= x2 && x <= x1))) {
let y = (det1 * height34 - height12 * det2) / det3; const y = (det1 * height34 - height12 * det2) / det3;
if (((y >= y3 && y <= y4) || (y >= y4 && y <= y3)) && ((y >= y1 && y <= y2) || (y >= y2 && y <= y1))) return true; if (((y >= y3 && y <= y4) || (y >= y4 && y <= y3)) && ((y >= y1 && y <= y2) || (y >= y2 && y <= y1))) return true;
} }
x3 = x4; x3 = x4;
@ -215,8 +215,8 @@ export class SkeletonBounds {
/** Returns the polygon for the specified bounding box, or null. */ /** Returns the polygon for the specified bounding box, or null. */
getPolygon (boundingBox: BoundingBoxAttachment) { getPolygon (boundingBox: BoundingBoxAttachment) {
if (!boundingBox) throw new Error("boundingBox cannot be null."); if (!boundingBox) throw new Error("boundingBox cannot be null.");
let index = this.boundingBoxes.indexOf(boundingBox); const index = this.boundingBoxes.indexOf(boundingBox);
return index == -1 ? null : this.polygons[index]; return index === -1 ? null : this.polygons[index];
} }
/** The width of the axis aligned bounding box. */ /** The width of the axis aligned bounding box. */

View File

@ -27,22 +27,22 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { ClippingAttachment } from "./attachments/ClippingAttachment.js"; import type { ClippingAttachment } from "./attachments/ClippingAttachment.js";
import { Skeleton } from "./Skeleton.js"; import type { Skeleton } from "./Skeleton.js";
import { Slot } from "./Slot.js"; import type { Slot } from "./Slot.js";
import { Triangulator } from "./Triangulator.js"; import { Triangulator } from "./Triangulator.js";
import { Utils, Color, NumberArrayLike } from "./Utils.js"; import { type Color, type NumberArrayLike, Utils } from "./Utils.js";
export class SkeletonClipping { export class SkeletonClipping {
private triangulator = new Triangulator(); private triangulator = new Triangulator();
private clippingPolygon = new Array<number>(); private clippingPolygon = [] as number[];
private clipOutput = new Array<number>(); private clipOutput = [] as number[];
clippedVertices = new Array<number>(); clippedVertices = [] as number[];
/** An empty array unless {@link clipTrianglesUnpacked} was used. **/ /** An empty array unless {@link clipTrianglesUnpacked} was used. **/
clippedUVs = new Array<number>(); clippedUVs = [] as number[];
clippedTriangles = new Array<number>(); clippedTriangles = [] as number[];
_clippedVerticesTyped = new Float32Array(1024); _clippedVerticesTyped = new Float32Array(1024);
_clippedUVsTyped = new Float32Array(1024); _clippedUVsTyped = new Float32Array(1024);
@ -54,7 +54,7 @@ export class SkeletonClipping {
clippedUVsLength = 0; clippedUVsLength = 0;
clippedTrianglesLength = 0; clippedTrianglesLength = 0;
private scratch = new Array<number>(); private scratch = [] as number[];
private clipAttachment: ClippingAttachment | null = null; private clipAttachment: ClippingAttachment | null = null;
private clippingPolygons: Array<Array<number>> | null = null; private clippingPolygons: Array<Array<number>> | null = null;
@ -63,14 +63,14 @@ export class SkeletonClipping {
if (this.clipAttachment) return 0; if (this.clipAttachment) return 0;
this.clipAttachment = clip; this.clipAttachment = clip;
let n = clip.worldVerticesLength; const n = clip.worldVerticesLength;
let vertices = Utils.setArraySize(this.clippingPolygon, n); const vertices = Utils.setArraySize(this.clippingPolygon, n);
clip.computeWorldVertices(skeleton, slot, 0, n, vertices, 0, 2); clip.computeWorldVertices(skeleton, slot, 0, n, vertices, 0, 2);
let clippingPolygon = this.clippingPolygon; const clippingPolygon = this.clippingPolygon;
SkeletonClipping.makeClockwise(clippingPolygon); SkeletonClipping.makeClockwise(clippingPolygon);
let clippingPolygons = this.clippingPolygons = this.triangulator.decompose(clippingPolygon, this.triangulator.triangulate(clippingPolygon)); const clippingPolygons = this.clippingPolygons = this.triangulator.decompose(clippingPolygon, this.triangulator.triangulate(clippingPolygon));
for (let i = 0, n = clippingPolygons.length; i < n; i++) { for (let i = 0, n = clippingPolygons.length; i < n; i++) {
let polygon = clippingPolygons[i]; const polygon = clippingPolygons[i];
SkeletonClipping.makeClockwise(polygon); SkeletonClipping.makeClockwise(polygon);
polygon.push(polygon[0]); polygon.push(polygon[0]);
polygon.push(polygon[1]); polygon.push(polygon[1]);
@ -109,10 +109,11 @@ export class SkeletonClipping {
private clipTrianglesNoRender (vertices: NumberArrayLike, triangles: NumberArrayLike, trianglesLength: number): boolean { private clipTrianglesNoRender (vertices: NumberArrayLike, triangles: NumberArrayLike, trianglesLength: number): boolean {
let clipOutput = this.clipOutput, clippedVertices = this.clippedVertices; const clipOutput = this.clipOutput, clippedVertices = this.clippedVertices;
let clippedTriangles = this.clippedTriangles; const clippedTriangles = this.clippedTriangles;
let polygons = this.clippingPolygons!; // biome-ignore lint/style/noNonNullAssertion: clipStart define it
let polygonsCount = polygons.length; const polygons = this.clippingPolygons!;
const polygonsCount = polygons.length;
let index = 0; let index = 0;
clippedVertices.length = 0; clippedVertices.length = 0;
@ -120,31 +121,31 @@ export class SkeletonClipping {
let clipOutputItems = null; let clipOutputItems = null;
for (let i = 0; i < trianglesLength; i += 3) { for (let i = 0; i < trianglesLength; i += 3) {
let v = triangles[i] << 1; let v = triangles[i] << 1;
let x1 = vertices[v], y1 = vertices[v + 1]; const x1 = vertices[v], y1 = vertices[v + 1];
v = triangles[i + 1] << 1; v = triangles[i + 1] << 1;
let x2 = vertices[v], y2 = vertices[v + 1]; const x2 = vertices[v], y2 = vertices[v + 1];
v = triangles[i + 2] << 1; v = triangles[i + 2] << 1;
let x3 = vertices[v], y3 = vertices[v + 1]; const x3 = vertices[v], y3 = vertices[v + 1];
for (let p = 0; p < polygonsCount; p++) { for (let p = 0; p < polygonsCount; p++) {
let s = clippedVertices.length; let s = clippedVertices.length;
if (this.clip(x1, y1, x2, y2, x3, y3, polygons[p], clipOutput)) { if (this.clip(x1, y1, x2, y2, x3, y3, polygons[p], clipOutput)) {
clipOutputItems = this.clipOutput; clipOutputItems = this.clipOutput;
let clipOutputLength = clipOutput.length; const clipOutputLength = clipOutput.length;
if (clipOutputLength == 0) continue; if (clipOutputLength === 0) continue;
let clipOutputCount = clipOutputLength >> 1; let clipOutputCount = clipOutputLength >> 1;
let clippedVerticesItems = Utils.setArraySize(clippedVertices, s + clipOutputCount * 2); const clippedVerticesItems = Utils.setArraySize(clippedVertices, s + clipOutputCount * 2);
for (let ii = 0; ii < clipOutputLength; ii += 2, s += 2) { for (let ii = 0; ii < clipOutputLength; ii += 2, s += 2) {
let x = clipOutputItems[ii], y = clipOutputItems[ii + 1]; const x = clipOutputItems[ii], y = clipOutputItems[ii + 1];
clippedVerticesItems[s] = x; clippedVerticesItems[s] = x;
clippedVerticesItems[s + 1] = y; clippedVerticesItems[s + 1] = y;
} }
s = clippedTriangles.length; s = clippedTriangles.length;
let clippedTrianglesItems = Utils.setArraySize(clippedTriangles, s + 3 * (clipOutputCount - 2)); const clippedTrianglesItems = Utils.setArraySize(clippedTriangles, s + 3 * (clipOutputCount - 2));
clipOutputCount--; clipOutputCount--;
for (let ii = 1; ii < clipOutputCount; ii++, s += 3) { for (let ii = 1; ii < clipOutputCount; ii++, s += 3) {
clippedTrianglesItems[s] = index; clippedTrianglesItems[s] = index;
@ -154,7 +155,7 @@ export class SkeletonClipping {
index += clipOutputCount + 1; index += clipOutputCount + 1;
} else { } else {
let clippedVerticesItems = Utils.setArraySize(clippedVertices, s + 3 * 2); const clippedVerticesItems = Utils.setArraySize(clippedVertices, s + 3 * 2);
clippedVerticesItems[s] = x1; clippedVerticesItems[s] = x1;
clippedVerticesItems[s + 1] = y1; clippedVerticesItems[s + 1] = y1;
@ -165,7 +166,7 @@ export class SkeletonClipping {
clippedVerticesItems[s + 5] = y3; clippedVerticesItems[s + 5] = y3;
s = clippedTriangles.length; s = clippedTriangles.length;
let clippedTrianglesItems = Utils.setArraySize(clippedTriangles, s + 3); const clippedTrianglesItems = Utils.setArraySize(clippedTriangles, s + 3);
clippedTrianglesItems[s] = index; clippedTrianglesItems[s] = index;
clippedTrianglesItems[s + 1] = (index + 1); clippedTrianglesItems[s + 1] = (index + 1);
clippedTrianglesItems[s + 2] = (index + 2); clippedTrianglesItems[s + 2] = (index + 2);
@ -180,10 +181,11 @@ export class SkeletonClipping {
private clipTrianglesRender (vertices: NumberArrayLike, triangles: NumberArrayLike, trianglesLength: number, uvs: NumberArrayLike, private clipTrianglesRender (vertices: NumberArrayLike, triangles: NumberArrayLike, trianglesLength: number, uvs: NumberArrayLike,
light: Color, dark: Color, twoColor: boolean, stride: number): boolean { light: Color, dark: Color, twoColor: boolean, stride: number): boolean {
let clipOutput = this.clipOutput, clippedVertices = this.clippedVertices; const clipOutput = this.clipOutput, clippedVertices = this.clippedVertices;
let clippedTriangles = this.clippedTriangles; const clippedTriangles = this.clippedTriangles;
let polygons = this.clippingPolygons!; // biome-ignore lint/style/noNonNullAssertion: clipStart define it
let polygonsCount = polygons.length; const polygons = this.clippingPolygons!;
const polygonsCount = polygons.length;
let index = 0; let index = 0;
clippedVertices.length = 0; clippedVertices.length = 0;
@ -191,40 +193,40 @@ export class SkeletonClipping {
let clipOutputItems = null; let clipOutputItems = null;
for (let i = 0; i < trianglesLength; i += 3) { for (let i = 0; i < trianglesLength; i += 3) {
let t = triangles[i]; let t = triangles[i];
let u1 = uvs[t << 1], v1 = uvs[(t << 1) + 1]; const u1 = uvs[t << 1], v1 = uvs[(t << 1) + 1];
let x1 = vertices[t * stride], y1 = vertices[t * stride + 1]; const x1 = vertices[t * stride], y1 = vertices[t * stride + 1];
t = triangles[i + 1]; t = triangles[i + 1];
let u2 = uvs[t << 1], v2 = uvs[(t << 1) + 1]; const u2 = uvs[t << 1], v2 = uvs[(t << 1) + 1];
let x2 = vertices[t * stride], y2 = vertices[t * stride + 1]; const x2 = vertices[t * stride], y2 = vertices[t * stride + 1];
t = triangles[i + 2]; t = triangles[i + 2];
let u3 = uvs[t << 1], v3 = uvs[(t << 1) + 1]; const u3 = uvs[t << 1], v3 = uvs[(t << 1) + 1];
let x3 = vertices[t * stride], y3 = vertices[t * stride + 1]; const x3 = vertices[t * stride], y3 = vertices[t * stride + 1];
for (let p = 0; p < polygonsCount; p++) { for (let p = 0; p < polygonsCount; p++) {
let s = clippedVertices.length; let s = clippedVertices.length;
if (this.clip(x1, y1, x2, y2, x3, y3, polygons[p], clipOutput)) { if (this.clip(x1, y1, x2, y2, x3, y3, polygons[p], clipOutput)) {
clipOutputItems = this.clipOutput; clipOutputItems = this.clipOutput;
let clipOutputLength = clipOutput.length; const clipOutputLength = clipOutput.length;
if (clipOutputLength == 0) continue; if (clipOutputLength === 0) continue;
let d0 = y2 - y3, d1 = x3 - x2, d2 = x1 - x3, d4 = y3 - y1; const d0 = y2 - y3, d1 = x3 - x2, d2 = x1 - x3, d4 = y3 - y1;
let d = 1 / (d0 * d2 + d1 * (y1 - y3)); const d = 1 / (d0 * d2 + d1 * (y1 - y3));
let clipOutputCount = clipOutputLength >> 1; let clipOutputCount = clipOutputLength >> 1;
let clippedVerticesItems = Utils.setArraySize(clippedVertices, s + clipOutputCount * stride); const clippedVerticesItems = Utils.setArraySize(clippedVertices, s + clipOutputCount * stride);
for (let ii = 0; ii < clipOutputLength; ii += 2, s += stride) { for (let ii = 0; ii < clipOutputLength; ii += 2, s += stride) {
let x = clipOutputItems[ii], y = clipOutputItems[ii + 1]; const x = clipOutputItems[ii], y = clipOutputItems[ii + 1];
clippedVerticesItems[s] = x; clippedVerticesItems[s] = x;
clippedVerticesItems[s + 1] = y; clippedVerticesItems[s + 1] = y;
clippedVerticesItems[s + 2] = light.r; clippedVerticesItems[s + 2] = light.r;
clippedVerticesItems[s + 3] = light.g; clippedVerticesItems[s + 3] = light.g;
clippedVerticesItems[s + 4] = light.b; clippedVerticesItems[s + 4] = light.b;
clippedVerticesItems[s + 5] = light.a; clippedVerticesItems[s + 5] = light.a;
let c0 = x - x3, c1 = y - y3; const c0 = x - x3, c1 = y - y3;
let a = (d0 * c0 + d1 * c1) * d; const a = (d0 * c0 + d1 * c1) * d;
let b = (d4 * c0 + d2 * c1) * d; const b = (d4 * c0 + d2 * c1) * d;
let c = 1 - a - b; const c = 1 - a - b;
clippedVerticesItems[s + 6] = u1 * a + u2 * b + u3 * c; clippedVerticesItems[s + 6] = u1 * a + u2 * b + u3 * c;
clippedVerticesItems[s + 7] = v1 * a + v2 * b + v3 * c; clippedVerticesItems[s + 7] = v1 * a + v2 * b + v3 * c;
if (twoColor) { if (twoColor) {
@ -236,7 +238,7 @@ export class SkeletonClipping {
} }
s = clippedTriangles.length; s = clippedTriangles.length;
let clippedTrianglesItems = Utils.setArraySize(clippedTriangles, s + 3 * (clipOutputCount - 2)); const clippedTrianglesItems = Utils.setArraySize(clippedTriangles, s + 3 * (clipOutputCount - 2));
clipOutputCount--; clipOutputCount--;
for (let ii = 1; ii < clipOutputCount; ii++, s += 3) { for (let ii = 1; ii < clipOutputCount; ii++, s += 3) {
clippedTrianglesItems[s] = index; clippedTrianglesItems[s] = index;
@ -246,7 +248,7 @@ export class SkeletonClipping {
index += clipOutputCount + 1; index += clipOutputCount + 1;
} else { } else {
let clippedVerticesItems = Utils.setArraySize(clippedVertices, s + 3 * stride); const clippedVerticesItems = Utils.setArraySize(clippedVertices, s + 3 * stride);
clippedVerticesItems[s] = x1; clippedVerticesItems[s] = x1;
clippedVerticesItems[s + 1] = y1; clippedVerticesItems[s + 1] = y1;
clippedVerticesItems[s + 2] = light.r; clippedVerticesItems[s + 2] = light.r;
@ -310,7 +312,7 @@ export class SkeletonClipping {
} }
s = clippedTriangles.length; s = clippedTriangles.length;
let clippedTrianglesItems = Utils.setArraySize(clippedTriangles, s + 3); const clippedTrianglesItems = Utils.setArraySize(clippedTriangles, s + 3);
clippedTrianglesItems[s] = index; clippedTrianglesItems[s] = index;
clippedTrianglesItems[s + 1] = (index + 1); clippedTrianglesItems[s + 1] = (index + 1);
clippedTrianglesItems[s + 2] = (index + 2); clippedTrianglesItems[s + 2] = (index + 2);
@ -325,6 +327,7 @@ export class SkeletonClipping {
public clipTrianglesUnpacked (vertices: NumberArrayLike, triangles: NumberArrayLike | Uint32Array, trianglesLength: number, uvs: NumberArrayLike) { public clipTrianglesUnpacked (vertices: NumberArrayLike, triangles: NumberArrayLike | Uint32Array, trianglesLength: number, uvs: NumberArrayLike) {
const clipOutput = this.clipOutput; const clipOutput = this.clipOutput;
let clippedVertices = this._clippedVerticesTyped, clippedUVs = this._clippedUVsTyped, clippedTriangles = this._clippedTrianglesTyped; let clippedVertices = this._clippedVerticesTyped, clippedUVs = this._clippedUVsTyped, clippedTriangles = this._clippedTrianglesTyped;
// biome-ignore lint/style/noNonNullAssertion: clipStart define it
const polygons = this.clippingPolygons!; const polygons = this.clippingPolygons!;
const polygonsCount = polygons.length; const polygonsCount = polygons.length;
@ -464,7 +467,7 @@ export class SkeletonClipping {
/** Clips the input triangle against the convex, clockwise clipping area. If the triangle lies entirely within the clipping /** Clips the input triangle against the convex, clockwise clipping area. If the triangle lies entirely within the clipping
* area, false is returned. The clipping area must duplicate the first vertex at the end of the vertices list. */ * area, false is returned. The clipping area must duplicate the first vertex at the end of the vertices list. */
private clip (x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, clippingArea: Array<number>, output: Array<number>) { private clip (x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, clippingArea: Array<number>, output: Array<number>) {
let originalOutput = output; const originalOutput = output;
let clipped = false; let clipped = false;
// Avoid copy at the end. // Avoid copy at the end.
@ -486,20 +489,20 @@ export class SkeletonClipping {
input.push(y1); input.push(y1);
output.length = 0; output.length = 0;
let clippingVerticesLast = clippingArea.length - 4; const clippingVerticesLast = clippingArea.length - 4;
let clippingVertices = clippingArea; const clippingVertices = clippingArea;
for (let i = 0; ; i += 2) { for (let i = 0; ; i += 2) {
let edgeX = clippingVertices[i], edgeY = clippingVertices[i + 1]; const edgeX = clippingVertices[i], edgeY = clippingVertices[i + 1];
let ex = edgeX - clippingVertices[i + 2], ey = edgeY - clippingVertices[i + 3]; const ex = edgeX - clippingVertices[i + 2], ey = edgeY - clippingVertices[i + 3];
let outputStart = output.length; const outputStart = output.length;
let inputVertices = input; const inputVertices = input;
for (let ii = 0, nn = input.length - 2; ii < nn;) { for (let ii = 0, nn = input.length - 2; ii < nn;) {
let inputX = inputVertices[ii], inputY = inputVertices[ii + 1]; const inputX = inputVertices[ii], inputY = inputVertices[ii + 1];
ii += 2; ii += 2;
let inputX2 = inputVertices[ii], inputY2 = inputVertices[ii + 1]; const inputX2 = inputVertices[ii], inputY2 = inputVertices[ii + 1];
let s2 = ey * (edgeX - inputX2) > ex * (edgeY - inputY2); const s2 = ey * (edgeX - inputX2) > ex * (edgeY - inputY2);
let s1 = ey * (edgeX - inputX) - ex * (edgeY - inputY); const s1 = ey * (edgeX - inputX) - ex * (edgeY - inputY);
if (s1 > 0) { if (s1 > 0) {
if (s2) { // v1 inside, v2 inside if (s2) { // v1 inside, v2 inside
output.push(inputX2); output.push(inputX2);
@ -507,7 +510,7 @@ export class SkeletonClipping {
continue; continue;
} }
// v1 inside, v2 outside // v1 inside, v2 outside
let ix = inputX2 - inputX, iy = inputY2 - inputY, t = s1 / (ix * ey - iy * ex); const ix = inputX2 - inputX, iy = inputY2 - inputY, t = s1 / (ix * ey - iy * ex);
if (t >= 0 && t <= 1) { if (t >= 0 && t <= 1) {
output.push(inputX + ix * t); output.push(inputX + ix * t);
output.push(inputY + iy * t); output.push(inputY + iy * t);
@ -517,7 +520,7 @@ export class SkeletonClipping {
continue; continue;
} }
} else if (s2) { // v1 outside, v2 inside } else if (s2) { // v1 outside, v2 inside
let ix = inputX2 - inputX, iy = inputY2 - inputY, t = s1 / (ix * ey - iy * ex); const ix = inputX2 - inputX, iy = inputY2 - inputY, t = s1 / (ix * ey - iy * ex);
if (t >= 0 && t <= 1) { if (t >= 0 && t <= 1) {
output.push(inputX + ix * t); output.push(inputX + ix * t);
output.push(inputY + iy * t); output.push(inputY + iy * t);
@ -532,7 +535,7 @@ export class SkeletonClipping {
clipped = true; clipped = true;
} }
if (outputStart == output.length) { // All edges outside. if (outputStart === output.length) { // All edges outside.
originalOutput.length = 0; originalOutput.length = 0;
return true; return true;
} }
@ -540,14 +543,14 @@ export class SkeletonClipping {
output.push(output[0]); output.push(output[0]);
output.push(output[1]); output.push(output[1]);
if (i == clippingVerticesLast) break; if (i === clippingVerticesLast) break;
let temp = output; const temp = output;
output = input; output = input;
output.length = 0; output.length = 0;
input = temp; input = temp;
} }
if (originalOutput != output) { if (originalOutput !== output) {
originalOutput.length = 0; originalOutput.length = 0;
for (let i = 0, n = output.length - 2; i < n; i++) for (let i = 0, n = output.length - 2; i < n; i++)
originalOutput[i] = output[i]; originalOutput[i] = output[i];
@ -558,8 +561,8 @@ export class SkeletonClipping {
} }
public static makeClockwise (polygon: NumberArrayLike) { public static makeClockwise (polygon: NumberArrayLike) {
let vertices = polygon; const vertices = polygon;
let verticeslength = polygon.length; const verticeslength = polygon.length;
let area = vertices[verticeslength - 2] * vertices[1] - vertices[0] * vertices[verticeslength - 1], p1x = 0, p1y = 0, p2x = 0, p2y = 0; let area = vertices[verticeslength - 2] * vertices[1] - vertices[0] * vertices[verticeslength - 1], p1x = 0, p1y = 0, p2x = 0, p2y = 0;
for (let i = 0, n = verticeslength - 3; i < n; i += 2) { for (let i = 0, n = verticeslength - 3; i < n; i += 2) {
@ -572,8 +575,8 @@ export class SkeletonClipping {
if (area < 0) return; if (area < 0) return;
for (let i = 0, lastX = verticeslength - 2, n = verticeslength >> 1; i < n; i += 2) { for (let i = 0, lastX = verticeslength - 2, n = verticeslength >> 1; i < n; i += 2) {
let x = vertices[i], y = vertices[i + 1]; const x = vertices[i], y = vertices[i + 1];
let other = lastX - i; const other = lastX - i;
vertices[i] = vertices[other]; vertices[i] = vertices[other];
vertices[i + 1] = vertices[other + 1]; vertices[i + 1] = vertices[other + 1];
vertices[other] = x; vertices[other] = x;

View File

@ -27,12 +27,12 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { Animation } from "./Animation" import type { Animation } from "./Animation"
import { BoneData } from "./BoneData.js"; import type { BoneData } from "./BoneData.js";
import { ConstraintData } from "./ConstraintData"; import type { ConstraintData } from "./ConstraintData";
import { EventData } from "./EventData.js"; import type { EventData } from "./EventData.js";
import { Skin } from "./Skin.js"; import type { Skin } from "./Skin.js";
import { SlotData } from "./SlotData.js"; import type { SlotData } from "./SlotData.js";
/** Stores the setup pose and all of the stateless data for a skeleton. /** Stores the setup pose and all of the stateless data for a skeleton.
* *
@ -44,12 +44,12 @@ export class SkeletonData {
name: string | null = null; name: string | null = null;
/** The skeleton's bones, sorted parent first. The root bone is always the first bone. */ /** The skeleton's bones, sorted parent first. The root bone is always the first bone. */
bones = new Array<BoneData>(); // Ordered parents first. bones = [] as BoneData[]; // Ordered parents first.
/** The skeleton's slots in the setup pose draw order. */ /** The skeleton's slots in the setup pose draw order. */
slots = new Array<SlotData>(); // Setup pose draw order. slots = [] as SlotData[]; // Setup pose draw order.
skins = new Array<Skin>(); skins = [] as Skin[];
/** The skeleton's default skin. By default this skin contains all attachments that were not in a skin in Spine. /** The skeleton's default skin. By default this skin contains all attachments that were not in a skin in Spine.
* *
@ -58,13 +58,14 @@ export class SkeletonData {
defaultSkin: Skin | null = null; defaultSkin: Skin | null = null;
/** The skeleton's events. */ /** The skeleton's events. */
events = new Array<EventData>(); events = [] as EventData[];
/** The skeleton's animations. */ /** The skeleton's animations. */
animations = new Array<Animation>(); animations = [] as Animation[];
/** The skeleton's IK constraints. */ /** The skeleton's IK constraints. */
constraints = new Array<ConstraintData<any, any>>(); // biome-ignore lint/suspicious/noExplicitAny: reference runtime does not restrict to specific types
constraints = [] as ConstraintData<any, any>[];
/** 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;
@ -103,9 +104,9 @@ export class SkeletonData {
* @returns May be null. */ * @returns May be null. */
findBone (boneName: string) { findBone (boneName: string) {
if (!boneName) throw new Error("boneName cannot be null."); if (!boneName) throw new Error("boneName cannot be null.");
let bones = this.bones; const bones = this.bones;
for (let i = 0, n = bones.length; i < n; i++) for (let i = 0, n = bones.length; i < n; i++)
if (bones[i].name == boneName) return bones[i]; if (bones[i].name === boneName) return bones[i];
return null; return null;
} }
@ -114,9 +115,9 @@ export class SkeletonData {
* @returns May be null. */ * @returns May be null. */
findSlot (slotName: string) { findSlot (slotName: string) {
if (!slotName) throw new Error("slotName cannot be null."); if (!slotName) throw new Error("slotName cannot be null.");
let slots = this.slots; const slots = this.slots;
for (let i = 0, n = slots.length; i < n; i++) for (let i = 0, n = slots.length; i < n; i++)
if (slots[i].name == slotName) return slots[i]; if (slots[i].name === slotName) return slots[i];
return null; return null;
} }
@ -125,9 +126,9 @@ export class SkeletonData {
* @returns May be null. */ * @returns May be null. */
findSkin (skinName: string) { findSkin (skinName: string) {
if (!skinName) throw new Error("skinName cannot be null."); if (!skinName) throw new Error("skinName cannot be null.");
let skins = this.skins; const skins = this.skins;
for (let i = 0, n = skins.length; i < n; i++) for (let i = 0, n = skins.length; i < n; i++)
if (skins[i].name == skinName) return skins[i]; if (skins[i].name === skinName) return skins[i];
return null; return null;
} }
@ -136,9 +137,9 @@ export class SkeletonData {
* @returns May be null. */ * @returns May be null. */
findEvent (eventDataName: string) { findEvent (eventDataName: string) {
if (!eventDataName) throw new Error("eventDataName cannot be null."); if (!eventDataName) throw new Error("eventDataName cannot be null.");
let events = this.events; const events = this.events;
for (let i = 0, n = events.length; i < n; i++) for (let i = 0, n = events.length; i < n; i++)
if (events[i].name == eventDataName) return events[i]; if (events[i].name === eventDataName) return events[i];
return null; return null;
} }
@ -147,20 +148,21 @@ export class SkeletonData {
* @returns May be null. */ * @returns May be null. */
findAnimation (animationName: string) { findAnimation (animationName: string) {
if (!animationName) throw new Error("animationName cannot be null."); if (!animationName) throw new Error("animationName cannot be null.");
let animations = this.animations; const animations = this.animations;
for (let i = 0, n = animations.length; i < n; i++) for (let i = 0, n = animations.length; i < n; i++)
if (animations[i].name == animationName) return animations[i]; if (animations[i].name === animationName) return animations[i];
return null; return null;
} }
// --- Constraints. // --- Constraints.
// biome-ignore lint/suspicious/noExplicitAny: reference runtime does not restrict to specific types
findConstraint<T extends ConstraintData<any, any>> (constraintName: string, type: new (name: string) => T): T | null { findConstraint<T extends ConstraintData<any, any>> (constraintName: string, type: new (name: string) => T): T | null {
if (!constraintName) throw new Error("constraintName cannot be null."); if (!constraintName) throw new Error("constraintName cannot be null.");
if (type == null) throw new Error("type cannot be null."); if (type == null) throw new Error("type cannot be null.");
const constraints = this.constraints; const constraints = this.constraints;
for (let i = 0, n = this.constraints.length; i < n; i++) { for (let i = 0, n = this.constraints.length; i < n; i++) {
let constraint = constraints[i]; const constraint = constraints[i];
if (constraint instanceof type && constraint.name === constraintName) return constraint as T; if (constraint instanceof type && constraint.name === constraintName) return constraint as T;
} }
return null; return null;

File diff suppressed because it is too large Load Diff

View File

@ -69,6 +69,7 @@ export class SkeletonRendererCore {
let indices: number[] | Uint32Array; let indices: number[] | Uint32Array;
let indicesCount: number; let indicesCount: number;
let attachmentColor: Color; let attachmentColor: Color;
// biome-ignore lint/suspicious/noExplicitAny: texture depends on the runtime
let texture: any; let texture: any;
if (attachment instanceof RegionAttachment) { if (attachment instanceof RegionAttachment) {
@ -270,6 +271,7 @@ interface RenderCommand {
numVertices: number; numVertices: number;
numIndices: number; numIndices: number;
blendMode: BlendMode; blendMode: BlendMode;
// biome-ignore lint/suspicious/noExplicitAny: texture depends on the runtime
texture: any; texture: any;
next?: RenderCommand; next?: RenderCommand;
} }

View File

@ -27,12 +27,12 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { Attachment } from "./attachments/Attachment.js"; import type { Attachment } from "./attachments/Attachment.js";
import { MeshAttachment } from "./attachments/MeshAttachment.js"; import { MeshAttachment } from "./attachments/MeshAttachment.js";
import { BoneData } from "./BoneData.js"; import type { BoneData } from "./BoneData.js";
import { ConstraintData } from "./ConstraintData.js"; import type { ConstraintData } from "./ConstraintData.js";
import { Skeleton } from "./Skeleton.js"; import type { Skeleton } from "./Skeleton.js";
import { Color, StringMap } from "./Utils.js"; import { Color, type 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 {
@ -47,9 +47,10 @@ export class Skin {
/** The skin's name, which is unique across all skins in the skeleton. */ /** The skin's name, which is unique across all skins in the skeleton. */
name: string; name: string;
attachments = new Array<StringMap<Attachment>>(); attachments = [] as StringMap<Attachment>[];
bones = Array<BoneData>(); bones = [] as BoneData[];
constraints = new Array<ConstraintData<any, any>>(); // biome-ignore lint/suspicious/noExplicitAny: reference runtime does not restrict to specific types
constraints = [] as ConstraintData<any, any>[];
/** The color of the skin as it was in Spine, or a default color if nonessential data was not exported. */ /** 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 color = new Color(0.99607843, 0.61960787, 0.30980393, 1); // fe9e4fff
@ -62,7 +63,7 @@ export class Skin {
/** Adds an attachment to the skin for the specified slot index and name. */ /** Adds an attachment to the skin for the specified slot index and name. */
setAttachment (slotIndex: number, name: string, attachment: Attachment) { setAttachment (slotIndex: number, name: string, attachment: Attachment) {
if (!attachment) throw new Error("attachment cannot be null."); if (!attachment) throw new Error("attachment cannot be null.");
let attachments = this.attachments; const attachments = this.attachments;
if (slotIndex >= attachments.length) attachments.length = slotIndex + 1; if (slotIndex >= attachments.length) attachments.length = slotIndex + 1;
if (!attachments[slotIndex]) attachments[slotIndex] = {}; if (!attachments[slotIndex]) attachments[slotIndex] = {};
attachments[slotIndex][name] = attachment; attachments[slotIndex][name] = attachment;
@ -71,10 +72,10 @@ export class Skin {
/** Adds all attachments, bones, and constraints from the specified skin to this skin. */ /** Adds all attachments, bones, and constraints from the specified skin to this skin. */
addSkin (skin: Skin) { addSkin (skin: Skin) {
for (let i = 0; i < skin.bones.length; i++) { for (let i = 0; i < skin.bones.length; i++) {
let bone = skin.bones[i]; const bone = skin.bones[i];
let contained = false; let contained = false;
for (let ii = 0; ii < this.bones.length; ii++) { for (let ii = 0; ii < this.bones.length; ii++) {
if (this.bones[ii] == bone) { if (this.bones[ii] === bone) {
contained = true; contained = true;
break; break;
} }
@ -83,10 +84,10 @@ export class Skin {
} }
for (let i = 0; i < skin.constraints.length; i++) { for (let i = 0; i < skin.constraints.length; i++) {
let constraint = skin.constraints[i]; const constraint = skin.constraints[i];
let contained = false; let contained = false;
for (let ii = 0; ii < this.constraints.length; ii++) { for (let ii = 0; ii < this.constraints.length; ii++) {
if (this.constraints[ii] == constraint) { if (this.constraints[ii] === constraint) {
contained = true; contained = true;
break; break;
} }
@ -94,9 +95,9 @@ export class Skin {
if (!contained) this.constraints.push(constraint); if (!contained) this.constraints.push(constraint);
} }
let attachments = skin.getAttachments(); const attachments = skin.getAttachments();
for (let i = 0; i < attachments.length; i++) { for (let i = 0; i < attachments.length; i++) {
var attachment = attachments[i]; const attachment = attachments[i];
this.setAttachment(attachment.slotIndex, attachment.name, attachment.attachment); this.setAttachment(attachment.slotIndex, attachment.name, attachment.attachment);
} }
} }
@ -105,10 +106,10 @@ export class Skin {
* copied, instead a new linked mesh is created. The attachment copies can be modified without affecting the originals. */ * copied, instead a new linked mesh is created. The attachment copies can be modified without affecting the originals. */
copySkin (skin: Skin) { copySkin (skin: Skin) {
for (let i = 0; i < skin.bones.length; i++) { for (let i = 0; i < skin.bones.length; i++) {
let bone = skin.bones[i]; const bone = skin.bones[i];
let contained = false; let contained = false;
for (let ii = 0; ii < this.bones.length; ii++) { for (let ii = 0; ii < this.bones.length; ii++) {
if (this.bones[ii] == bone) { if (this.bones[ii] === bone) {
contained = true; contained = true;
break; break;
} }
@ -117,10 +118,10 @@ export class Skin {
} }
for (let i = 0; i < skin.constraints.length; i++) { for (let i = 0; i < skin.constraints.length; i++) {
let constraint = skin.constraints[i]; const constraint = skin.constraints[i];
let contained = false; let contained = false;
for (let ii = 0; ii < this.constraints.length; ii++) { for (let ii = 0; ii < this.constraints.length; ii++) {
if (this.constraints[ii] == constraint) { if (this.constraints[ii] === constraint) {
contained = true; contained = true;
break; break;
} }
@ -128,9 +129,9 @@ export class Skin {
if (!contained) this.constraints.push(constraint); if (!contained) this.constraints.push(constraint);
} }
let attachments = skin.getAttachments(); const attachments = skin.getAttachments();
for (let i = 0; i < attachments.length; i++) { for (let i = 0; i < attachments.length; i++) {
var attachment = attachments[i]; const attachment = attachments[i];
if (!attachment.attachment) continue; if (!attachment.attachment) continue;
if (attachment.attachment instanceof MeshAttachment) { if (attachment.attachment instanceof MeshAttachment) {
attachment.attachment = attachment.attachment.newLinkedMesh(); attachment.attachment = attachment.attachment.newLinkedMesh();
@ -144,24 +145,24 @@ export class Skin {
/** Returns the attachment for the specified slot index and name, or null. */ /** Returns the attachment for the specified slot index and name, or null. */
getAttachment (slotIndex: number, name: string): Attachment | null { getAttachment (slotIndex: number, name: string): Attachment | null {
let dictionary = this.attachments[slotIndex]; const dictionary = this.attachments[slotIndex];
return dictionary ? dictionary[name] : null; return dictionary ? dictionary[name] : null;
} }
/** Removes the attachment in the skin for the specified slot index and name, if any. */ /** Removes the attachment in the skin for the specified slot index and name, if any. */
removeAttachment (slotIndex: number, name: string) { removeAttachment (slotIndex: number, name: string) {
let dictionary = this.attachments[slotIndex]; const dictionary = this.attachments[slotIndex];
if (dictionary) delete dictionary[name]; if (dictionary) delete dictionary[name];
} }
/** Returns all attachments in this skin. */ /** Returns all attachments in this skin. */
getAttachments (): Array<SkinEntry> { getAttachments (): Array<SkinEntry> {
let entries = new Array<SkinEntry>(); const entries: SkinEntry[] = [];
for (var i = 0; i < this.attachments.length; i++) { for (let i = 0; i < this.attachments.length; i++) {
let slotAttachments = this.attachments[i]; const slotAttachments = this.attachments[i];
if (slotAttachments) { if (slotAttachments) {
for (let name in slotAttachments) { for (const name in slotAttachments) {
let attachment = slotAttachments[name]; const attachment = slotAttachments[name];
if (attachment) entries.push(new SkinEntry(i, name, attachment)); if (attachment) entries.push(new SkinEntry(i, name, attachment));
} }
} }
@ -171,10 +172,10 @@ export class Skin {
/** Returns all attachments in this skin for the specified slot index. */ /** Returns all attachments in this skin for the specified slot index. */
getAttachmentsForSlot (slotIndex: number, attachments: Array<SkinEntry>) { getAttachmentsForSlot (slotIndex: number, attachments: Array<SkinEntry>) {
let slotAttachments = this.attachments[slotIndex]; const slotAttachments = this.attachments[slotIndex];
if (slotAttachments) { if (slotAttachments) {
for (let name in slotAttachments) { for (const name in slotAttachments) {
let attachment = slotAttachments[name]; const attachment = slotAttachments[name];
if (attachment) attachments.push(new SkinEntry(slotIndex, name, attachment)); if (attachment) attachments.push(new SkinEntry(slotIndex, name, attachment));
} }
} }
@ -191,14 +192,14 @@ export class Skin {
attachAll (skeleton: Skeleton, oldSkin: Skin) { attachAll (skeleton: Skeleton, oldSkin: Skin) {
let slotIndex = 0; let slotIndex = 0;
for (let i = 0; i < skeleton.slots.length; i++) { for (let i = 0; i < skeleton.slots.length; i++) {
let slot = skeleton.slots[i]; const slot = skeleton.slots[i];
let slotAttachment = slot.pose.getAttachment(); const slotAttachment = slot.pose.getAttachment();
if (slotAttachment && slotIndex < oldSkin.attachments.length) { if (slotAttachment && slotIndex < oldSkin.attachments.length) {
let dictionary = oldSkin.attachments[slotIndex]; const dictionary = oldSkin.attachments[slotIndex];
for (let key in dictionary) { for (const key in dictionary) {
let skinAttachment: Attachment = dictionary[key]; const skinAttachment: Attachment = dictionary[key];
if (slotAttachment == skinAttachment) { if (slotAttachment === skinAttachment) {
let attachment = this.getAttachment(slotIndex, key); const attachment = this.getAttachment(slotIndex, key);
if (attachment) slot.pose.setAttachment(attachment); if (attachment) slot.pose.setAttachment(attachment);
break; break;
} }

View File

@ -27,13 +27,13 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { Animation } from "./Animation.js"; import type { Animation } from "./Animation.js";
import { BoneData } from "./BoneData.js"; import type { BoneData } from "./BoneData.js";
import { ConstraintData } from "./ConstraintData.js"; import { ConstraintData } from "./ConstraintData.js";
import { Skeleton } from "./Skeleton.js"; import type { Skeleton } from "./Skeleton.js";
import { Slider } from "./Slider.js"; import { Slider } from "./Slider.js";
import { SliderPose } from "./SliderPose.js"; import { SliderPose } from "./SliderPose.js";
import { FromProperty } from "./TransformConstraintData.js"; import type { FromProperty } from "./TransformConstraintData.js";
/** Stores the setup pose for a {@link SliderConstraint}. /** Stores the setup pose for a {@link SliderConstraint}.
* *

View File

@ -27,7 +27,7 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { Pose } from "./Pose.js"; import type { Pose } from "./Pose.js";
/** Stores a pose for a slider. */ /** Stores a pose for a slider. */
export class SliderPose implements Pose<SliderPose> { export class SliderPose implements Pose<SliderPose> {

View File

@ -27,10 +27,10 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { Bone } from "./Bone.js"; import type { Bone } from "./Bone.js";
import { Posed } from "./Posed.js"; import { Posed } from "./Posed.js";
import { Skeleton } from "./Skeleton.js"; import type { Skeleton } from "./Skeleton.js";
import { SlotData } from "./SlotData.js"; import type { SlotData } from "./SlotData.js";
import { SlotPose } from "./SlotPose.js"; import { SlotPose } from "./SlotPose.js";
import { Color } from "./Utils.js"; import { Color } from "./Utils.js";
@ -59,6 +59,7 @@ export class Slot extends Posed<SlotData, SlotPose, SlotPose> {
setupPose () { setupPose () {
this.pose.color.setFromColor(this.data.setup.color); this.pose.color.setFromColor(this.data.setup.color);
// biome-ignore lint/style/noNonNullAssertion: reference runtime
if (this.pose.darkColor) this.pose.darkColor.setFromColor(this.data.setup.darkColor!); if (this.pose.darkColor) this.pose.darkColor.setFromColor(this.data.setup.darkColor!);
this.pose.sequenceIndex = this.data.setup.sequenceIndex; this.pose.sequenceIndex = this.data.setup.sequenceIndex;
if (!this.data.attachmentName) if (!this.data.attachmentName)

View File

@ -27,11 +27,10 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { BoneData } from "./BoneData.js"; import type { BoneData } from "./BoneData.js";
import { PosedData } from "./PosedData.js"; import { PosedData } from "./PosedData.js";
import { SlotPose } from "./SlotPose.js";
import type { Skeleton } from "./Skeleton.js"; import type { Skeleton } from "./Skeleton.js";
import { SlotPose } from "./SlotPose.js";
/** Stores the setup pose for a {@link Slot}. */ /** Stores the setup pose for a {@link Slot}. */
export class SlotData extends PosedData<SlotPose> { export class SlotData extends PosedData<SlotPose> {

View File

@ -27,11 +27,11 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { Pose } from "./Pose.js"; import type { Attachment } from "./attachments/Attachment.js";
import { Color } from "./Utils.js";
import { VertexAttachment } from "./attachments/Attachment.js"; import { VertexAttachment } from "./attachments/Attachment.js";
import { Attachment } from "./attachments/Attachment.js";
import type { Sequence } from "./attachments/Sequence.js"; import type { Sequence } from "./attachments/Sequence.js";
import type { Pose } from "./Pose.js";
import { Color } from "./Utils.js";
/** Stores a slot's pose. Slots organize attachments for {@link Skeleton#drawOrder} purposes and provide a place to store state /** Stores a slot's pose. Slots organize attachments for {@link Skeleton#drawOrder} purposes and provide a place to store state
* for an attachment. State cannot be stored in an attachment itself because attachments are stateless and may be shared across * for an attachment. State cannot be stored in an attachment itself because attachments are stateless and may be shared across
@ -57,7 +57,7 @@ export class SlotPose implements Pose<SlotPose> {
* *
* See {@link VertexAttachment.computeWorldVertices()} and * See {@link VertexAttachment.computeWorldVertices()} and
* {@link DeformTimeline}. */ * {@link DeformTimeline}. */
readonly deform = new Array<number>(); readonly deform = [] as number[];
SlotPose () { SlotPose () {
} }
@ -81,9 +81,9 @@ export class SlotPose implements Pose<SlotPose> {
* The deform is not cleared if the old attachment has the same {@link VertexAttachment.getTimelineAttachment()} as the * The deform is not cleared if the old attachment has the same {@link VertexAttachment.getTimelineAttachment()} as the
* specified attachment. */ * specified attachment. */
setAttachment (attachment: Attachment | null): void { setAttachment (attachment: Attachment | null): void {
if (this.attachment == attachment) return; if (this.attachment === attachment) return;
if (!(attachment instanceof VertexAttachment) || !(this.attachment instanceof VertexAttachment) if (!(attachment instanceof VertexAttachment) || !(this.attachment instanceof VertexAttachment)
|| attachment.timelineAttachment != this.attachment.timelineAttachment) { || attachment.timelineAttachment !== this.attachment.timelineAttachment) {
this.deform.length = 0; this.deform.length = 0;
} }
this.attachment = attachment; this.attachment = attachment;

View File

@ -27,6 +27,8 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
/** biome-ignore-all lint/suspicious/noExplicitAny: textures can be various type */
export abstract class Texture { export abstract class Texture {
protected _image: HTMLImageElement | ImageBitmap | any; protected _image: HTMLImageElement | ImageBitmap | any;

View File

@ -27,86 +27,86 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { AssetManagerBase } from "./AssetManagerBase.js"; import type { AssetManagerBase } from "./AssetManagerBase.js";
import { TextureFilter, TextureWrap, Texture, TextureRegion } from "./Texture.js"; import { type Texture, TextureFilter, TextureRegion, TextureWrap } from "./Texture.js";
import { Disposable, Utils, StringMap } from "./Utils.js"; import { type Disposable, type StringMap, Utils } from "./Utils.js";
export class TextureAtlas implements Disposable { export class TextureAtlas implements Disposable {
pages = new Array<TextureAtlasPage>(); pages = [] as TextureAtlasPage[];
regions = new Array<TextureAtlasRegion>(); regions = [] as TextureAtlasRegion[];
constructor (atlasText: string) { constructor (atlasText: string) {
let reader = new TextureAtlasReader(atlasText); const reader = new TextureAtlasReader(atlasText);
let entry = new Array<string>(4); const entry = new Array<string>(4);
let pageFields: StringMap<(page: TextureAtlasPage) => void> = {}; const pageFields: StringMap<(page: TextureAtlasPage) => void> = {};
pageFields["size"] = (page: TextureAtlasPage) => { pageFields.size = (page: TextureAtlasPage) => {
page!.width = parseInt(entry[1]); page.width = parseInt(entry[1]);
page!.height = parseInt(entry[2]); page.height = parseInt(entry[2]);
}; };
pageFields["format"] = () => { pageFields.format = () => {
// page.format = Format[tuple[0]]; we don't need format in WebGL // page.format = Format[tuple[0]]; we don't need format in WebGL
}; };
pageFields["filter"] = (page: TextureAtlasPage) => { pageFields.filter = (page: TextureAtlasPage) => {
page!.minFilter = Utils.enumValue(TextureFilter, entry[1]); page.minFilter = Utils.enumValue(TextureFilter, entry[1]);
page!.magFilter = Utils.enumValue(TextureFilter, entry[2]); page.magFilter = Utils.enumValue(TextureFilter, entry[2]);
}; };
pageFields["repeat"] = (page: TextureAtlasPage) => { pageFields.repeat = (page: TextureAtlasPage) => {
if (entry[1].indexOf('x') != -1) page!.uWrap = TextureWrap.Repeat; if (entry[1].indexOf('x') !== -1) page.uWrap = TextureWrap.Repeat;
if (entry[1].indexOf('y') != -1) page!.vWrap = TextureWrap.Repeat; if (entry[1].indexOf('y') !== -1) page.vWrap = TextureWrap.Repeat;
}; };
pageFields["pma"] = (page: TextureAtlasPage) => { pageFields.pma = (page: TextureAtlasPage) => {
page!.pma = entry[1] == "true"; page.pma = entry[1] === "true";
}; };
var regionFields: StringMap<(region: TextureAtlasRegion) => void> = {}; var regionFields: StringMap<(region: TextureAtlasRegion) => void> = {};
regionFields["xy"] = (region: TextureAtlasRegion) => { // Deprecated, use bounds. regionFields.xy = (region: TextureAtlasRegion) => { // Deprecated, use bounds.
region.x = parseInt(entry[1]); region.x = parseInt(entry[1]);
region.y = parseInt(entry[2]); region.y = parseInt(entry[2]);
}; };
regionFields["size"] = (region: TextureAtlasRegion) => { // Deprecated, use bounds. regionFields.size = (region: TextureAtlasRegion) => { // Deprecated, use bounds.
region.width = parseInt(entry[1]); region.width = parseInt(entry[1]);
region.height = parseInt(entry[2]); region.height = parseInt(entry[2]);
}; };
regionFields["bounds"] = (region: TextureAtlasRegion) => { regionFields.bounds = (region: TextureAtlasRegion) => {
region.x = parseInt(entry[1]); region.x = parseInt(entry[1]);
region.y = parseInt(entry[2]); region.y = parseInt(entry[2]);
region.width = parseInt(entry[3]); region.width = parseInt(entry[3]);
region.height = parseInt(entry[4]); region.height = parseInt(entry[4]);
}; };
regionFields["offset"] = (region: TextureAtlasRegion) => { // Deprecated, use offsets. regionFields.offset = (region: TextureAtlasRegion) => { // Deprecated, use offsets.
region.offsetX = parseInt(entry[1]); region.offsetX = parseInt(entry[1]);
region.offsetY = parseInt(entry[2]); region.offsetY = parseInt(entry[2]);
}; };
regionFields["orig"] = (region: TextureAtlasRegion) => { // Deprecated, use offsets. regionFields.orig = (region: TextureAtlasRegion) => { // Deprecated, use offsets.
region.originalWidth = parseInt(entry[1]); region.originalWidth = parseInt(entry[1]);
region.originalHeight = parseInt(entry[2]); region.originalHeight = parseInt(entry[2]);
}; };
regionFields["offsets"] = (region: TextureAtlasRegion) => { regionFields.offsets = (region: TextureAtlasRegion) => {
region.offsetX = parseInt(entry[1]); region.offsetX = parseInt(entry[1]);
region.offsetY = parseInt(entry[2]); region.offsetY = parseInt(entry[2]);
region.originalWidth = parseInt(entry[3]); region.originalWidth = parseInt(entry[3]);
region.originalHeight = parseInt(entry[4]); region.originalHeight = parseInt(entry[4]);
}; };
regionFields["rotate"] = (region: TextureAtlasRegion) => { regionFields.rotate = (region: TextureAtlasRegion) => {
let value = entry[1]; const value = entry[1];
if (value == "true") if (value === "true")
region.degrees = 90; region.degrees = 90;
else if (value != "false") else if (value !== "false")
region.degrees = parseInt(value); region.degrees = parseInt(value);
}; };
regionFields["index"] = (region: TextureAtlasRegion) => { regionFields.index = (region: TextureAtlasRegion) => {
region.index = parseInt(entry[1]); region.index = parseInt(entry[1]);
}; };
let line = reader.readLine(); let line = reader.readLine();
// Ignore empty lines before first entry. // Ignore empty lines before first entry.
while (line && line.trim().length == 0) while (line && line.trim().length === 0)
line = reader.readLine(); line = reader.readLine();
// Header entries. // Header entries.
while (true) { while (true) {
if (!line || line.trim().length == 0) break; if (!line || line.trim().length === 0) break;
if (reader.readEntry(entry, line) == 0) break; // Silently ignore all header fields. if (reader.readEntry(entry, line) === 0) break; // Silently ignore all header fields.
line = reader.readLine(); line = reader.readLine();
} }
@ -116,37 +116,37 @@ export class TextureAtlas implements Disposable {
let values: number[][] | null = null; let values: number[][] | null = null;
while (true) { while (true) {
if (line === null) break; if (line === null) break;
if (line.trim().length == 0) { if (line.trim().length === 0) {
page = null; page = null;
line = reader.readLine(); line = reader.readLine();
} else if (!page) { } else if (!page) {
page = new TextureAtlasPage(line.trim()); page = new TextureAtlasPage(line.trim());
while (true) { while (true) {
if (reader.readEntry(entry, line = reader.readLine()) == 0) break; if (reader.readEntry(entry, line = reader.readLine()) === 0) break;
let field = pageFields[entry[0]]; const field = pageFields[entry[0]];
if (field) field(page); if (field) field(page);
} }
this.pages.push(page); this.pages.push(page);
} else { } else {
let region = new TextureAtlasRegion(page, line); const region = new TextureAtlasRegion(page, line);
while (true) { while (true) {
let count = reader.readEntry(entry, line = reader.readLine()); const count = reader.readEntry(entry, line = reader.readLine());
if (count == 0) break; if (count === 0) break;
let field = regionFields[entry[0]]; const field = regionFields[entry[0]];
if (field) if (field)
field(region); field(region);
else { else {
if (!names) names = []; if (!names) names = [];
if (!values) values = []; if (!values) values = [];
names.push(entry[0]); names.push(entry[0]);
let entryValues: number[] = []; const entryValues: number[] = [];
for (let i = 0; i < count; i++) for (let i = 0; i < count; i++)
entryValues.push(parseInt(entry[i + 1])); entryValues.push(parseInt(entry[i + 1]));
values.push(entryValues); values.push(entryValues);
} }
} }
if (region.originalWidth == 0 && region.originalHeight == 0) { if (region.originalWidth === 0 && region.originalHeight === 0) {
region.originalWidth = region.width; region.originalWidth = region.width;
region.originalHeight = region.height; region.originalHeight = region.height;
} }
@ -158,7 +158,7 @@ export class TextureAtlas implements Disposable {
} }
region.u = region.x / page.width; region.u = region.x / page.width;
region.v = region.y / page.height; region.v = region.y / page.height;
if (region.degrees == 90) { if (region.degrees === 90) {
region.u2 = (region.x + region.height) / page.width; region.u2 = (region.x + region.height) / page.width;
region.v2 = (region.y + region.width) / page.height; region.v2 = (region.y + region.width) / page.height;
} else { } else {
@ -172,7 +172,7 @@ export class TextureAtlas implements Disposable {
findRegion (name: string): TextureAtlasRegion | null { findRegion (name: string): TextureAtlasRegion | null {
for (let i = 0; i < this.regions.length; i++) { for (let i = 0; i < this.regions.length; i++) {
if (this.regions[i].name == name) { if (this.regions[i].name === name) {
return this.regions[i]; return this.regions[i];
} }
} }
@ -180,8 +180,8 @@ export class TextureAtlas implements Disposable {
} }
setTextures (assetManager: AssetManagerBase, pathPrefix: string = "") { setTextures (assetManager: AssetManagerBase, pathPrefix: string = "") {
for (let page of this.pages) for (const page of this.pages)
page.setTexture(assetManager.get(pathPrefix + page.name)); page.setTexture(assetManager.get(pathPrefix + page.name) as Texture);
} }
dispose () { dispose () {
@ -208,20 +208,20 @@ class TextureAtlasReader {
readEntry (entry: string[], line: string | null): number { readEntry (entry: string[], line: string | null): number {
if (!line) return 0; if (!line) return 0;
line = line.trim(); line = line.trim();
if (line.length == 0) return 0; if (line.length === 0) return 0;
let colon = line.indexOf(':'); const colon = line.indexOf(':');
if (colon == -1) return 0; if (colon === -1) return 0;
entry[0] = line.substr(0, colon).trim(); entry[0] = line.substr(0, colon).trim();
for (let i = 1, lastMatch = colon + 1; ; i++) { for (let i = 1, lastMatch = colon + 1; ; i++) {
let comma = line.indexOf(',', lastMatch); const comma = line.indexOf(',', lastMatch);
if (comma == -1) { if (comma === -1) {
entry[i] = line.substr(lastMatch).trim(); entry[i] = line.substr(lastMatch).trim();
return i; return i;
} }
entry[i] = line.substr(lastMatch, comma - lastMatch).trim(); entry[i] = line.substr(lastMatch, comma - lastMatch).trim();
lastMatch = comma + 1; lastMatch = comma + 1;
if (i == 4) return 4; if (i === 4) return 4;
} }
} }
} }
@ -236,7 +236,7 @@ export class TextureAtlasPage {
width: number = 0; width: number = 0;
height: number = 0; height: number = 0;
pma: boolean = false; pma: boolean = false;
regions = new Array<TextureAtlasRegion>(); regions = [] as TextureAtlasRegion[];
constructor (name: string) { constructor (name: string) {
this.name = name; this.name = name;
@ -246,7 +246,7 @@ export class TextureAtlasPage {
this.texture = texture; this.texture = texture;
texture.setFilters(this.minFilter, this.magFilter); texture.setFilters(this.minFilter, this.magFilter);
texture.setWraps(this.uWrap, this.vWrap); texture.setWraps(this.uWrap, this.vWrap);
for (let region of this.regions) for (const region of this.regions)
region.texture = texture; region.texture = texture;
} }
} }

View File

@ -27,12 +27,12 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { Bone } from "./Bone.js"; import type { Bone } from "./Bone.js";
import { BonePose } from "./BonePose.js"; import type { BonePose } from "./BonePose.js";
import { Constraint } from "./Constraint.js"; import { Constraint } from "./Constraint.js";
import { Physics } from "./Physics.js"; import type { Physics } from "./Physics.js";
import { Skeleton } from "./Skeleton.js"; import type { Skeleton } from "./Skeleton.js";
import { TransformConstraintData } from "./TransformConstraintData.js"; import type { TransformConstraintData } from "./TransformConstraintData.js";
import { TransformConstraintPose } from "./TransformConstraintPose.js"; import { TransformConstraintPose } from "./TransformConstraintPose.js";
import { MathUtils } from "./Utils.js"; import { MathUtils } from "./Utils.js";
@ -53,7 +53,7 @@ export class TransformConstraint extends Constraint<TransformConstraint, Transfo
super(data, new TransformConstraintPose(), new TransformConstraintPose()); super(data, new TransformConstraintPose(), new TransformConstraintPose());
if (!skeleton) throw new Error("skeleton cannot be null."); if (!skeleton) throw new Error("skeleton cannot be null.");
this.bones = new Array<BonePose>(); this.bones = [] as BonePose[];
for (const boneData of data.bones) for (const boneData of data.bones)
this.bones.push(skeleton.bones[boneData.index].constrained); this.bones.push(skeleton.bones[boneData.index].constrained);
@ -70,7 +70,7 @@ export class TransformConstraint extends Constraint<TransformConstraint, Transfo
update (skeleton: Skeleton, physics: Physics) { update (skeleton: Skeleton, physics: Physics) {
const p = this.applied; const p = this.applied;
if (p.mixRotate == 0 && p.mixX == 0 && p.mixY == 0 && p.mixScaleX == 0 && p.mixScaleY == 0 && p.mixShearY == 0) return; if (p.mixRotate === 0 && p.mixX === 0 && p.mixY === 0 && p.mixScaleX === 0 && p.mixScaleY === 0 && p.mixShearY === 0) return;
const data = this.data; const data = this.data;
const localSource = data.localSource, localTarget = data.localTarget, additive = data.additive, clamp = data.clamp; const localSource = data.localSource, localTarget = data.localTarget, additive = data.additive, clamp = data.clamp;

View File

@ -27,13 +27,13 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import type { BoneData } from "./BoneData.js";
import type { BonePose } from "./BonePose.js";
import { ConstraintData } from "./ConstraintData.js"; import { ConstraintData } from "./ConstraintData.js";
import { BoneData } from "./BoneData.js"; import type { Skeleton } from "./Skeleton.js";
import { TransformConstraint } from "./TransformConstraint.js"; import { TransformConstraint } from "./TransformConstraint.js";
import { MathUtils } from "./Utils.js";
import { Skeleton } from "./Skeleton.js";
import { TransformConstraintPose } from "./TransformConstraintPose.js"; import { TransformConstraintPose } from "./TransformConstraintPose.js";
import { BonePose } from "./BonePose.js"; import { MathUtils } from "./Utils.js";
/** Stores the setup pose for a {@link TransformConstraint}. /** Stores the setup pose for a {@link TransformConstraint}.
* *
@ -47,7 +47,7 @@ export class TransformConstraintData extends ConstraintData<TransformConstraint,
public static readonly SHEARY = 5; public static readonly SHEARY = 5;
/** The bones that will be modified by this transform constraint. */ /** The bones that will be modified by this transform constraint. */
bones = new Array<BoneData>(); bones = [] as BoneData[];
/** The bone whose world transform will be copied to the constrained bones. */ /** The bone whose world transform will be copied to the constrained bones. */
public set source (source: BoneData) { this._source = source; } public set source (source: BoneData) { this._source = source; }
@ -275,7 +275,7 @@ export class ToScaleX extends ToProperty {
if (local) { if (local) {
if (additive) if (additive)
bone.scaleX *= 1 + (value - 1) * pose.mixScaleX; bone.scaleX *= 1 + (value - 1) * pose.mixScaleX;
else if (bone.scaleX != 0) // else if (bone.scaleX !== 0) //
bone.scaleX += (value - bone.scaleX) * pose.mixScaleX; bone.scaleX += (value - bone.scaleX) * pose.mixScaleX;
} else if (additive) { } else if (additive) {
const s = 1 + (value - 1) * pose.mixScaleX; const s = 1 + (value - 1) * pose.mixScaleX;
@ -283,7 +283,7 @@ export class ToScaleX extends ToProperty {
bone.c *= s; bone.c *= s;
} else { } else {
let a = bone.a / skeleton.scaleX, c = bone.c / skeleton.scaleY, s = Math.sqrt(a * a + c * c); let a = bone.a / skeleton.scaleX, c = bone.c / skeleton.scaleY, s = Math.sqrt(a * a + c * c);
if (s != 0) { if (s !== 0) {
s = 1 + (value - s) * pose.mixScaleX / s; s = 1 + (value - s) * pose.mixScaleX / s;
bone.a *= s; bone.a *= s;
bone.c *= s; bone.c *= s;
@ -309,7 +309,7 @@ export class ToScaleY extends ToProperty {
if (local) { if (local) {
if (additive) if (additive)
bone.scaleY *= 1 + (value - 1) * pose.mixScaleY; bone.scaleY *= 1 + (value - 1) * pose.mixScaleY;
else if (bone.scaleY != 0) // else if (bone.scaleY !== 0) //
bone.scaleY += (value - bone.scaleY) * pose.mixScaleY; bone.scaleY += (value - bone.scaleY) * pose.mixScaleY;
} else if (additive) { } else if (additive) {
const s = 1 + (value - 1) * pose.mixScaleY; const s = 1 + (value - 1) * pose.mixScaleY;
@ -317,7 +317,7 @@ export class ToScaleY extends ToProperty {
bone.d *= s; bone.d *= s;
} else { } else {
let b = bone.b / skeleton.scaleX, d = bone.d / skeleton.scaleY, s = Math.sqrt(b * b + d * d); let b = bone.b / skeleton.scaleX, d = bone.d / skeleton.scaleY, s = Math.sqrt(b * b + d * d);
if (s != 0) { if (s !== 0) {
s = 1 + (value - s) * pose.mixScaleY / s; s = 1 + (value - s) * pose.mixScaleY / s;
bone.b *= s; bone.b *= s;
bone.d *= s; bone.d *= s;

View File

@ -27,7 +27,7 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { Pose } from "./Pose" import type { Pose } from "./Pose"
/** Stores a pose for a transform constraint. */ /** Stores a pose for a transform constraint. */
export class TransformConstraintPose implements Pose<TransformConstraintPose> { export class TransformConstraintPose implements Pose<TransformConstraintPose> {

View File

@ -27,55 +27,56 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { NumberArrayLike, Pool } from "./Utils.js"; import { type NumberArrayLike, Pool } from "./Utils.js";
export class Triangulator { export class Triangulator {
private convexPolygons = new Array<Array<number>>(); private convexPolygons = [] as Array<number>[];
private convexPolygonsIndices = new Array<Array<number>>(); private convexPolygonsIndices = [] as Array<number>[];
private indicesArray = new Array<number>(); private indicesArray = [] as number[];
private isConcaveArray = new Array<boolean>(); private isConcaveArray = [] as boolean[];
private triangles = new Array<number>(); private triangles = [] as number[];
private polygonPool = new Pool<Array<number>>(() => { private polygonPool = new Pool<Array<number>>(() => {
return new Array<number>(); return [] as number[];
}); });
private polygonIndicesPool = new Pool<Array<number>>(() => { private polygonIndicesPool = new Pool<Array<number>>(() => {
return new Array<number>(); return [] as number[];
}); });
public triangulate (verticesArray: NumberArrayLike): Array<number> { public triangulate (verticesArray: NumberArrayLike): Array<number> {
let vertices = verticesArray; const vertices = verticesArray;
let vertexCount = verticesArray.length >> 1; let vertexCount = verticesArray.length >> 1;
let indices = this.indicesArray; const indices = this.indicesArray;
indices.length = 0; indices.length = 0;
for (let i = 0; i < vertexCount; i++) for (let i = 0; i < vertexCount; i++)
indices[i] = i; indices[i] = i;
let isConcave = this.isConcaveArray; const isConcave = this.isConcaveArray;
isConcave.length = 0; isConcave.length = 0;
for (let i = 0, n = vertexCount; i < n; ++i) for (let i = 0, n = vertexCount; i < n; ++i)
isConcave[i] = Triangulator.isConcave(i, vertexCount, vertices, indices); isConcave[i] = Triangulator.isConcave(i, vertexCount, vertices, indices);
let triangles = this.triangles; const triangles = this.triangles;
triangles.length = 0; triangles.length = 0;
while (vertexCount > 3) { while (vertexCount > 3) {
// Find ear tip. // Find ear tip.
let previous = vertexCount - 1, i = 0, next = 1; let previous = vertexCount - 1, i = 0, next = 1;
while (true) { while (true) {
// biome-ignore lint/suspicious/noConfusingLabels: reference runtime
outer: outer:
if (!isConcave[i]) { if (!isConcave[i]) {
let p1 = indices[previous] << 1, p2 = indices[i] << 1, p3 = indices[next] << 1; const p1 = indices[previous] << 1, p2 = indices[i] << 1, p3 = indices[next] << 1;
let p1x = vertices[p1], p1y = vertices[p1 + 1]; const p1x = vertices[p1], p1y = vertices[p1 + 1];
let p2x = vertices[p2], p2y = vertices[p2 + 1]; const p2x = vertices[p2], p2y = vertices[p2 + 1];
let p3x = vertices[p3], p3y = vertices[p3 + 1]; const p3x = vertices[p3], p3y = vertices[p3 + 1];
for (let ii = (next + 1) % vertexCount; ii != previous; ii = (ii + 1) % vertexCount) { for (let ii = (next + 1) % vertexCount; ii !== previous; ii = (ii + 1) % vertexCount) {
if (!isConcave[ii]) continue; if (!isConcave[ii]) continue;
let v = indices[ii] << 1; const v = indices[ii] << 1;
let vx = vertices[v], vy = vertices[v + 1]; const vx = vertices[v], vy = vertices[v + 1];
if (Triangulator.positiveArea(p3x, p3y, p1x, p1y, vx, vy)) { if (Triangulator.positiveArea(p3x, p3y, p1x, p1y, vx, vy)) {
if (Triangulator.positiveArea(p1x, p1y, p2x, p2y, vx, vy)) { if (Triangulator.positiveArea(p1x, p1y, p2x, p2y, vx, vy)) {
if (Triangulator.positiveArea(p2x, p2y, p3x, p3y, vx, vy)) break outer; if (Triangulator.positiveArea(p2x, p2y, p3x, p3y, vx, vy)) break outer;
@ -85,7 +86,7 @@ export class Triangulator {
break; break;
} }
if (next == 0) { if (next === 0) {
do { do {
if (!isConcave[i]) break; if (!isConcave[i]) break;
i--; i--;
@ -106,13 +107,13 @@ export class Triangulator {
isConcave.splice(i, 1); isConcave.splice(i, 1);
vertexCount--; vertexCount--;
let previousIndex = (vertexCount + i - 1) % vertexCount; const previousIndex = (vertexCount + i - 1) % vertexCount;
let nextIndex = i == vertexCount ? 0 : i; const nextIndex = i === vertexCount ? 0 : i;
isConcave[previousIndex] = Triangulator.isConcave(previousIndex, vertexCount, vertices, indices); isConcave[previousIndex] = Triangulator.isConcave(previousIndex, vertexCount, vertices, indices);
isConcave[nextIndex] = Triangulator.isConcave(nextIndex, vertexCount, vertices, indices); isConcave[nextIndex] = Triangulator.isConcave(nextIndex, vertexCount, vertices, indices);
} }
if (vertexCount == 3) { if (vertexCount === 3) {
triangles.push(indices[2]); triangles.push(indices[2]);
triangles.push(indices[0]); triangles.push(indices[0]);
triangles.push(indices[1]); triangles.push(indices[1]);
@ -122,12 +123,12 @@ export class Triangulator {
} }
decompose (verticesArray: Array<number>, triangles: Array<number>): Array<Array<number>> { decompose (verticesArray: Array<number>, triangles: Array<number>): Array<Array<number>> {
let vertices = verticesArray; const vertices = verticesArray;
let convexPolygons = this.convexPolygons; const convexPolygons = this.convexPolygons;
this.polygonPool.freeAll(convexPolygons); this.polygonPool.freeAll(convexPolygons);
convexPolygons.length = 0; convexPolygons.length = 0;
let convexPolygonsIndices = this.convexPolygonsIndices; const convexPolygonsIndices = this.convexPolygonsIndices;
this.polygonIndicesPool.freeAll(convexPolygonsIndices); this.polygonIndicesPool.freeAll(convexPolygonsIndices);
convexPolygonsIndices.length = 0; convexPolygonsIndices.length = 0;
@ -140,18 +141,18 @@ export class Triangulator {
// Merge subsequent triangles if they form a triangle fan. // Merge subsequent triangles if they form a triangle fan.
let fanBaseIndex = -1, lastWinding = 0; let fanBaseIndex = -1, lastWinding = 0;
for (let i = 0, n = triangles.length; i < n; i += 3) { for (let i = 0, n = triangles.length; i < n; i += 3) {
let t1 = triangles[i] << 1, t2 = triangles[i + 1] << 1, t3 = triangles[i + 2] << 1; const t1 = triangles[i] << 1, t2 = triangles[i + 1] << 1, t3 = triangles[i + 2] << 1;
let x1 = vertices[t1], y1 = vertices[t1 + 1]; const x1 = vertices[t1], y1 = vertices[t1 + 1];
let x2 = vertices[t2], y2 = vertices[t2 + 1]; const x2 = vertices[t2], y2 = vertices[t2 + 1];
let x3 = vertices[t3], y3 = vertices[t3 + 1]; const x3 = vertices[t3], y3 = vertices[t3 + 1];
// If the base of the last triangle is the same as this triangle, check if they form a convex polygon (triangle fan). // If the base of the last triangle is the same as this triangle, check if they form a convex polygon (triangle fan).
let merged = false; let merged = false;
if (fanBaseIndex == t1) { if (fanBaseIndex === t1) {
let o = polygon.length - 4; const o = polygon.length - 4;
let winding1 = Triangulator.winding(polygon[o], polygon[o + 1], polygon[o + 2], polygon[o + 3], x3, y3); const winding1 = Triangulator.winding(polygon[o], polygon[o + 1], polygon[o + 2], polygon[o + 3], x3, y3);
let winding2 = Triangulator.winding(x3, y3, polygon[0], polygon[1], polygon[2], polygon[3]); const winding2 = Triangulator.winding(x3, y3, polygon[0], polygon[1], polygon[2], polygon[3]);
if (winding1 == lastWinding && winding2 == lastWinding) { if (winding1 === lastWinding && winding2 === lastWinding) {
polygon.push(x3); polygon.push(x3);
polygon.push(y3); polygon.push(y3);
polygonIndices.push(t3); polygonIndices.push(t3);
@ -194,33 +195,33 @@ export class Triangulator {
// Go through the list of polygons and try to merge the remaining triangles with the found triangle fans. // Go through the list of polygons and try to merge the remaining triangles with the found triangle fans.
for (let i = 0, n = convexPolygons.length; i < n; i++) { for (let i = 0, n = convexPolygons.length; i < n; i++) {
polygonIndices = convexPolygonsIndices[i]; polygonIndices = convexPolygonsIndices[i];
if (polygonIndices.length == 0) continue; if (polygonIndices.length === 0) continue;
let firstIndex = polygonIndices[0]; const firstIndex = polygonIndices[0];
let lastIndex = polygonIndices[polygonIndices.length - 1]; const lastIndex = polygonIndices[polygonIndices.length - 1];
polygon = convexPolygons[i]; polygon = convexPolygons[i];
let o = polygon.length - 4; const o = polygon.length - 4;
let prevPrevX = polygon[o], prevPrevY = polygon[o + 1]; let prevPrevX = polygon[o], prevPrevY = polygon[o + 1];
let prevX = polygon[o + 2], prevY = polygon[o + 3]; let prevX = polygon[o + 2], prevY = polygon[o + 3];
let firstX = polygon[0], firstY = polygon[1]; const firstX = polygon[0], firstY = polygon[1];
let secondX = polygon[2], secondY = polygon[3]; const secondX = polygon[2], secondY = polygon[3];
let winding = Triangulator.winding(prevPrevX, prevPrevY, prevX, prevY, firstX, firstY); const winding = Triangulator.winding(prevPrevX, prevPrevY, prevX, prevY, firstX, firstY);
for (let ii = 0; ii < n; ii++) { for (let ii = 0; ii < n; ii++) {
if (ii == i) continue; if (ii === i) continue;
let otherIndices = convexPolygonsIndices[ii]; const otherIndices = convexPolygonsIndices[ii];
if (otherIndices.length != 3) continue; if (otherIndices.length !== 3) continue;
let otherFirstIndex = otherIndices[0]; const otherFirstIndex = otherIndices[0];
let otherSecondIndex = otherIndices[1]; const otherSecondIndex = otherIndices[1];
let otherLastIndex = otherIndices[2]; const otherLastIndex = otherIndices[2];
let otherPoly = convexPolygons[ii]; const otherPoly = convexPolygons[ii];
let x3 = otherPoly[otherPoly.length - 2], y3 = otherPoly[otherPoly.length - 1]; const x3 = otherPoly[otherPoly.length - 2], y3 = otherPoly[otherPoly.length - 1];
if (otherFirstIndex != firstIndex || otherSecondIndex != lastIndex) continue; if (otherFirstIndex !== firstIndex || otherSecondIndex !== lastIndex) continue;
let winding1 = Triangulator.winding(prevPrevX, prevPrevY, prevX, prevY, x3, y3); const winding1 = Triangulator.winding(prevPrevX, prevPrevY, prevX, prevY, x3, y3);
let winding2 = Triangulator.winding(x3, y3, firstX, firstY, secondX, secondY); const winding2 = Triangulator.winding(x3, y3, firstX, firstY, secondX, secondY);
if (winding1 == winding && winding2 == winding) { if (winding1 === winding && winding2 === winding) {
otherPoly.length = 0; otherPoly.length = 0;
otherIndices.length = 0; otherIndices.length = 0;
polygon.push(x3); polygon.push(x3);
@ -238,7 +239,7 @@ export class Triangulator {
// Remove empty polygons that resulted from the merge step above. // Remove empty polygons that resulted from the merge step above.
for (let i = convexPolygons.length - 1; i >= 0; i--) { for (let i = convexPolygons.length - 1; i >= 0; i--) {
polygon = convexPolygons[i]; polygon = convexPolygons[i];
if (polygon.length == 0) { if (polygon.length === 0) {
convexPolygons.splice(i, 1); convexPolygons.splice(i, 1);
this.polygonPool.free(polygon); this.polygonPool.free(polygon);
polygonIndices = convexPolygonsIndices[i] polygonIndices = convexPolygonsIndices[i]
@ -251,10 +252,10 @@ export class Triangulator {
} }
private static isConcave (index: number, vertexCount: number, vertices: NumberArrayLike, indices: NumberArrayLike): boolean { private static isConcave (index: number, vertexCount: number, vertices: NumberArrayLike, indices: NumberArrayLike): boolean {
let previous = indices[(vertexCount + index - 1) % vertexCount] << 1; const previous = indices[(vertexCount + index - 1) % vertexCount] << 1;
let current = indices[index] << 1; const current = indices[index] << 1;
let next = indices[(index + 1) % vertexCount] << 1; const next = indices[(index + 1) % vertexCount] << 1;
return !this.positiveArea(vertices[previous], vertices[previous + 1], vertices[current], vertices[current + 1], vertices[next], return !Triangulator.positiveArea(vertices[previous], vertices[previous + 1], vertices[current], vertices[current + 1], vertices[next],
vertices[next + 1]); vertices[next + 1]);
} }
@ -263,7 +264,7 @@ export class Triangulator {
} }
private static winding (p1x: number, p1y: number, p2x: number, p2y: number, p3x: number, p3y: number): number { private static winding (p1x: number, p1y: number, p2x: number, p2y: number, p3x: number, p3y: number): number {
let px = p2x - p1x, py = p2y - p1y; const px = p2x - p1x, py = p2y - p1y;
return p3x * py - p3y * px + px * p1y - p1x * py >= 0 ? 1 : -1; return p3x * py - p3y * px + px * p1y - p1x * py >= 0 ? 1 : -1;
} }
} }

View File

@ -27,24 +27,26 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { Skeleton } from "./Skeleton.js"; /** biome-ignore-all lint/complexity/noStaticOnlyClass: too much things to update */
import { MixBlend } from "./Animation.js";
import type { MixBlend } from "./Animation.js";
import type { Skeleton } from "./Skeleton.js";
export interface StringMap<T> { export interface StringMap<T> {
[key: string]: T; [key: string]: T;
} }
export class IntSet { export class IntSet {
array = new Array<number | undefined>(); array = [] as (number | undefined)[];
add (value: number): boolean { add (value: number): boolean {
let contains = this.contains(value); const contains = this.contains(value);
this.array[value | 0] = value | 0; this.array[value | 0] = value | 0;
return !contains; return !contains;
} }
contains (value: number) { contains (value: number) {
return this.array[value | 0] != undefined; return this.array[value | 0] !== undefined;
} }
remove (value: number) { remove (value: number) {
@ -61,7 +63,7 @@ export class StringSet {
size = 0; size = 0;
add (value: string): boolean { add (value: string): boolean {
let contains = this.entries[value]; const contains = this.entries[value];
this.entries[value] = true; this.entries[value] = true;
if (!contains) { if (!contains) {
this.size++; this.size++;
@ -71,10 +73,10 @@ export class StringSet {
} }
addAll (values: string[]): boolean { addAll (values: string[]): boolean {
let oldSize = this.size; const oldSize = this.size;
for (var i = 0, n = values.length; i < n; i++) for (let i = 0, n = values.length; i < n; i++)
this.add(values[i]); this.add(values[i]);
return oldSize != this.size; return oldSize !== this.size;
} }
contains (value: string) { contains (value: string) {
@ -125,11 +127,11 @@ export class Color {
} }
setFromString (hex: string) { setFromString (hex: string) {
hex = hex.charAt(0) == '#' ? hex.substr(1) : hex; hex = hex.charAt(0) === '#' ? hex.substr(1) : hex;
this.r = parseInt(hex.substr(0, 2), 16) / 255; this.r = parseInt(hex.substr(0, 2), 16) / 255;
this.g = parseInt(hex.substr(2, 2), 16) / 255; this.g = parseInt(hex.substr(2, 2), 16) / 255;
this.b = parseInt(hex.substr(4, 2), 16) / 255; this.b = parseInt(hex.substr(4, 2), 16) / 255;
this.a = hex.length != 8 ? 1 : parseInt(hex.substr(6, 2), 16) / 255; this.a = hex.length !== 8 ? 1 : parseInt(hex.substr(6, 2), 16) / 255;
return this; return this;
} }
@ -170,8 +172,8 @@ export class Color {
} }
toRgb888 () { toRgb888 () {
const hex = (x: number) => ("0" + (x * 255).toString(16)).slice(-2); const hex = (x: number) => (`0${(x * 255).toString(16)}`).slice(-2);
return Number("0x" + hex(this.r) + hex(this.g) + hex(this.b)); return Number(`0x${hex(this.r)}${hex(this.g)}${hex(this.b)}`);
} }
static fromString (hex: string, color = new Color()): Color { static fromString (hex: string, color = new Color()): Color {
@ -180,6 +182,7 @@ export class Color {
} }
export class MathUtils { export class MathUtils {
// biome-ignore lint/suspicious/noApproximativeNumericConstant: reference runtime
static PI = 3.1415927; static PI = 3.1415927;
static PI2 = MathUtils.PI * 2; static PI2 = MathUtils.PI * 2;
static invPI2 = 1 / MathUtils.PI2; static invPI2 = 1 / MathUtils.PI2;
@ -215,7 +218,7 @@ export class MathUtils {
} }
static cbrt (x: number) { static cbrt (x: number) {
let y = Math.pow(Math.abs(x), 1 / 3); const y = Math.pow(Math.abs(x), 1 / 3);
return x < 0 ? -y : y; return x < 0 ? -y : y;
} }
@ -224,8 +227,8 @@ export class MathUtils {
} }
static randomTriangularWith (min: number, max: number, mode: number): number { static randomTriangularWith (min: number, max: number, mode: number): number {
let u = Math.random(); const u = Math.random();
let d = max - min; const d = max - min;
if (u <= (mode - min) / d) return min + Math.sqrt(u * d * (mode - min)); if (u <= (mode - min) / d) return min + Math.sqrt(u * d * (mode - min));
return max - Math.sqrt((1 - u) * d * (max - mode)); return max - Math.sqrt((1 - u) * d * (max - mode));
} }
@ -252,7 +255,7 @@ export class Pow extends Interpolation {
applyInternal (a: number): number { applyInternal (a: number): number {
if (a <= 0.5) return Math.pow(a * 2, this.power) / 2; if (a <= 0.5) return Math.pow(a * 2, this.power) / 2;
return Math.pow((a - 1) * 2, this.power) / (this.power % 2 == 0 ? -2 : 2) + 1; return Math.pow((a - 1) * 2, this.power) / (this.power % 2 === 0 ? -2 : 2) + 1;
} }
} }
@ -262,7 +265,7 @@ export class PowOut extends Pow {
} }
applyInternal (a: number): number { applyInternal (a: number): number {
return Math.pow(a - 1, this.power) * (this.power % 2 == 0 ? -1 : 1) + 1; return Math.pow(a - 1, this.power) * (this.power % 2 === 0 ? -1 : 1) + 1;
} }
} }
@ -280,9 +283,10 @@ export class Utils {
array[i] = value; array[i] = value;
} }
// biome-ignore lint/suspicious/noExplicitAny: ok any in this case
static setArraySize<T> (array: Array<T>, size: number, value: any = 0): Array<T> { static setArraySize<T> (array: Array<T>, size: number, value: any = 0): Array<T> {
let oldSize = array.length; const oldSize = array.length;
if (oldSize == size) return array; if (oldSize === size) return array;
array.length = size; array.length = size;
if (oldSize < size) { if (oldSize < size) {
for (let i = oldSize; i < size; i++) array[i] = value; for (let i = oldSize; i < size; i++) array[i] = value;
@ -290,13 +294,14 @@ export class Utils {
return array; return array;
} }
// biome-ignore lint/suspicious/noExplicitAny: ok any in this case
static ensureArrayCapacity<T> (array: Array<T>, size: number, value: any = 0): Array<T> { static ensureArrayCapacity<T> (array: Array<T>, size: number, value: any = 0): Array<T> {
if (array.length >= size) return array; if (array.length >= size) return array;
return Utils.setArraySize(array, size, value); return Utils.setArraySize(array, size, value);
} }
static newArray<T> (size: number, defaultValue: T): Array<T> { static newArray<T> (size: number, defaultValue: T): Array<T> {
let array = new Array<T>(size); const array = new Array<T>(size);
for (let i = 0; i < size; i++) array[i] = defaultValue; for (let i = 0; i < size; i++) array[i] = defaultValue;
return array; return array;
} }
@ -305,7 +310,7 @@ export class Utils {
if (Utils.SUPPORTS_TYPED_ARRAYS) if (Utils.SUPPORTS_TYPED_ARRAYS)
return new Float32Array(size) return new Float32Array(size)
else { else {
let array = new Array<number>(size); const array = new Array<number>(size);
for (let i = 0; i < array.length; i++) array[i] = 0; for (let i = 0; i < array.length; i++) array[i] = 0;
return array; return array;
} }
@ -315,7 +320,7 @@ export class Utils {
if (Utils.SUPPORTS_TYPED_ARRAYS) if (Utils.SUPPORTS_TYPED_ARRAYS)
return new Int16Array(size) return new Int16Array(size)
else { else {
let array = new Array<number>(size); const array = new Array<number>(size);
for (let i = 0; i < array.length; i++) array[i] = 0; for (let i = 0; i < array.length; i++) array[i] = 0;
return array; return array;
} }
@ -334,11 +339,12 @@ export class Utils {
} }
static contains<T> (array: Array<T>, element: T, identity = true) { static contains<T> (array: Array<T>, element: T, identity = true) {
for (var i = 0; i < array.length; i++) for (let i = 0; i < array.length; i++)
if (array[i] == element) return true; if (array[i] === element) return true;
return false; return false;
} }
// biome-ignore lint/suspicious/noExplicitAny: ok any in this case
static enumValue (type: any, name: string) { static enumValue (type: any, name: string) {
return type[name[0].toUpperCase() + name.slice(1)]; return type[name[0].toUpperCase() + name.slice(1)];
} }
@ -347,14 +353,14 @@ export class Utils {
export class DebugUtils { export class DebugUtils {
static logBones (skeleton: Skeleton) { static logBones (skeleton: Skeleton) {
for (let i = 0; i < skeleton.bones.length; i++) { for (let i = 0; i < skeleton.bones.length; i++) {
let bone = skeleton.bones[i].applied; const bone = skeleton.bones[i].applied;
console.log(bone.bone.data.name + ", " + bone.a + ", " + bone.b + ", " + bone.c + ", " + bone.d + ", " + bone.worldX + ", " + bone.worldY); console.log(`${bone.bone.data.name}, ${bone.a}, ${bone.b}, ${bone.c}, ${bone.d}, ${bone.worldX}, ${bone.worldY}`);
} }
} }
} }
export class Pool<T> { export class Pool<T> {
private items = new Array<T>(); private items = [] as T[];
private instantiator: () => T; private instantiator: () => T;
constructor (instantiator: () => T) { constructor (instantiator: () => T) {
@ -362,11 +368,13 @@ export class Pool<T> {
} }
obtain () { obtain () {
// biome-ignore lint/style/noNonNullAssertion: length check
return this.items.length > 0 ? this.items.pop()! : this.instantiator(); return this.items.length > 0 ? this.items.pop()! : this.instantiator();
} }
free (item: T) { free (item: T) {
if ((item as any).reset) (item as any).reset(); // biome-ignore lint/suspicious/noExplicitAny: T can be anything
(item as any).reset?.();
this.items.push(item); this.items.push(item);
} }
@ -391,14 +399,14 @@ export class Vector2 {
} }
length () { length () {
let x = this.x; const x = this.x;
let y = this.y; const y = this.y;
return Math.sqrt(x * x + y * y); return Math.sqrt(x * x + y * y);
} }
normalize () { normalize () {
let len = this.length(); const len = this.length();
if (len != 0) { if (len !== 0) {
this.x /= len; this.x /= len;
this.y /= len; this.y /= len;
} }
@ -417,7 +425,7 @@ export class TimeKeeper {
private frameTime = 0; private frameTime = 0;
update () { update () {
let now = Date.now() / 1000; const now = Date.now() / 1000;
this.delta = now - this.lastTime; this.delta = now - this.lastTime;
this.frameTime += this.delta; this.frameTime += this.delta;
this.totalTime += this.delta; this.totalTime += this.delta;

View File

@ -27,9 +27,9 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { Skeleton } from "src/Skeleton.js"; import type { Skeleton } from "src/Skeleton.js";
import { Slot } from "../Slot.js"; import type { Slot } from "../Slot.js";
import { NumberArrayLike, Utils } from "../Utils.js"; import { type NumberArrayLike, Utils } from "../Utils.js";
/** The base class for all attachments. */ /** The base class for all attachments. */
export abstract class Attachment { export abstract class Attachment {
@ -88,17 +88,17 @@ export abstract class VertexAttachment extends Attachment {
stride: number) { stride: number) {
count = offset + (count >> 1) * stride; count = offset + (count >> 1) * stride;
let deformArray = slot.applied.deform; const deformArray = slot.applied.deform;
let vertices = this.vertices; let vertices = this.vertices;
let bones = this.bones; const bones = this.bones;
if (!bones) { if (!bones) {
if (deformArray.length > 0) vertices = deformArray; if (deformArray.length > 0) vertices = deformArray;
let bone = slot.bone.applied; const bone = slot.bone.applied;
let x = bone.worldX; const x = bone.worldX;
let y = bone.worldY; const y = bone.worldY;
let a = bone.a, b = bone.b, c = bone.c, d = bone.d; const a = bone.a, b = bone.b, c = bone.c, d = bone.d;
for (let v = start, w = offset; w < count; v += 2, w += stride) { for (let v = start, w = offset; w < count; v += 2, w += stride) {
let vx = vertices[v], vy = vertices[v + 1]; const vx = vertices[v], vy = vertices[v + 1];
worldVertices[w] = vx * a + vy * b + x; worldVertices[w] = vx * a + vy * b + x;
worldVertices[w + 1] = vx * c + vy * d + y; worldVertices[w + 1] = vx * c + vy * d + y;
} }
@ -106,19 +106,19 @@ export abstract class VertexAttachment extends Attachment {
} }
let v = 0, skip = 0; let v = 0, skip = 0;
for (let i = 0; i < start; i += 2) { for (let i = 0; i < start; i += 2) {
let n = bones[v]; const n = bones[v];
v += n + 1; v += n + 1;
skip += n; skip += n;
} }
let skeletonBones = skeleton.bones; const skeletonBones = skeleton.bones;
if (deformArray.length == 0) { if (deformArray.length === 0) {
for (let w = offset, b = skip * 3; w < count; w += stride) { for (let w = offset, b = skip * 3; w < count; w += stride) {
let wx = 0, wy = 0; let wx = 0, wy = 0;
let n = bones[v++]; let n = bones[v++];
n += v; n += v;
for (; v < n; v++, b += 3) { for (; v < n; v++, b += 3) {
let bone = skeletonBones[bones[v]].applied; const bone = skeletonBones[bones[v]].applied;
let vx = vertices[b], vy = vertices[b + 1], weight = vertices[b + 2]; const vx = vertices[b], vy = vertices[b + 1], weight = vertices[b + 2];
wx += (vx * bone.a + vy * bone.b + bone.worldX) * weight; wx += (vx * bone.a + vy * bone.b + bone.worldX) * weight;
wy += (vx * bone.c + vy * bone.d + bone.worldY) * weight; wy += (vx * bone.c + vy * bone.d + bone.worldY) * weight;
} }
@ -126,14 +126,14 @@ export abstract class VertexAttachment extends Attachment {
worldVertices[w + 1] = wy; worldVertices[w + 1] = wy;
} }
} else { } else {
let deform = deformArray; const deform = deformArray;
for (let w = offset, b = skip * 3, f = skip << 1; w < count; w += stride) { for (let w = offset, b = skip * 3, f = skip << 1; w < count; w += stride) {
let wx = 0, wy = 0; let wx = 0, wy = 0;
let n = bones[v++]; let n = bones[v++];
n += v; n += v;
for (; v < n; v++, b += 3, f += 2) { for (; v < n; v++, b += 3, f += 2) {
let bone = skeletonBones[bones[v]].applied; const bone = skeletonBones[bones[v]].applied;
let vx = vertices[b] + deform[f], vy = vertices[b + 1] + deform[f + 1], weight = vertices[b + 2]; const vx = vertices[b] + deform[f], vy = vertices[b + 1] + deform[f + 1], weight = vertices[b + 2];
wx += (vx * bone.a + vy * bone.b + bone.worldX) * weight; wx += (vx * bone.a + vy * bone.b + bone.worldX) * weight;
wy += (vx * bone.c + vy * bone.d + bone.worldY) * weight; wy += (vx * bone.c + vy * bone.d + bone.worldY) * weight;
} }

View File

@ -27,14 +27,14 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { Skin } from "../Skin.js"; import type { Skin } from "../Skin.js";
import { BoundingBoxAttachment } from "./BoundingBoxAttachment.js"; import type { BoundingBoxAttachment } from "./BoundingBoxAttachment.js";
import { ClippingAttachment } from "./ClippingAttachment.js"; import type { ClippingAttachment } from "./ClippingAttachment.js";
import { MeshAttachment } from "./MeshAttachment.js"; import type { MeshAttachment } from "./MeshAttachment.js";
import { PathAttachment } from "./PathAttachment.js"; import type { PathAttachment } from "./PathAttachment.js";
import { PointAttachment } from "./PointAttachment.js"; import type { PointAttachment } from "./PointAttachment.js";
import { RegionAttachment } from "./RegionAttachment.js"; import type { RegionAttachment } from "./RegionAttachment.js";
import { Sequence } from "./Sequence.js"; import type { Sequence } from "./Sequence.js";
/** The interface which can be implemented to customize creating and populating attachments. /** The interface which can be implemented to customize creating and populating attachments.
* *

View File

@ -28,7 +28,7 @@
*****************************************************************************/ *****************************************************************************/
import { Color } from "../Utils.js"; import { Color } from "../Utils.js";
import { VertexAttachment, Attachment } from "./Attachment.js"; import { type Attachment, VertexAttachment } from "./Attachment.js";
/** An attachment with vertices that make up a polygon. Can be used for hit detection, creating physics bodies, spawning particle /** An attachment with vertices that make up a polygon. Can be used for hit detection, creating physics bodies, spawning particle
* effects, and more. * effects, and more.
@ -43,7 +43,7 @@ export class BoundingBoxAttachment extends VertexAttachment {
} }
copy (): Attachment { copy (): Attachment {
let copy = new BoundingBoxAttachment(this.name); const copy = new BoundingBoxAttachment(this.name);
this.copyTo(copy); this.copyTo(copy);
copy.color.setFromColor(this.color); copy.color.setFromColor(this.color);
return copy; return copy;

View File

@ -27,9 +27,9 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { SlotData } from "../SlotData.js"; import type { SlotData } from "../SlotData.js";
import { Color } from "../Utils.js"; import { Color } from "../Utils.js";
import { VertexAttachment, Attachment } from "./Attachment.js"; import { type Attachment, VertexAttachment } from "./Attachment.js";
/** An attachment with vertices that make up a polygon used for clipping the rendering of other attachments. */ /** An attachment with vertices that make up a polygon used for clipping the rendering of other attachments. */
export class ClippingAttachment extends VertexAttachment { export class ClippingAttachment extends VertexAttachment {
@ -47,7 +47,7 @@ export class ClippingAttachment extends VertexAttachment {
} }
copy (): Attachment { copy (): Attachment {
let copy = new ClippingAttachment(this.name); const copy = new ClippingAttachment(this.name);
this.copyTo(copy); this.copyTo(copy);
copy.endSlot = this.endSlot; copy.endSlot = this.endSlot;
copy.color.setFromColor(this.color); copy.color.setFromColor(this.color);

View File

@ -27,9 +27,9 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { TextureRegion } from "../Texture.js" import type { TextureRegion } from "../Texture.js"
import { Color } from "../Utils.js" import type { Color } from "../Utils.js"
import { Sequence } from "./Sequence.js" import type { Sequence } from "./Sequence.js"
export interface HasTextureRegion { export interface HasTextureRegion {
/** The name used to find the {@link #region()}. */ /** The name used to find the {@link #region()}. */

View File

@ -27,14 +27,14 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { TextureRegion } from "../Texture.js"; import type { Skeleton } from "src/Skeleton.js";
import type { Slot } from "../Slot.js";
import type { TextureRegion } from "../Texture.js";
import { TextureAtlasRegion } from "../TextureAtlas.js"; import { TextureAtlasRegion } from "../TextureAtlas.js";
import { Color, NumberArrayLike, Utils } from "../Utils.js"; import { Color, type NumberArrayLike, Utils } from "../Utils.js";
import { VertexAttachment, Attachment } from "./Attachment.js"; import { type Attachment, VertexAttachment } from "./Attachment.js";
import { HasTextureRegion } from "./HasTextureRegion.js"; import type { HasTextureRegion } from "./HasTextureRegion.js";
import { Sequence } from "./Sequence.js"; import type { Sequence } from "./Sequence.js";
import { Slot } from "../Slot.js";
import { Skeleton } from "src/Skeleton.js";
/** An attachment that displays a textured mesh. A mesh has hull vertices and internal vertices within the hull. Holes are not /** An attachment that displays a textured mesh. A mesh has hull vertices and internal vertices within the hull. Holes are not
* supported. Each vertex has UVs (texture coordinates) and triangles are used to map an image on to the mesh. * supported. Each vertex has UVs (texture coordinates) and triangles are used to map an image on to the mesh.
@ -88,14 +88,14 @@ export class MeshAttachment extends VertexAttachment implements HasTextureRegion
* the {@link #regionUVs} are changed. */ * the {@link #regionUVs} are changed. */
updateRegion () { updateRegion () {
if (!this.region) throw new Error("Region not set."); if (!this.region) throw new Error("Region not set.");
let regionUVs = this.regionUVs; const regionUVs = this.regionUVs;
if (!this.uvs || this.uvs.length != regionUVs.length) this.uvs = Utils.newFloatArray(regionUVs.length); if (!this.uvs || this.uvs.length !== regionUVs.length) this.uvs = Utils.newFloatArray(regionUVs.length);
let uvs = this.uvs; const uvs = this.uvs;
let n = this.uvs.length; const n = this.uvs.length;
let u = this.region.u, v = this.region.v, width = 0, height = 0; let u = this.region.u, v = this.region.v, width = 0, height = 0;
if (this.region instanceof TextureAtlasRegion) { if (this.region instanceof TextureAtlasRegion) {
let region = this.region, page = region.page; const region = this.region, page = region.page;
let textureWidth = page.width, textureHeight = page.height; const textureWidth = page.width, textureHeight = page.height;
switch (region.degrees) { switch (region.degrees) {
case 90: case 90:
u -= (region.originalHeight - region.offsetY - region.height) / textureWidth; u -= (region.originalHeight - region.offsetY - region.height) / textureWidth;
@ -171,7 +171,7 @@ export class MeshAttachment extends VertexAttachment implements HasTextureRegion
copy (): Attachment { copy (): Attachment {
if (this.parentMesh) return this.newLinkedMesh(); if (this.parentMesh) return this.newLinkedMesh();
let copy = new MeshAttachment(this.name, this.path); const copy = new MeshAttachment(this.name, this.path);
copy.region = this.region; copy.region = this.region;
copy.color.setFromColor(this.color); copy.color.setFromColor(this.color);
@ -204,7 +204,7 @@ export class MeshAttachment extends VertexAttachment implements HasTextureRegion
/** Returns a new mesh with the {@link #parentMesh} set to this mesh's parent mesh, if any, else to this mesh. **/ /** Returns a new mesh with the {@link #parentMesh} set to this mesh's parent mesh, if any, else to this mesh. **/
newLinkedMesh (): MeshAttachment { newLinkedMesh (): MeshAttachment {
let copy = new MeshAttachment(this.name, this.path); const copy = new MeshAttachment(this.name, this.path);
copy.region = this.region; copy.region = this.region;
copy.color.setFromColor(this.color); copy.color.setFromColor(this.color);
copy.timelineAttachment = this.timelineAttachment; copy.timelineAttachment = this.timelineAttachment;

View File

@ -28,7 +28,7 @@
*****************************************************************************/ *****************************************************************************/
import { Color, Utils } from "../Utils.js"; import { Color, Utils } from "../Utils.js";
import { VertexAttachment, Attachment } from "./Attachment.js"; import { type Attachment, VertexAttachment } from "./Attachment.js";
/** An attachment whose vertices make up a composite Bezier curve. /** An attachment whose vertices make up a composite Bezier curve.
* *
@ -54,7 +54,7 @@ export class PathAttachment extends VertexAttachment {
} }
copy (): Attachment { copy (): Attachment {
let copy = new PathAttachment(this.name); const copy = new PathAttachment(this.name);
this.copyTo(copy); this.copyTo(copy);
copy.lengths = new Array<number>(this.lengths.length); copy.lengths = new Array<number>(this.lengths.length);
Utils.arrayCopy(this.lengths, 0, copy.lengths, 0, this.lengths.length); Utils.arrayCopy(this.lengths, 0, copy.lengths, 0, this.lengths.length);

View File

@ -27,9 +27,9 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { BonePose } from "src/BonePose.js"; import type { BonePose } from "src/BonePose.js";
import { Color, Vector2, MathUtils } from "../Utils.js"; import { Color, MathUtils, type Vector2 } from "../Utils.js";
import { VertexAttachment, Attachment } from "./Attachment.js"; import { type Attachment, VertexAttachment } from "./Attachment.js";
/** An attachment which is a single point and a rotation. This can be used to spawn projectiles, particles, etc. A bone can be /** An attachment which is a single point and a rotation. This can be used to spawn projectiles, particles, etc. A bone can be
* used in similar ways, but a PointAttachment is slightly less expensive to compute and can be hidden, shown, and placed in a * used in similar ways, but a PointAttachment is slightly less expensive to compute and can be hidden, shown, and placed in a
@ -63,7 +63,7 @@ export class PointAttachment extends VertexAttachment {
} }
copy (): Attachment { copy (): Attachment {
let copy = new PointAttachment(this.name); const copy = new PointAttachment(this.name);
copy.x = this.x; copy.x = this.x;
copy.y = this.y; copy.y = this.y;
copy.rotation = this.rotation; copy.rotation = this.rotation;

View File

@ -27,12 +27,12 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { TextureRegion } from "../Texture.js"; import type { Slot } from "../Slot.js";
import { Color, MathUtils, NumberArrayLike, Utils } from "../Utils.js"; import type { TextureRegion } from "../Texture.js";
import { Color, MathUtils, type NumberArrayLike, Utils } from "../Utils.js";
import { Attachment } from "./Attachment.js"; import { Attachment } from "./Attachment.js";
import { HasTextureRegion } from "./HasTextureRegion.js"; import type { HasTextureRegion } from "./HasTextureRegion.js";
import { Sequence } from "./Sequence.js"; import type { Sequence } from "./Sequence.js";
import { Slot } from "../Slot.js";
/** An attachment that displays a textured quadrilateral. /** An attachment that displays a textured quadrilateral.
* *
@ -85,27 +85,27 @@ export class RegionAttachment extends Attachment implements HasTextureRegion {
/** Calculates the {@link #offset} using the region settings. Must be called after changing region settings. */ /** Calculates the {@link #offset} using the region settings. Must be called after changing region settings. */
updateRegion (): void { updateRegion (): void {
if (!this.region) throw new Error("Region not set."); if (!this.region) throw new Error("Region not set.");
let region = this.region; const region = this.region;
let uvs = this.uvs; const uvs = this.uvs;
let regionScaleX = this.width / this.region.originalWidth * this.scaleX; const regionScaleX = this.width / this.region.originalWidth * this.scaleX;
let regionScaleY = this.height / this.region.originalHeight * this.scaleY; const regionScaleY = this.height / this.region.originalHeight * this.scaleY;
let localX = -this.width / 2 * this.scaleX + this.region.offsetX * regionScaleX; const localX = -this.width / 2 * this.scaleX + this.region.offsetX * regionScaleX;
let localY = -this.height / 2 * this.scaleY + this.region.offsetY * regionScaleY; const localY = -this.height / 2 * this.scaleY + this.region.offsetY * regionScaleY;
let localX2 = localX + this.region.width * regionScaleX; const localX2 = localX + this.region.width * regionScaleX;
let localY2 = localY + this.region.height * regionScaleY; const localY2 = localY + this.region.height * regionScaleY;
let radians = this.rotation * MathUtils.degRad; const radians = this.rotation * MathUtils.degRad;
let cos = Math.cos(radians); const cos = Math.cos(radians);
let sin = Math.sin(radians); const sin = Math.sin(radians);
let x = this.x, y = this.y; const x = this.x, y = this.y;
let localXCos = localX * cos + x; const localXCos = localX * cos + x;
let localXSin = localX * sin; const localXSin = localX * sin;
let localYCos = localY * cos + y; const localYCos = localY * cos + y;
let localYSin = localY * sin; const localYSin = localY * sin;
let localX2Cos = localX2 * cos + x; const localX2Cos = localX2 * cos + x;
let localX2Sin = localX2 * sin; const localX2Sin = localX2 * sin;
let localY2Cos = localY2 * cos + y; const localY2Cos = localY2 * cos + y;
let localY2Sin = localY2 * sin; const localY2Sin = localY2 * sin;
let offset = this.offset; const offset = this.offset;
offset[0] = localXCos - localYSin; offset[0] = localXCos - localYSin;
offset[1] = localYCos + localXSin; offset[1] = localYCos + localXSin;
offset[2] = localXCos - localY2Sin; offset[2] = localXCos - localY2Sin;
@ -124,7 +124,7 @@ export class RegionAttachment extends Attachment implements HasTextureRegion {
uvs[5] = 1; uvs[5] = 1;
uvs[6] = 1; uvs[6] = 1;
uvs[7] = 0; uvs[7] = 0;
} else if (region.degrees == 90) { } else if (region.degrees === 90) {
uvs[0] = region.u2; uvs[0] = region.u2;
uvs[1] = region.v2; uvs[1] = region.v2;
uvs[2] = region.u; uvs[2] = region.u;
@ -156,10 +156,10 @@ export class RegionAttachment extends Attachment implements HasTextureRegion {
computeWorldVertices (slot: Slot, worldVertices: NumberArrayLike, offset: number, stride: number) { computeWorldVertices (slot: Slot, worldVertices: NumberArrayLike, offset: number, stride: number) {
if (this.sequence) this.sequence.apply(slot.applied, this); if (this.sequence) this.sequence.apply(slot.applied, this);
let bone = slot.bone.applied; const bone = slot.bone.applied;
let vertexOffset = this.offset; const vertexOffset = this.offset;
let x = bone.worldX, y = bone.worldY; const x = bone.worldX, y = bone.worldY;
let a = bone.a, b = bone.b, c = bone.c, d = bone.d; const a = bone.a, b = bone.b, c = bone.c, d = bone.d;
let offsetX = 0, offsetY = 0; let offsetX = 0, offsetY = 0;
offsetX = vertexOffset[0]; offsetX = vertexOffset[0];
@ -187,7 +187,7 @@ export class RegionAttachment extends Attachment implements HasTextureRegion {
} }
copy (): Attachment { copy (): Attachment {
let copy = new RegionAttachment(this.name, this.path); const copy = new RegionAttachment(this.name, this.path);
copy.region = this.region; copy.region = this.region;
copy.x = this.x; copy.x = this.x;
copy.y = this.y; copy.y = this.y;

View File

@ -29,10 +29,9 @@
(() => { (() => {
if (typeof Math.fround === "undefined") { if (typeof Math.fround === "undefined") {
Math.fround = (function (array) { Math.fround = ((array) => (x: number) => {
return function (x: number) { array[0] = x;
return array[0] = x, array[0]; return array[0];
};
})(new Float32Array(1)); })(new Float32Array(1));
} }
})(); })();

View File

@ -919,7 +919,7 @@ export class SpinePlayer implements Disposable {
// Draw the background image. // Draw the background image.
let bgImage = config.backgroundImage; let bgImage = config.backgroundImage;
if (bgImage) { if (bgImage) {
let texture = this.assetManager!.require(bgImage.url); let texture = this.assetManager!.require(bgImage.url) as GLTexture;
if (bgImage.x !== void 0 && bgImage.y !== void 0 && bgImage.width && bgImage.height) if (bgImage.x !== void 0 && bgImage.y !== void 0 && bgImage.width && bgImage.height)
renderer.drawTexture(texture, bgImage.x, bgImage.y, bgImage.width, bgImage.height); renderer.drawTexture(texture, bgImage.x, bgImage.y, bgImage.width, bgImage.height);
else else

View File

@ -978,7 +978,8 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable
const skeletonLoader = isBinary ? new SkeletonBinary(atlasLoader) : new SkeletonJson(atlasLoader); const skeletonLoader = isBinary ? new SkeletonBinary(atlasLoader) : new SkeletonJson(atlasLoader);
skeletonLoader.scale = scale; skeletonLoader.scale = scale;
const skeletonFileAsset = this.overlay.assetManager.require(skeletonPath); // biome-ignore lint/suspicious/noExplicitAny: it is any untile we have a json schema
const skeletonFileAsset = this.overlay.assetManager.require(skeletonPath) as Record<string, any>;
const skeletonFile = this.jsonSkeletonKey ? skeletonFileAsset[this.jsonSkeletonKey] : skeletonFileAsset; const skeletonFile = this.jsonSkeletonKey ? skeletonFileAsset[this.jsonSkeletonKey] : skeletonFileAsset;
const skeletonData = (skeletonDataInput || this.skeleton?.data) ?? skeletonLoader.readSkeletonData(skeletonFile); const skeletonData = (skeletonDataInput || this.skeleton?.data) ?? skeletonLoader.readSkeletonData(skeletonFile);