[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,
"rules": {
"recommended": true,
"complexity": {
"noUselessConstructor": "off"
},
"correctness": {
"noUnusedFunctionParameters": "off"
},
"style": {
"useExponentiationOperator": "off"
},
"suspicious": {
"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.
*****************************************************************************/
import { Animation, MixBlend, AttachmentTimeline, MixDirection, RotateTimeline, DrawOrderTimeline, Timeline, EventTimeline } from "./Animation.js";
import { AnimationStateData } from "./AnimationStateData.js";
import { Skeleton } from "./Skeleton.js";
import { Slot } from "./Slot.js";
import { StringSet, Pool, Utils, MathUtils } from "./Utils.js";
import { Event } from "./Event.js";
/** biome-ignore-all lint/style/noNonNullAssertion: reference runtime expects some nullable to not be null */
import { Animation, AttachmentTimeline, DrawOrderTimeline, EventTimeline, MixBlend, MixDirection, RotateTimeline, Timeline } from "./Animation.js";
import type { AnimationStateData } from "./AnimationStateData.js";
import type { 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
@ -46,7 +48,7 @@ export class AnimationState {
data: AnimationStateData;
/** 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
* or faster. Defaults to 1.
@ -55,8 +57,8 @@ export class AnimationState {
timeScale = 1;
unkeyedState = 0;
readonly events = new Array<Event>();
readonly listeners = new Array<AnimationStateListener>();
readonly events = [] as Event[];
readonly listeners = [] as AnimationStateListener[];
queue = new EventQueue(this);
propertyIDs = new StringSet();
animationsChanged = false;
@ -70,9 +72,9 @@ export class AnimationState {
/** Increments each track entry {@link TrackEntry#trackTime()}, setting queued animations as current if needed. */
update (delta: number) {
delta *= this.timeScale;
let tracks = this.tracks;
const tracks = this.tracks;
for (let i = 0, n = tracks.length; i < n; i++) {
let current = tracks[i];
const current = tracks[i];
if (!current) continue;
current.animationLast = current.nextAnimationLast;
@ -90,10 +92,10 @@ export class AnimationState {
let next = current.next;
if (next) {
// 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) {
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;
this.setCurrent(i, next, true);
while (next.mixingFrom) {
@ -127,18 +129,18 @@ export class AnimationState {
/** Returns true when all mixing from entries are complete. */
updateMixingFrom (to: TrackEntry, delta: number): boolean {
let from = to.mixingFrom;
const from = to.mixingFrom;
if (!from) return true;
let finished = this.updateMixingFrom(from, delta);
const finished = this.updateMixingFrom(from, delta);
from.animationLast = from.nextAnimationLast;
from.trackLast = from.nextTrackLast;
// 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.
if (from.totalAlpha == 0 || to.mixDuration == 0) {
if (from.totalAlpha === 0 || to.mixDuration === 0) {
to.mixingFrom = from.mixingFrom;
if (from.mixingFrom != null) from.mixingFrom.mixingTo = to;
to.interruptAlpha = from.interruptAlpha;
@ -159,15 +161,15 @@ export class AnimationState {
if (!skeleton) throw new Error("skeleton cannot be null.");
if (this.animationsChanged) this._animationsChanged();
let events = this.events;
let tracks = this.tracks;
const events = this.events;
const tracks = this.tracks;
let applied = false;
for (let i = 0, n = tracks.length; i < n; i++) {
let current = tracks[i];
const current = tracks[i];
if (!current || current.delay > 0) continue;
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.
let alpha = current.alpha;
@ -185,10 +187,10 @@ export class AnimationState {
applyTime = current.animation!.duration - applyTime;
applyEvents = null;
}
let timelines = current.animation!.timelines;
let timelineCount = timelines.length;
if ((i == 0 && alpha == 1) || blend == MixBlend.add) {
if (i == 0) attachments = true;
const timelines = current.animation!.timelines;
const timelineCount = timelines.length;
if ((i === 0 && alpha === 1) || blend === MixBlend.add) {
if (i === 0) attachments = true;
for (let ii = 0; ii < timelineCount; ii++) {
// Fixes issue #302 on IOS9 where mix, blend sometimes became undefined and caused assets
// to sometimes stop rendering when using color correction, as their RGBA values become NaN.
@ -201,15 +203,15 @@ export class AnimationState {
timeline.apply(skeleton, animationLast, applyTime, applyEvents, alpha, blend, MixDirection.in, false);
}
} else {
let timelineMode = current.timelineMode;
const timelineMode = current.timelineMode;
let shortestRotation = current.shortestRotation;
let firstFrame = !shortestRotation && current.timelinesRotation.length != timelineCount << 1;
const shortestRotation = current.shortestRotation;
const firstFrame = !shortestRotation && current.timelinesRotation.length !== timelineCount << 1;
if (firstFrame) current.timelinesRotation.length = timelineCount << 1;
for (let ii = 0; ii < timelineCount; ii++) {
let timeline = timelines[ii];
let timelineBlend = timelineMode[ii] == SUBSEQUENT ? blend : MixBlend.setup;
const timeline = timelines[ii];
const timelineBlend = timelineMode[ii] === SUBSEQUENT ? blend : MixBlend.setup;
if (!shortestRotation && timeline instanceof RotateTimeline) {
this.applyRotateTimeline(timeline, skeleton, applyTime, alpha, timelineBlend, current.timelinesRotation, ii << 1, firstFrame);
} else if (timeline instanceof AttachmentTimeline) {
@ -234,7 +236,7 @@ export class AnimationState {
const slots = skeleton.slots;
for (let i = 0, n = skeleton.slots.length; i < n; i++) {
const slot = slots[i];
if (slot.attachmentState == setupState) {
if (slot.attachmentState === setupState) {
const attachmentName = slot.data.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) {
let from = to.mixingFrom!;
const from = to.mixingFrom!;
if (from.mixingFrom) this.applyMixingFrom(from, skeleton, blend);
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;
if (blend == MixBlend.first) blend = MixBlend.setup;
if (blend === MixBlend.first) blend = MixBlend.setup;
} else {
mix = to.mixTime / to.mixDuration;
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;
let timelines = from.animation!.timelines;
let timelineCount = timelines.length;
let alphaHold = from.alpha * to.interruptAlpha, alphaMix = alphaHold * (1 - mix);
const attachments = mix < from.mixAttachmentThreshold, drawOrder = mix < from.mixDrawOrderThreshold;
const timelines = from.animation!.timelines;
const timelineCount = timelines.length;
const alphaHold = from.alpha * to.interruptAlpha, alphaMix = alphaHold * (1 - mix);
let animationLast = from.animationLast, animationTime = from.getAnimationTime(), applyTime = animationTime;
let events = null;
if (from.reverse)
@ -270,20 +272,20 @@ export class AnimationState {
else if (mix < from.eventThreshold)
events = this.events;
if (blend == MixBlend.add) {
if (blend === MixBlend.add) {
for (let i = 0; i < timelineCount; i++)
timelines[i].apply(skeleton, animationLast, applyTime, events, alphaMix, blend, MixDirection.out, false);
} else {
let timelineMode = from.timelineMode;
let timelineHoldMix = from.timelineHoldMix;
const timelineMode = from.timelineMode;
const timelineHoldMix = from.timelineHoldMix;
let shortestRotation = from.shortestRotation;
let firstFrame = !shortestRotation && from.timelinesRotation.length != timelineCount << 1;
const shortestRotation = from.shortestRotation;
const firstFrame = !shortestRotation && from.timelinesRotation.length !== timelineCount << 1;
if (firstFrame) from.timelinesRotation.length = timelineCount << 1;
from.totalAlpha = 0;
for (let i = 0; i < timelineCount; i++) {
let timeline = timelines[i];
const timeline = timelines[i];
let direction = MixDirection.out;
let timelineBlend: MixBlend;
let alpha = 0;
@ -305,11 +307,12 @@ export class AnimationState {
timelineBlend = MixBlend.setup;
alpha = alphaHold;
break;
default: // HOLD_MIX
default: { // HOLD_MIX
timelineBlend = MixBlend.setup;
let holdMix = timelineHoldMix[i];
const holdMix = timelineHoldMix[i];
alpha = alphaHold * Math.max(0, 1 - holdMix.mixTime / holdMix.mixDuration);
break;
}
}
from.totalAlpha += alpha;
@ -320,7 +323,7 @@ export class AnimationState {
else {
// This fixes the WebKit 602 specific issue described at https://esotericsoftware.com/forum/d/10109-ios-10-disappearing-graphics
Utils.webkit602BugfixHelper(alpha, blend);
if (drawOrder && timeline instanceof DrawOrderTimeline && timelineBlend == MixBlend.setup)
if (drawOrder && timeline instanceof DrawOrderTimeline && timelineBlend === MixBlend.setup)
direction = MixDirection.in;
timeline.apply(skeleton, animationLast, applyTime, events, alpha, timelineBlend, direction, false);
}
@ -340,7 +343,7 @@ export class AnimationState {
if (!slot.bone.active) return;
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);
} else
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 (alpha == 1) {
if (alpha === 1) {
timeline.apply(skeleton, 0, time, null, 1, blend, MixDirection.in, false);
return;
}
let bone = skeleton.bones[timeline.boneIndex];
const bone = skeleton.bones[timeline.boneIndex];
if (!bone.active) return;
const pose = bone.pose, setup = bone.data.setup;
let frames = timeline.frames;
const frames = timeline.frames;
let r1 = 0, r2 = 0;
if (time < frames[0]) {
switch (blend) {
// biome-ignore lint/suspicious/noFallthroughSwitchClause: reference runtime does fall through
case MixBlend.setup:
pose.rotation = setup.rotation;
// biome-ignore lint/suspicious/useDefaultSwitchClauseLast: needed for fall through
default:
return;
case MixBlend.first:
@ -380,14 +385,14 @@ export class AnimationState {
r2 = setup.rotation;
}
} else {
r1 = blend == MixBlend.setup ? setup.rotation : pose.rotation;
r1 = blend === MixBlend.setup ? setup.rotation : pose.rotation;
r2 = setup.rotation + timeline.getCurveValue(time);
}
// Mix between rotations using the direction of the shortest route on the first frame while detecting crosses.
let total = 0, diff = r2 - r1;
diff -= Math.ceil(diff / 360 - 0.5) * 360;
if (diff == 0) {
if (diff === 0) {
total = timelinesRotation[i];
} else {
let lastTotal = 0, lastDiff = 0;
@ -398,19 +403,19 @@ export class AnimationState {
lastTotal = timelinesRotation[i];
lastDiff = timelinesRotation[i + 1];
}
let loops = lastTotal - lastTotal % 360;
const loops = lastTotal - lastTotal % 360;
total = diff + loops;
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) {
total += 360 * MathUtils.signum(lastTotal);
dir = current;
} else if (loops != 0)
} else if (loops !== 0)
total -= 360 * MathUtils.signum(lastTotal);
else
dir = current;
}
if (dir != current) total += 360 * MathUtils.signum(lastTotal);
if (dir !== current) total += 360 * MathUtils.signum(lastTotal);
timelinesRotation[i] = total;
}
timelinesRotation[i + 1] = diff;
@ -418,15 +423,15 @@ export class AnimationState {
}
queueEvents (entry: TrackEntry, animationTime: number) {
let animationStart = entry.animationStart, animationEnd = entry.animationEnd;
let duration = animationEnd - animationStart;
let trackLastWrapped = entry.trackLast % duration;
const animationStart = entry.animationStart, animationEnd = entry.animationEnd;
const duration = animationEnd - animationStart;
const trackLastWrapped = entry.trackLast % duration;
// Queue events before complete.
let events = this.events;
const events = this.events;
let i = 0, n = events.length;
for (; i < n; i++) {
let event = events[i];
const event = events[i];
if (event.time < trackLastWrapped) break;
if (event.time > animationEnd) continue; // Discard events outside animation start/end.
this.queue.event(entry, event);
@ -435,7 +440,7 @@ export class AnimationState {
// Queue complete if completed a loop iteration or the animation.
let complete = false;
if (entry.loop) {
if (duration == 0)
if (duration === 0)
complete = true;
else {
const cycles = Math.floor(entry.trackTime / duration);
@ -447,7 +452,7 @@ export class AnimationState {
// Queue events after complete.
for (; i < n; i++) {
let event = events[i];
const event = events[i];
if (event.time < animationStart) continue; // Discard events outside animation start/end.
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,
* rather than leaving them in their current pose. */
clearTracks () {
let oldDrainDisabled = this.queue.drainDisabled;
const oldDrainDisabled = this.queue.drainDisabled;
this.queue.drainDisabled = true;
for (let i = 0, n = this.tracks.length; i < n; i++)
this.clearTrack(i);
@ -473,7 +478,7 @@ export class AnimationState {
* rather than leaving them in their current pose. */
clearTrack (trackIndex: number) {
if (trackIndex >= this.tracks.length) return;
let current = this.tracks[trackIndex];
const current = this.tracks[trackIndex];
if (!current) return;
this.queue.end(current);
@ -482,7 +487,7 @@ export class AnimationState {
let entry = current;
while (true) {
let from = entry.mixingFrom;
const from = entry.mixingFrom;
if (!from) break;
this.queue.end(from);
entry.mixingFrom = null;
@ -496,7 +501,7 @@ export class AnimationState {
}
setCurrent (index: number, current: TrackEntry, interrupt: boolean) {
let from = this.expandToIndex(index);
const from = this.expandToIndex(index);
this.tracks[index] = current;
current.previous = null;
@ -538,8 +543,8 @@ export class AnimationState {
}
private setAnimation1 (trackIndex: number, animationName: string, loop: boolean = false) {
let animation = this.data.skeletonData.findAnimation(animationName);
if (!animation) throw new Error("Animation not found: " + animationName);
const animation = this.data.skeletonData.findAnimation(animationName);
if (!animation) throw new Error(`Animation not found: ${animationName}`);
return this.setAnimation2(trackIndex, animation, loop);
}
@ -568,7 +573,7 @@ export class AnimationState {
} else
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.queue.drain();
return entry;
@ -596,8 +601,8 @@ export class AnimationState {
}
private addAnimation1 (trackIndex: number, animationName: string, loop: boolean = false, delay: number = 0) {
let animation = this.data.skeletonData.findAnimation(animationName);
if (!animation) throw new Error("Animation not found: " + animationName);
const animation = this.data.skeletonData.findAnimation(animationName);
if (!animation) throw new Error(`Animation not found: ${animationName}`);
return this.addAnimation2(trackIndex, animation, loop, delay);
}
@ -610,7 +615,7 @@ export class AnimationState {
last = last.next;
}
let entry = this.trackEntry(trackIndex, animation, loop, last);
const entry = this.trackEntry(trackIndex, animation, loop, last);
if (!last) {
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
* Runtimes Guide. */
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.trackEnd = mixDuration;
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
* after the {@link AnimationStateListener#dispose(TrackEntry)} event occurs. */
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);
entry.mixDuration = 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
* Runtimes Guide. */
setEmptyAnimations (mixDuration: number = 0) {
let oldDrainDisabled = this.queue.drainDisabled;
const oldDrainDisabled = this.queue.drainDisabled;
this.queue.drainDisabled = true;
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);
}
this.queue.drainDisabled = oldDrainDisabled;
@ -695,7 +700,7 @@ export class AnimationState {
/** @param last May be null. */
trackEntry (trackIndex: number, animation: Animation, loop: boolean, last: TrackEntry | null) {
let entry = this.trackEntryPool.obtain();
const entry = this.trackEntryPool.obtain();
entry.reset();
entry.trackIndex = trackIndex;
entry.animation = animation;
@ -745,30 +750,30 @@ export class AnimationState {
this.animationsChanged = false;
this.propertyIDs.clear();
let tracks = this.tracks;
const tracks = this.tracks;
for (let i = 0, n = tracks.length; i < n; i++) {
let entry = tracks[i];
if (!entry) continue;
while (entry.mixingFrom)
entry = entry.mixingFrom;
do {
if (!entry.mixingTo || entry.mixBlend != MixBlend.add) this.computeHold(entry);
if (!entry.mixingTo || entry.mixBlend !== MixBlend.add) this.computeHold(entry);
entry = entry.mixingTo;
} while (entry);
}
}
computeHold (entry: TrackEntry) {
let to = entry.mixingTo;
let timelines = entry.animation!.timelines;
let timelinesCount = entry.animation!.timelines.length;
let timelineMode = entry.timelineMode;
const to = entry.mixingTo;
const timelines = entry.animation!.timelines;
const timelinesCount = entry.animation!.timelines.length;
const timelineMode = entry.timelineMode;
timelineMode.length = timelinesCount;
let timelineHoldMix = entry.timelineHoldMix;
const timelineHoldMix = entry.timelineHoldMix;
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++)
timelineMode[i] = propertyIDs.addAll(timelines[i].getPropertyIds()) ? HOLD_FIRST : HOLD_SUBSEQUENT;
return;
@ -776,8 +781,8 @@ export class AnimationState {
outer:
for (let i = 0; i < timelinesCount; i++) {
let timeline = timelines[i];
let ids = timeline.getPropertyIds();
const timeline = timelines[i];
const ids = timeline.getPropertyIds();
if (!propertyIDs.addAll(ids))
timelineMode[i] = SUBSEQUENT;
else if (!to || timeline instanceof AttachmentTimeline || timeline instanceof DrawOrderTimeline
@ -812,7 +817,7 @@ export class AnimationState {
/** Removes the listener added with {@link #addListener()}. */
removeListener (listener: AnimationStateListener) {
let index = this.listeners.indexOf(listener);
const index = this.listeners.indexOf(listener);
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
* called. */
mixBlend = MixBlend.replace;
timelineMode = new Array<number>();
timelineHoldMix = new Array<TrackEntry>();
timelinesRotation = new Array<number>();
timelineMode = [] as number[];
timelineHoldMix = [] as TrackEntry[];
timelinesRotation = [] as number[];
reset () {
this.next = null;
@ -1030,8 +1035,8 @@ export class TrackEntry {
* `animationStart` time. */
getAnimationTime () {
if (this.loop) {
let duration = this.animationEnd - this.animationStart;
if (duration == 0) return this.animationStart;
const duration = this.animationEnd - this.animationStart;
if (duration === 0) return this.animationStart;
return (this.trackTime % duration) + this.animationStart;
}
return Math.min(this.trackTime + this.animationStart, this.animationEnd);
@ -1061,8 +1066,8 @@ export class TrackEntry {
}
getTrackComplete () {
let duration = this.animationEnd - this.animationStart;
if (duration != 0) {
const duration = this.animationEnd - this.animationStart;
if (duration !== 0) {
if (this.loop) return duration * (1 + ((this.trackTime / duration) | 0)); // Completion of next loop.
if (this.trackTime < duration) return duration; // Before duration.
}
@ -1073,7 +1078,7 @@ export class TrackEntry {
* <p>
* See {@link AnimationState#apply(Skeleton)}. */
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
@ -1084,7 +1089,7 @@ export class TrackEntry {
}
export class EventQueue {
objects: Array<any> = [];
objects: Array<EventType | TrackEntry | Event> = [];
drainDisabled = false;
animState: AnimationState;
@ -1137,28 +1142,29 @@ export class EventQueue {
const entry = objects[i + 1] as TrackEntry;
switch (type) {
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++) {
const listener = listeners[ii];
if (listener.start) listener.start(entry);
}
break;
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++) {
const listener = listeners[ii];
if (listener.interrupt) listener.interrupt(entry);
}
break;
// biome-ignore lint/suspicious/noFallthroughSwitchClause: reference runtime does fall through
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++) {
const listener = listeners[ii];
if (listener.end) listener.end(entry);
}
// Fall through.
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++) {
const listener = listeners[ii];
if (listener.dispose) listener.dispose(entry);
@ -1166,20 +1172,21 @@ export class EventQueue {
this.animState.trackEntryPool.free(entry);
break;
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++) {
const listener = listeners[ii];
if (listener.complete) listener.complete(entry);
}
break;
case EventType.event:
case EventType.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++) {
const listener = listeners[ii];
if (listener.event) listener.event(entry, event);
}
break;
}
}
}
this.clear();

View File

@ -27,9 +27,9 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
import { Animation } from "./Animation.js";
import { SkeletonData } from "./SkeletonData.js";
import { StringMap } from "./Utils.js";
import type { Animation } from "./Animation.js";
import type { SkeletonData } from "./SkeletonData.js";
import type { StringMap } from "./Utils.js";
/** 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.
*
* 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.
*
* 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) {
if (typeof from === "string")
@ -64,25 +64,25 @@ export class AnimationStateData {
}
private setMix1 (fromName: string, toName: string, duration: number) {
let from = this.skeletonData.findAnimation(fromName);
if (!from) throw new Error("Animation not found: " + fromName);
let to = this.skeletonData.findAnimation(toName);
if (!to) throw new Error("Animation not found: " + toName);
const from = this.skeletonData.findAnimation(fromName);
if (!from) throw new Error(`Animation not found: ${fromName}`);
const to = this.skeletonData.findAnimation(toName);
if (!to) throw new Error(`Animation not found: ${toName}`);
this.setMix2(from, to, duration);
}
private setMix2 (from: Animation, to: Animation, duration: number) {
if (!from) throw new Error("from 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;
}
/** 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. */
getMix (from: Animation, to: Animation) {
let key = from.name + "." + to.name;
let value = this.animationToMixTime[key];
const key = `${from.name}.${to.name}`;
const value = this.animationToMixTime[key];
return value === undefined ? this.defaultMix : value;
}
}

View File

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

View File

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

View File

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

View File

@ -27,13 +27,13 @@
* 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 { BoneLocal } from "./BoneLocal.js";
import { Physics } from "./Physics.js";
import { Skeleton } from "./Skeleton.js";
import { Update } from "./Update.js";
import { MathUtils, Vector2 } from "./Utils.js";
import type { Physics } from "./Physics.js";
import type { Skeleton } from "./Skeleton.js";
import type { Update } from "./Update.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
* {@link Skeleton#updateWorldTransform()}. */
@ -76,7 +76,7 @@ export class BonePose extends BoneLocal implements Update {
else
this.world = skeleton._update;
let rotation = this.rotation;
const rotation = this.rotation;
const scaleX = this.scaleX;
const scaleY = this.scaleY;
const shearX = this.shearX;
@ -123,7 +123,7 @@ export class BonePose extends BoneLocal implements Update {
break;
}
case Inherit.NoRotationOrReflection: {
let sx = 1 / skeleton.scaleX, sy = 1 / skeleton.scaleY;
const sx = 1 / skeleton.scaleX, sy = 1 / skeleton.scaleY;
pa *= sx;
pc *= sy;
let s = pa * pa + pc * pc;
@ -160,7 +160,7 @@ export class BonePose extends BoneLocal implements Update {
za *= s;
zc *= s;
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);
const zb = Math.cos(r) * s;
const zd = Math.sin(r) * s;
@ -198,7 +198,7 @@ export class BonePose extends BoneLocal implements Update {
if (!this.bone.parent) {
this.x = this.worldX - skeleton.x;
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.scaleX = Math.sqrt(a * a + c * c);
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 pid = 1 / (pa * pd - pb * pc);
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.y = (dy * id - dx * ic);
let ra, rb, rc, rd;
if (this.inherit == Inherit.OnlyTranslation) {
let ra: number, rb: number, rc: number, rd: number;
if (this.inherit === Inherit.OnlyTranslation) {
ra = this.a;
rb = this.b;
rc = this.c;
@ -224,7 +224,7 @@ export class BonePose extends BoneLocal implements Update {
} else {
switch (this.inherit) {
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;
pd = pa * skeleton.scaleY * s / skeleton.scaleX;
pid = 1 / (pa * pd - pb * pc);
@ -233,7 +233,7 @@ export class BonePose extends BoneLocal implements Update {
break;
}
case Inherit.NoScale:
case Inherit.NoScaleOrReflection:
case Inherit.NoScaleOrReflection: {
let r = this.rotation * MathUtils.degRad, cos = Math.cos(r), sin = Math.sin(r);
pa = (pa * cos + pb * sin) / skeleton.scaleX;
pc = (pc * cos + pd * sin) / skeleton.scaleY;
@ -242,7 +242,7 @@ export class BonePose extends BoneLocal implements Update {
pa *= s;
pc *= s;
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);
pb = Math.cos(r) * s;
pd = Math.sin(r) * s;
@ -251,6 +251,7 @@ export class BonePose extends BoneLocal implements Update {
ib = pb * pid;
ic = pc * pid;
id = pa * pid;
}
}
ra = ia * this.a - ib * this.c;
rb = ia * this.b - ib * this.d;
@ -261,7 +262,7 @@ export class BonePose extends BoneLocal implements Update {
this.shearX = 0;
this.scaleX = Math.sqrt(ra * ra + rc * rc);
if (this.scaleX > 0.0001) {
let det = ra * rd - rb * rc;
const det = ra * rd - rb * rc;
this.scaleY = det / this.scaleX;
this.shearY = -MathUtils.atan2Deg(ra * rb + rc * rd, det);
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. */
public worldToLocal (world: Vector2): Vector2 {
if (world == null) throw new Error("world cannot be null.");
let det = this.a * this.d - this.b * this.c;
let x = world.x - this.worldX, y = world.y - this.worldY;
const det = this.a * this.d - this.b * this.c;
const x = world.x - this.worldX, y = world.y - this.worldY;
world.x = (x * this.d - y * this.b) / det;
world.y = (y * this.a - x * this.c) / det;
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. */
public localToWorld (local: Vector2): Vector2 {
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.y = x * this.c + y * this.d + this.worldY;
return local;
@ -372,14 +373,14 @@ export class BonePose extends BoneLocal implements Update {
/** Transforms a world rotation to a local rotation. */
public worldToLocalRotation (worldRotation: number): number {
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;
}
/** Transforms a local rotation to a world rotation. */
localToWorldRotation (localRotation: number): number {
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);
}

View File

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

View File

@ -27,7 +27,7 @@
* 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 { AnimationStateListener } from "./AnimationState.js";

View File

@ -27,14 +27,14 @@
* 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 { BonePose } from "./BonePose.js";
import type { BonePose } from "./BonePose.js";
import { Constraint } from "./Constraint.js";
import { IkConstraintData } from "./IkConstraintData.js";
import type { IkConstraintData } from "./IkConstraintData.js";
import { IkConstraintPose } from "./IkConstraintPose.js";
import { Physics } from "./Physics.js";
import { Skeleton } from "./Skeleton.js";
import type { Physics } from "./Physics.js";
import type { Skeleton } from "./Skeleton.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
@ -52,7 +52,7 @@ export class IkConstraint extends Constraint<IkConstraint, IkConstraintData, IkC
super(data, new IkConstraintPose(), new IkConstraintPose());
if (!skeleton) throw new Error("skeleton cannot be null.");
this.bones = new Array<BonePose>();
this.bones = [] as BonePose[];
for (const boneData of data.bones)
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) {
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
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);
}
private static apply1 (skeleton: Skeleton, bone: BonePose, targetX: number, targetY: number, compress: boolean, stretch: boolean, uniform: boolean, mix: number) {
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 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);
ty = (targetY - bone.worldY) * MathUtils.signum(skeleton.scaleY);
break;
case Inherit.NoRotationOrReflection:
let s = Math.abs(pa * pd - pb * pc) / Math.max(0.0001, pa * pa + pc * pc);
let sa = pa / skeleton.scaleX;
let sc = pc / skeleton.scaleY;
// biome-ignore lint/suspicious/noFallthroughSwitchClause: reference runtime
case Inherit.NoRotationOrReflection: {
const s = Math.abs(pa * pd - pb * pc) / Math.max(0.0001, pa * pa + pc * pc);
const sa = pa / skeleton.scaleX;
const sc = pc / skeleton.scaleY;
pb = -sc * s * skeleton.scaleX;
pd = sa * s * skeleton.scaleY;
rotationIK += Math.atan2(sc, sa) * MathUtils.radDeg;
}
// Fall through
default:
let x = targetX - p.worldX, y = targetY - p.worldY;
let d = pa * pd - pb * pc;
default: {
const x = targetX - p.worldX, y = targetY - p.worldY;
const d = pa * pd - pb * pc;
if (Math.abs(d) <= 0.0001) {
tx = 0;
ty = 0;
@ -144,6 +147,7 @@ export class IkConstraint extends Constraint<IkConstraint, IkConstraintData, IkC
tx = (x * pd - y * pb) / d - bone.x;
ty = (y * pa - x * pc) / d - bone.y;
}
}
}
rotationIK += MathUtils.atan2Deg(ty, tx);
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.
* @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) {
if (parent.inherit != Inherit.Normal || child.inherit != Inherit.Normal) return;
if (parent.inherit !== Inherit.Normal || child.inherit !== Inherit.Normal) return;
parent.modifyLocal(skeleton);
child.modifyLocal(skeleton);
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
os2 = 0;
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) {
child.y = 0;
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;
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;
b = pp.b;
c = pp.c;
d = pp.d;
let id = a * d - b * c, x = cwx - pp.worldX, y = cwy - pp.worldY;
id = Math.abs(id) <= 0.0001 ? 0 : 1 / id;
let 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;
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: number, a2: number;
if (l1 < 0.0001) {
IkConstraint.apply(skeleton, parent, targetX, targetY, false, stretch, false, mix);
child.rotation = 0;
@ -224,9 +229,9 @@ export class IkConstraint extends Constraint<IkConstraint, IkConstraintData, IkC
y = targetY - pp.worldY;
let tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py;
let dd = tx * tx + ty * ty;
if (softness != 0) {
if (softness !== 0) {
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) {
let p = Math.min(1, sd / (softness * 2)) - 1;
p = (sd - softness * (1 - p * p)) / td;
@ -235,6 +240,7 @@ export class IkConstraint extends Constraint<IkConstraint, IkConstraintData, IkC
dd = tx * tx + ty * ty;
}
}
// biome-ignore lint/suspicious/noConfusingLabels: reference runtime
outer:
if (u) {
l2 *= psx;
@ -258,16 +264,16 @@ export class IkConstraint extends Constraint<IkConstraint, IkConstraintData, IkC
} else {
a = psx * 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;
let c1 = -2 * bb * l1, c2 = bb - aa;
const c1 = -2 * bb * l1, c2 = bb - aa;
d = c1 * c1 - 4 * c2 * c;
if (d >= 0) {
let q = Math.sqrt(d);
if (c1 < 0) q = -q;
q = -(c1 + q) * 0.5;
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;
if (r0 >= 0) {
y = Math.sqrt(r0) * bendDir;
@ -305,7 +311,7 @@ export class IkConstraint extends Constraint<IkConstraint, IkConstraintData, IkC
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;
if (a1 > 180)
a1 -= 360;

View File

@ -27,18 +27,18 @@
* 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 { IkConstraint } from "./IkConstraint.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}.
*
* See [IK constraints](http://esotericsoftware.com/spine-ik-constraints) in the Spine User Guide. */
export class IkConstraintData extends ConstraintData<IkConstraint, IkConstraintPose> {
/** The bones that are constrained by this IK constraint. */
bones = new Array<BoneData>();
bones = [] as BoneData[];
private _target: BoneData | null = null;
/** 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.
*****************************************************************************/
import { Pose } from "./Pose";
import type { Pose } from "./Pose";
/** Stores the current pose for an IK constraint. */
export class IkConstraintPose implements Pose<IkConstraintPose> {

View File

@ -27,18 +27,18 @@
* 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 { Bone } from "./Bone.js";
import { BonePose } from "./BonePose.js";
import type { Bone } from "./Bone.js";
import type { BonePose } from "./BonePose.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 { Physics } from "./Physics.js";
import { Skeleton } from "./Skeleton.js";
import { Skin, SkinEntry } from "./Skin.js";
import { Slot } from "./Slot.js";
import { Utils, MathUtils } from "./Utils.js";
import type { Physics } from "./Physics.js";
import type { Skeleton } from "./Skeleton.js";
import type { Skin } from "./Skin.js";
import type { Slot } from "./Slot.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
@ -58,16 +58,16 @@ export class PathConstraint extends Constraint<PathConstraint, PathConstraintDat
/** The slot whose path attachment will be used to constrained the bones. */
slot: Slot;
spaces = new Array<number>(); positions = new Array<number>();
world = new Array<number>(); curves = new Array<number>(); lengths = new Array<number>();
segments = new Array<number>();
spaces = [] as number[]; positions = [] as number[];
world = [] as number[]; curves = [] as number[]; lengths = [] as number[];
segments = [] as number[];
constructor (data: PathConstraintData, skeleton: Skeleton) {
super(data, new PathConstraintPose(), new PathConstraintPose());
if (!skeleton) throw new Error("skeleton cannot be null.");
this.data = data;
this.bones = new Array<BonePose>();
this.bones = [] as BonePose[];
for (const boneData of this.data.bones)
this.bones.push(skeleton.bones[boneData.index].constrained);
@ -81,44 +81,44 @@ export class PathConstraint extends Constraint<PathConstraint, PathConstraintDat
}
update (skeleton: Skeleton, physics: Physics) {
let attachment = this.slot.applied.attachment;
const attachment = this.slot.applied.attachment;
if (!(attachment instanceof PathAttachment)) return;
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;
let data = this.data;
let tangents = data.rotateMode == RotateMode.Tangent, scale = data.rotateMode == RotateMode.ChainScale;
const data = this.data;
const tangents = data.rotateMode === RotateMode.Tangent, scale = data.rotateMode === RotateMode.ChainScale;
let bones = this.bones;
let 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) : [];
let spacing = p.spacing;
const bones = this.bones;
const boneCount = bones.length, spacesCount = tangents ? boneCount : boneCount + 1;
const spaces = Utils.setArraySize(this.spaces, spacesCount), lengths: Array<number> = scale ? this.lengths = Utils.setArraySize(this.lengths, boneCount) : [];
const spacing = p.spacing;
switch (data.spacingMode) {
case SpacingMode.Percent:
if (scale) {
for (let i = 0, n = spacesCount - 1; i < n; i++) {
let bone = bones[i];
let setupLength = bone.bone.data.length;
let x = setupLength * bone.a, y = setupLength * bone.c;
const bone = bones[i];
const setupLength = bone.bone.data.length;
const x = setupLength * bone.a, y = setupLength * bone.c;
lengths[i] = Math.sqrt(x * x + y * y);
}
}
Utils.arrayFill(spaces, 1, spacesCount, spacing);
break;
case SpacingMode.Proportional:
case SpacingMode.Proportional: {
let sum = 0;
for (let i = 0, n = spacesCount - 1; i < n;) {
let bone = bones[i];
let setupLength = bone.bone.data.length;
const bone = bones[i];
const setupLength = bone.bone.data.length;
if (setupLength < PathConstraint.epsilon) {
if (scale) lengths[i] = 0;
spaces[++i] = spacing;
} else {
let x = setupLength * bone.a, y = setupLength * bone.c;
let length = Math.sqrt(x * x + y * y);
const x = setupLength * bone.a, y = setupLength * bone.c;
const length = Math.sqrt(x * x + y * y);
if (scale) lengths[i] = length;
spaces[++i] = length;
sum += length;
@ -130,42 +130,44 @@ export class PathConstraint extends Constraint<PathConstraint, PathConstraintDat
spaces[i] *= sum;
}
break;
default:
let lengthSpacing = data.spacingMode == SpacingMode.Length;
}
default: {
const lengthSpacing = data.spacingMode === SpacingMode.Length;
for (let i = 0, n = spacesCount - 1; i < n;) {
let bone = bones[i];
let setupLength = bone.bone.data.length;
const bone = bones[i];
const setupLength = bone.bone.data.length;
if (setupLength < PathConstraint.epsilon) {
if (scale) lengths[i] = 0;
spaces[++i] = spacing;
} else {
let x = setupLength * bone.a, y = setupLength * bone.c;
let length = Math.sqrt(x * x + y * y);
const x = setupLength * bone.a, y = setupLength * bone.c;
const length = Math.sqrt(x * x + y * y);
if (scale) lengths[i] = length;
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 tip = false;
if (offsetRotation == 0)
tip = data.rotateMode == RotateMode.Chain;
if (offsetRotation === 0)
tip = data.rotateMode === RotateMode.Chain;
else {
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;
}
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.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) {
let length = lengths[i];
if (length != 0) {
let s = (Math.sqrt(dx * dx + dy * dy) / length - 1) * mixRotate + 1;
const length = lengths[i];
if (length !== 0) {
const s = (Math.sqrt(dx * dx + dy * dy) / length - 1) * mixRotate + 1;
bone.a *= 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;
if (tangents)
r = positions[ip - 1];
else if (spaces[i + 1] == 0)
else if (spaces[i + 1] === 0)
r = positions[ip + 2];
else
r = Math.atan2(dy, dx);
@ -184,7 +186,7 @@ export class PathConstraint extends Constraint<PathConstraint, PathConstraintDat
if (tip) {
cos = Math.cos(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;
boneY += (length * (sin * a + cos * c) - dy) * mixRotate;
} else {
@ -207,19 +209,19 @@ export class PathConstraint extends Constraint<PathConstraint, PathConstraintDat
}
computeWorldPositions (skeleton: Skeleton, path: PathAttachment, spacesCount: number, tangents: boolean) {
let slot = this.slot;
const slot = this.slot;
let position = this.applied.position;
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;
if (!path.constantSpeed) {
let lengths = path.lengths;
const lengths = path.lengths;
curveCount -= closed ? 1 : 2;
let pathLength = lengths[curveCount];
if (this.data.positionMode == PositionMode.Percent) position *= pathLength;
const pathLength = lengths[curveCount];
if (this.data.positionMode === PositionMode.Percent) position *= pathLength;
let multiplier;
let multiplier: number;
switch (this.data.spacingMode) {
case SpacingMode.Percent: multiplier = pathLength; 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);
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;
let p = position;
@ -237,14 +239,14 @@ export class PathConstraint extends Constraint<PathConstraint, PathConstraintDat
if (p < 0) p += pathLength;
curve = 0;
} else if (p < 0) {
if (prevCurve != PathConstraint.BEFORE) {
if (prevCurve !== PathConstraint.BEFORE) {
prevCurve = PathConstraint.BEFORE;
path.computeWorldVertices(skeleton, slot, 2, 4, world, 0, 2);
}
this.addBeforePosition(p, world, 0, out, o);
continue;
} else if (p > pathLength) {
if (prevCurve != PathConstraint.AFTER) {
if (prevCurve !== PathConstraint.AFTER) {
prevCurve = PathConstraint.AFTER;
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.
for (; ; curve++) {
let length = lengths[curve];
const length = lengths[curve];
if (p > length) continue;
if (curve == 0)
if (curve === 0)
p /= length;
else {
let prev = lengths[curve - 1];
const prev = lengths[curve - 1];
p = (p - prev) / (length - prev);
}
break;
}
if (curve != prevCurve) {
if (curve !== prevCurve) {
prevCurve = curve;
if (closed && curve == curveCount) {
if (closed && curve === curveCount) {
path.computeWorldVertices(skeleton, slot, verticesLength - 4, 4, world, 0, 2);
path.computeWorldVertices(skeleton, slot, 0, 4, world, 4, 2);
} else
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,
tangents || (i > 0 && space == 0));
tangents || (i > 0 && space === 0));
}
return out;
}
@ -294,7 +296,7 @@ export class PathConstraint extends Constraint<PathConstraint, PathConstraintDat
}
// Curve lengths.
let curves = Utils.setArraySize(this.curves, curveCount);
const curves = Utils.setArraySize(this.curves, curveCount);
let pathLength = 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;
@ -330,19 +332,19 @@ export class PathConstraint extends Constraint<PathConstraint, PathConstraintDat
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) {
case SpacingMode.Percent: multiplier = pathLength; break;
case SpacingMode.Proportional: multiplier = pathLength / spacesCount; break;
default: multiplier = 1;
}
let segments = this.segments;
const segments = this.segments;
let curveLength = 0;
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;
let p = position;
@ -361,19 +363,19 @@ export class PathConstraint extends Constraint<PathConstraint, PathConstraintDat
// Determine curve containing position.
for (; ; curve++) {
let length = curves[curve];
const length = curves[curve];
if (p > length) continue;
if (curve == 0)
if (curve === 0)
p /= length;
else {
let prev = curves[curve - 1];
const prev = curves[curve - 1];
p = (p - prev) / (length - prev);
}
break;
}
// Curve segment lengths.
if (curve != prevCurve) {
if (curve !== prevCurve) {
prevCurve = curve;
let ii = curve * 6;
x1 = world[ii];
@ -416,30 +418,30 @@ export class PathConstraint extends Constraint<PathConstraint, PathConstraintDat
// Weight by segment length.
p *= curveLength;
for (; ; segment++) {
let length = segments[segment];
const length = segments[segment];
if (p > length) continue;
if (segment == 0)
if (segment === 0)
p /= length;
else {
let prev = segments[segment - 1];
const prev = segments[segment - 1];
p = segment + (p - prev) / (length - prev);
}
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;
}
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 + 1] = y1 + p * Math.sin(r);
out[o + 2] = r;
}
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 + 1] = y1 + p * Math.sin(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,
out: Array<number>, o: number, tangents: boolean) {
if (p == 0 || isNaN(p)) {
if (p === 0 || Number.isNaN(p)) {
out[o] = x1;
out[o + 1] = y1;
out[o + 2] = Math.atan2(cy1 - y1, cx1 - x1);
return;
}
let 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;
let x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt, y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt;
const tt = p * p, ttt = tt * p, u = 1 - p, uu = u * u, uuu = uu * u;
const ut = u * p, ut3 = ut * 3, uut3 = u * ut3, utt3 = ut3 * p;
const x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt, y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt;
out[o] = x;
out[o + 1] = y;
if (tangents) {
@ -470,7 +472,7 @@ export class PathConstraint extends Constraint<PathConstraint, PathConstraintDat
const slotIndex = this.slot.data.index;
const slotBone = this.slot.bone;
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.sortPath(skeleton, this.slot.pose.attachment, slotBone);
const bones = this.bones;
@ -491,7 +493,7 @@ export class PathConstraint extends Constraint<PathConstraint, PathConstraintDat
const entries = skin.getAttachments();
for (let i = 0, n = entries.length; i < n; 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.
*****************************************************************************/
import { BoneData } from "./BoneData.js";
import type { BoneData } from "./BoneData.js";
import { ConstraintData } from "./ConstraintData.js";
import { PathConstraint } from "./PathConstraint.js";
import { PathConstraintPose } from "./PathConstraintPose.js";
import { Skeleton } from "./Skeleton.js";
import { SlotData } from "./SlotData.js";
import type { Skeleton } from "./Skeleton.js";
import type { SlotData } from "./SlotData.js";
/** 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. */
export class PathConstraintData extends ConstraintData<PathConstraint, PathConstraintPose> {
/** 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. */
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.
*****************************************************************************/
import { Pose } from "./Pose"
import type { Pose } from "./Pose"
/** Stores a pose for a path constraint. */
export class PathConstraintPose implements Pose<PathConstraintPose> {

View File

@ -27,11 +27,11 @@
* 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 { PhysicsConstraint } from "./PhysicsConstraint.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}.

View File

@ -27,7 +27,7 @@
* 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. */
export class PhysicsConstraintPose implements Pose<PhysicsConstraintPose> {

View File

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

View File

@ -27,15 +27,15 @@
* 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 { PosedData } from "./PosedData.js";
import type { PosedData } from "./PosedData.js";
import type { Skeleton } from "./Skeleton";
export abstract class PosedActive<
D extends PosedData<P>,
P extends Pose<any>,
P extends Pose<P>,
A extends P>
extends Posed<D, P, A> {

View File

@ -27,10 +27,10 @@
* 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. */
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. */
readonly name: string;

View File

@ -27,20 +27,20 @@
* 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 { MeshAttachment } from "./attachments/MeshAttachment.js";
import { RegionAttachment } from "./attachments/RegionAttachment.js";
import { Bone } from "./Bone.js";
import { Constraint } from "./Constraint.js";
import { Physics } from "./Physics.js";
import type { Constraint } from "./Constraint.js";
import type { Physics } from "./Physics.js";
import { PhysicsConstraint } from "./PhysicsConstraint.js";
import { Posed } from "./Posed.js";
import { SkeletonClipping } from "./SkeletonClipping.js";
import { SkeletonData } from "./SkeletonData.js";
import { Skin } from "./Skin.js";
import type { Posed } from "./Posed.js";
import type { SkeletonClipping } from "./SkeletonClipping.js";
import type { SkeletonData } from "./SkeletonData.js";
import type { Skin } from "./Skin.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.
*
@ -65,15 +65,18 @@ export class Skeleton {
drawOrder: Array<Slot>;
/** The skeleton's constraints. */
// biome-ignore lint/suspicious/noExplicitAny: reference runtime does not restrict to specific types
readonly constraints: Array<Constraint<any, any, any>>;
/** The skeleton's physics constraints. */
readonly physics: Array<PhysicsConstraint>;
/** 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. */
skin: Skin | null = null;
@ -125,30 +128,31 @@ export class Skeleton {
if (!data) throw new Error("data cannot be null.");
this.data = data;
this.bones = new Array<Bone>();
this.bones = [] as Bone[];
for (let i = 0; i < data.bones.length; i++) {
let boneData = data.bones[i];
const boneData = data.bones[i];
let bone: Bone;
if (!boneData.parent)
bone = new Bone(boneData, null);
else {
let parent = this.bones[boneData.parent.index];
const parent = this.bones[boneData.parent.index];
bone = new Bone(boneData, parent);
parent.children.push(bone);
}
this.bones.push(bone);
}
this.slots = new Array<Slot>();
this.drawOrder = new Array<Slot>();
this.slots = [] as Slot[];
this.drawOrder = [] as Slot[];
for (const slotData of this.data.slots) {
let slot = new Slot(slotData, this);
const slot = new Slot(slotData, this);
this.slots.push(slot);
this.drawOrder.push(slot);
}
this.physics = new Array<PhysicsConstraint>();
this.constraints = new Array<Constraint<any, any, any>>();
this.physics = [] as PhysicsConstraint[];
// 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) {
const constraint = constraintData.create(this);
if (constraint instanceof PhysicsConstraint) this.physics.push(constraint);
@ -166,20 +170,20 @@ export class Skeleton {
this._updateCache.length = 0;
this.resetCache.length = 0;
let slots = this.slots;
const slots = this.slots;
for (let i = 0, n = slots.length; i < n; i++)
slots[i].usePose();
let bones = this.bones;
const bones = this.bones;
const boneCount = bones.length;
for (let i = 0, n = boneCount; i < n; i++) {
let bone = bones[i];
const bone = bones[i];
bone.sorted = bone.data.skinRequired;
bone.active = !bone.sorted;
bone.usePose();
}
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++) {
let bone: Bone | null = this.bones[skinBones[i].index];
do {
@ -190,13 +194,14 @@ export class Skeleton {
}
}
let constraints = this.constraints;
const constraints = this.constraints;
let n = this.constraints.length;
for (let i = 0; i < n; i++)
constraints[i].usePose();
for (let i = 0; i < n; i++) {
const constraint = constraints[i];
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)));
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>) {
if (object.pose === object.applied) {
object.useConstrained();
@ -221,7 +227,7 @@ export class Skeleton {
sortBone (bone: Bone) {
if (bone.sorted || !bone.active) return;
let parent = bone.parent;
const parent = bone.parent;
if (parent) this.sortBone(parent);
bone.sorted = true;
this._updateCache.push(bone);
@ -229,7 +235,7 @@ export class Skeleton {
sortReset (bones: Array<Bone>) {
for (let i = 0, n = bones.length; i < n; i++) {
let bone = bones[i];
const bone = bones[i];
if (bone.active) {
if (bone.sorted) this.sortReset(bone.children);
bone.sorted = false;
@ -272,7 +278,7 @@ export class Skeleton {
/** Sets the slots and draw order to their setup pose values. */
setupPoseSlots () {
let slots = this.slots;
const slots = this.slots;
Utils.arrayCopy(slots, 0, this.drawOrder, 0, slots.length);
for (let i = 0, n = slots.length; i < n; i++)
slots[i].setupPose();
@ -280,7 +286,7 @@ export class Skeleton {
/** Returns the root bone, or null if the skeleton has no bones. */
getRootBone () {
if (this.bones.length == 0) return null;
if (this.bones.length === 0) return null;
return this.bones[0];
}
@ -288,9 +294,9 @@ export class Skeleton {
* repeatedly. */
findBone (boneName: string) {
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++)
if (bones[i].data.name == boneName) return bones[i];
if (bones[i].data.name === boneName) return bones[i];
return null;
}
@ -298,9 +304,9 @@ export class Skeleton {
* repeatedly. */
findSlot (slotName: string) {
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++)
if (slots[i].data.name == slotName) return slots[i];
if (slots[i].data.name === slotName) return slots[i];
return null;
}
@ -328,23 +334,23 @@ export class Skeleton {
};
private setSkinByName (skinName: string) {
let skin = this.data.findSkin(skinName);
if (!skin) throw new Error("Skin not found: " + skinName);
const skin = this.data.findSkin(skinName);
if (!skin) throw new Error(`Skin not found: ${skinName}`);
this.setSkin(skin);
}
private setSkinBySkin (newSkin: Skin | null) {
if (newSkin == this.skin) return;
if (newSkin === this.skin) return;
if (newSkin) {
if (this.skin)
newSkin.attachAll(this, this.skin);
else {
let slots = this.slots;
const slots = this.slots;
for (let i = 0, n = slots.length; i < n; i++) {
let slot = slots[i];
let name = slot.data.attachmentName;
const slot = slots[i];
const name = slot.data.attachmentName;
if (name) {
let attachment = newSkin.getAttachment(i, name);
const attachment = newSkin.getAttachment(i, name);
if (attachment) slot.pose.setAttachment(attachment);
}
}
@ -378,7 +384,7 @@ export class Skeleton {
* See {@link #getAttachment()}.
* @returns May be 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}`);
return this.getAttachment(slot.index, attachmentName);
}
@ -391,7 +397,7 @@ export class Skeleton {
private getAttachmentByIndex (slotIndex: number, attachmentName: string): Attachment | null {
if (!attachmentName) throw new Error("attachmentName cannot be null.");
if (this.skin) {
let attachment = this.skin.getAttachment(slotIndex, attachmentName);
const attachment = this.skin.getAttachment(slotIndex, attachmentName);
if (attachment) return attachment;
}
if (this.data.defaultSkin) return this.data.defaultSkin.getAttachment(slotIndex, attachmentName);
@ -404,16 +410,17 @@ export class Skeleton {
setAttachment (slotName: string, attachmentName: string) {
if (!slotName) throw new Error("slotName cannot be null.");
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;
if (attachmentName) {
attachment = this.getAttachment(slot.data.index, attachmentName);
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);
}
// 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 {
if (constraintName == null) throw new Error("constraintName 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 }`.
* 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) {
let offset = new Vector2();
let size = new Vector2();
const offset = new Vector2();
const size = new Vector2();
this.getBounds(offset, size, undefined, clipper);
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) {
if (!offset) throw new Error("offset 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;
for (let i = 0, n = drawOrder.length; i < n; i++) {
let slot = drawOrder[i];
const slot = drawOrder[i];
if (!slot.bone.active) continue;
let verticesLength = 0;
let vertices: NumberArrayLike | null = null;
let triangles: NumberArrayLike | null = null;
let attachment = slot.pose.attachment;
const attachment = slot.pose.attachment;
if (attachment) {
if (attachment instanceof RegionAttachment) {
verticesLength = 8;
@ -468,12 +475,12 @@ export class Skeleton {
continue;
}
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;
verticesLength = clipper.clippedVertices.length;
}
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);
minY = Math.min(minY, y);
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 { Skeleton } from "./Skeleton.js";
import { NumberArrayLike, Pool, Utils } from "./Utils.js";
import type { Skeleton } from "./Skeleton.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
* provided along with convenience methods for doing hit detection. */
@ -48,10 +48,10 @@ export class SkeletonBounds {
maxY = 0;
/** The visible bounding boxes. */
boundingBoxes = new Array<BoundingBoxAttachment>();
boundingBoxes = [] as BoundingBoxAttachment[];
/** The world vertices for the bounding box polygons. */
polygons = new Array<NumberArrayLike>();
polygons = [] as NumberArrayLike[];
private polygonPool = new Pool<NumberArrayLike>(() => {
return Utils.newFloatArray(16);
@ -63,25 +63,25 @@ export class SkeletonBounds {
* SkeletonBounds AABB methods will always return true. */
update (skeleton: Skeleton, updateAabb: boolean) {
if (!skeleton) throw new Error("skeleton cannot be null.");
let boundingBoxes = this.boundingBoxes;
let polygons = this.polygons;
let polygonPool = this.polygonPool;
let slots = skeleton.slots;
let slotCount = slots.length;
const boundingBoxes = this.boundingBoxes;
const polygons = this.polygons;
const polygonPool = this.polygonPool;
const slots = skeleton.slots;
const slotCount = slots.length;
boundingBoxes.length = 0;
polygonPool.freeAll(polygons);
polygons.length = 0;
for (let i = 0; i < slotCount; i++) {
let slot = slots[i];
const slot = slots[i];
if (!slot.bone.active) continue;
let attachment = slot.applied.attachment;
const attachment = slot.applied.attachment;
if (attachment instanceof BoundingBoxAttachment) {
boundingBoxes.push(attachment);
let polygon = polygonPool.obtain();
if (polygon.length != attachment.worldVerticesLength) {
if (polygon.length !== attachment.worldVerticesLength) {
polygon = Utils.newFloatArray(attachment.worldVerticesLength);
}
polygons.push(polygon);
@ -101,13 +101,13 @@ export class SkeletonBounds {
aabbCompute () {
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++) {
let polygon = polygons[i];
let vertices = polygon;
const polygon = polygons[i];
const vertices = polygon;
for (let ii = 0, nn = polygon.length; ii < nn; ii += 2) {
let x = vertices[ii];
let y = vertices[ii + 1];
const x = vertices[ii];
const y = vertices[ii + 1];
minX = Math.min(minX, x);
minY = Math.min(minY, y);
maxX = Math.max(maxX, x);
@ -127,13 +127,13 @@ export class SkeletonBounds {
/** Returns true if the axis aligned bounding box intersects the line segment. */
aabbIntersectsSegment (x1: number, y1: number, x2: number, y2: number) {
let minX = this.minX;
let minY = this.minY;
let maxX = this.maxX;
let maxY = this.maxY;
const minX = this.minX;
const minY = this.minY;
const maxX = this.maxX;
const maxY = this.maxY;
if ((x1 <= minX && x2 <= minX) || (y1 <= minY && y2 <= minY) || (x1 >= maxX && x2 >= maxX) || (y1 >= maxY && y2 >= maxY))
return false;
let m = (y2 - y1) / (x2 - x1);
const m = (y2 - y1) / (x2 - x1);
let y = m * (minX - x1) + y1;
if (y > minY && y < maxY) return true;
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
* efficient to only call this method if {@link #aabbContainsPoint(float, float)} returns true. */
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++)
if (this.containsPointPolygon(polygons[i], x, y)) return this.boundingBoxes[i];
return null;
@ -161,16 +161,16 @@ export class SkeletonBounds {
/** Returns true if the polygon contains the point. */
containsPointPolygon (polygon: NumberArrayLike, x: number, y: number) {
let vertices = polygon;
let nn = polygon.length;
const vertices = polygon;
const nn = polygon.length;
let prevIndex = nn - 2;
let inside = false;
for (let ii = 0; ii < nn; ii += 2) {
let vertexY = vertices[ii + 1];
let prevY = vertices[prevIndex + 1];
const vertexY = vertices[ii + 1];
const prevY = vertices[prevIndex + 1];
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;
}
prevIndex = ii;
@ -182,7 +182,7 @@ export class SkeletonBounds {
* is usually more efficient to only call this method if {@link #aabbIntersectsSegment()} returns
* true. */
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++)
if (this.intersectsSegmentPolygon(polygons[i], x1, y1, x2, y2)) return this.boundingBoxes[i];
return null;
@ -190,20 +190,20 @@ export class SkeletonBounds {
/** Returns true if the polygon contains any part of the line segment. */
intersectsSegmentPolygon (polygon: NumberArrayLike, x1: number, y1: number, x2: number, y2: number) {
let vertices = polygon;
let nn = polygon.length;
const vertices = polygon;
const nn = polygon.length;
let width12 = x1 - x2, height12 = y1 - y2;
let det1 = x1 * y2 - y1 * x2;
const width12 = x1 - x2, height12 = y1 - y2;
const det1 = x1 * y2 - y1 * x2;
let x3 = vertices[nn - 2], y3 = vertices[nn - 1];
for (let ii = 0; ii < nn; ii += 2) {
let x4 = vertices[ii], y4 = vertices[ii + 1];
let det2 = x3 * y4 - y3 * x4;
let width34 = x3 - x4, height34 = y3 - y4;
let det3 = width12 * height34 - height12 * width34;
let x = (det1 * width34 - width12 * det2) / det3;
const x4 = vertices[ii], y4 = vertices[ii + 1];
const det2 = x3 * y4 - y3 * x4;
const width34 = x3 - x4, height34 = y3 - y4;
const det3 = width12 * height34 - height12 * width34;
const x = (det1 * width34 - width12 * det2) / det3;
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;
}
x3 = x4;
@ -215,8 +215,8 @@ export class SkeletonBounds {
/** Returns the polygon for the specified bounding box, or null. */
getPolygon (boundingBox: BoundingBoxAttachment) {
if (!boundingBox) throw new Error("boundingBox cannot be null.");
let index = this.boundingBoxes.indexOf(boundingBox);
return index == -1 ? null : this.polygons[index];
const index = this.boundingBoxes.indexOf(boundingBox);
return index === -1 ? null : this.polygons[index];
}
/** 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.
*****************************************************************************/
import { ClippingAttachment } from "./attachments/ClippingAttachment.js";
import { Skeleton } from "./Skeleton.js";
import { Slot } from "./Slot.js";
import type { ClippingAttachment } from "./attachments/ClippingAttachment.js";
import type { Skeleton } from "./Skeleton.js";
import type { Slot } from "./Slot.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 {
private triangulator = new Triangulator();
private clippingPolygon = new Array<number>();
private clipOutput = new Array<number>();
clippedVertices = new Array<number>();
private clippingPolygon = [] as number[];
private clipOutput = [] as number[];
clippedVertices = [] as number[];
/** 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);
_clippedUVsTyped = new Float32Array(1024);
@ -54,7 +54,7 @@ export class SkeletonClipping {
clippedUVsLength = 0;
clippedTrianglesLength = 0;
private scratch = new Array<number>();
private scratch = [] as number[];
private clipAttachment: ClippingAttachment | null = null;
private clippingPolygons: Array<Array<number>> | null = null;
@ -63,14 +63,14 @@ export class SkeletonClipping {
if (this.clipAttachment) return 0;
this.clipAttachment = clip;
let n = clip.worldVerticesLength;
let vertices = Utils.setArraySize(this.clippingPolygon, n);
const n = clip.worldVerticesLength;
const vertices = Utils.setArraySize(this.clippingPolygon, n);
clip.computeWorldVertices(skeleton, slot, 0, n, vertices, 0, 2);
let clippingPolygon = this.clippingPolygon;
const clippingPolygon = this.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++) {
let polygon = clippingPolygons[i];
const polygon = clippingPolygons[i];
SkeletonClipping.makeClockwise(polygon);
polygon.push(polygon[0]);
polygon.push(polygon[1]);
@ -109,10 +109,11 @@ export class SkeletonClipping {
private clipTrianglesNoRender (vertices: NumberArrayLike, triangles: NumberArrayLike, trianglesLength: number): boolean {
let clipOutput = this.clipOutput, clippedVertices = this.clippedVertices;
let clippedTriangles = this.clippedTriangles;
let polygons = this.clippingPolygons!;
let polygonsCount = polygons.length;
const clipOutput = this.clipOutput, clippedVertices = this.clippedVertices;
const clippedTriangles = this.clippedTriangles;
// biome-ignore lint/style/noNonNullAssertion: clipStart define it
const polygons = this.clippingPolygons!;
const polygonsCount = polygons.length;
let index = 0;
clippedVertices.length = 0;
@ -120,31 +121,31 @@ export class SkeletonClipping {
let clipOutputItems = null;
for (let i = 0; i < trianglesLength; i += 3) {
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;
let x2 = vertices[v], y2 = vertices[v + 1];
const x2 = vertices[v], y2 = vertices[v + 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++) {
let s = clippedVertices.length;
if (this.clip(x1, y1, x2, y2, x3, y3, polygons[p], clipOutput)) {
clipOutputItems = this.clipOutput;
let clipOutputLength = clipOutput.length;
if (clipOutputLength == 0) continue;
const clipOutputLength = clipOutput.length;
if (clipOutputLength === 0) continue;
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) {
let x = clipOutputItems[ii], y = clipOutputItems[ii + 1];
const x = clipOutputItems[ii], y = clipOutputItems[ii + 1];
clippedVerticesItems[s] = x;
clippedVerticesItems[s + 1] = y;
}
s = clippedTriangles.length;
let clippedTrianglesItems = Utils.setArraySize(clippedTriangles, s + 3 * (clipOutputCount - 2));
const clippedTrianglesItems = Utils.setArraySize(clippedTriangles, s + 3 * (clipOutputCount - 2));
clipOutputCount--;
for (let ii = 1; ii < clipOutputCount; ii++, s += 3) {
clippedTrianglesItems[s] = index;
@ -154,7 +155,7 @@ export class SkeletonClipping {
index += clipOutputCount + 1;
} else {
let clippedVerticesItems = Utils.setArraySize(clippedVertices, s + 3 * 2);
const clippedVerticesItems = Utils.setArraySize(clippedVertices, s + 3 * 2);
clippedVerticesItems[s] = x1;
clippedVerticesItems[s + 1] = y1;
@ -165,7 +166,7 @@ export class SkeletonClipping {
clippedVerticesItems[s + 5] = y3;
s = clippedTriangles.length;
let clippedTrianglesItems = Utils.setArraySize(clippedTriangles, s + 3);
const clippedTrianglesItems = Utils.setArraySize(clippedTriangles, s + 3);
clippedTrianglesItems[s] = index;
clippedTrianglesItems[s + 1] = (index + 1);
clippedTrianglesItems[s + 2] = (index + 2);
@ -180,10 +181,11 @@ export class SkeletonClipping {
private clipTrianglesRender (vertices: NumberArrayLike, triangles: NumberArrayLike, trianglesLength: number, uvs: NumberArrayLike,
light: Color, dark: Color, twoColor: boolean, stride: number): boolean {
let clipOutput = this.clipOutput, clippedVertices = this.clippedVertices;
let clippedTriangles = this.clippedTriangles;
let polygons = this.clippingPolygons!;
let polygonsCount = polygons.length;
const clipOutput = this.clipOutput, clippedVertices = this.clippedVertices;
const clippedTriangles = this.clippedTriangles;
// biome-ignore lint/style/noNonNullAssertion: clipStart define it
const polygons = this.clippingPolygons!;
const polygonsCount = polygons.length;
let index = 0;
clippedVertices.length = 0;
@ -191,40 +193,40 @@ export class SkeletonClipping {
let clipOutputItems = null;
for (let i = 0; i < trianglesLength; i += 3) {
let t = triangles[i];
let u1 = uvs[t << 1], v1 = uvs[(t << 1) + 1];
let x1 = vertices[t * stride], y1 = vertices[t * stride + 1];
const u1 = uvs[t << 1], v1 = uvs[(t << 1) + 1];
const x1 = vertices[t * stride], y1 = vertices[t * stride + 1];
t = triangles[i + 1];
let u2 = uvs[t << 1], v2 = uvs[(t << 1) + 1];
let x2 = vertices[t * stride], y2 = vertices[t * stride + 1];
const u2 = uvs[t << 1], v2 = uvs[(t << 1) + 1];
const x2 = vertices[t * stride], y2 = vertices[t * stride + 1];
t = triangles[i + 2];
let u3 = uvs[t << 1], v3 = uvs[(t << 1) + 1];
let x3 = vertices[t * stride], y3 = vertices[t * stride + 1];
const u3 = uvs[t << 1], v3 = uvs[(t << 1) + 1];
const x3 = vertices[t * stride], y3 = vertices[t * stride + 1];
for (let p = 0; p < polygonsCount; p++) {
let s = clippedVertices.length;
if (this.clip(x1, y1, x2, y2, x3, y3, polygons[p], clipOutput)) {
clipOutputItems = this.clipOutput;
let clipOutputLength = clipOutput.length;
if (clipOutputLength == 0) continue;
let d0 = y2 - y3, d1 = x3 - x2, d2 = x1 - x3, d4 = y3 - y1;
let d = 1 / (d0 * d2 + d1 * (y1 - y3));
const clipOutputLength = clipOutput.length;
if (clipOutputLength === 0) continue;
const d0 = y2 - y3, d1 = x3 - x2, d2 = x1 - x3, d4 = y3 - y1;
const d = 1 / (d0 * d2 + d1 * (y1 - y3));
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) {
let x = clipOutputItems[ii], y = clipOutputItems[ii + 1];
const x = clipOutputItems[ii], y = clipOutputItems[ii + 1];
clippedVerticesItems[s] = x;
clippedVerticesItems[s + 1] = y;
clippedVerticesItems[s + 2] = light.r;
clippedVerticesItems[s + 3] = light.g;
clippedVerticesItems[s + 4] = light.b;
clippedVerticesItems[s + 5] = light.a;
let c0 = x - x3, c1 = y - y3;
let a = (d0 * c0 + d1 * c1) * d;
let b = (d4 * c0 + d2 * c1) * d;
let c = 1 - a - b;
const c0 = x - x3, c1 = y - y3;
const a = (d0 * c0 + d1 * c1) * d;
const b = (d4 * c0 + d2 * c1) * d;
const c = 1 - a - b;
clippedVerticesItems[s + 6] = u1 * a + u2 * b + u3 * c;
clippedVerticesItems[s + 7] = v1 * a + v2 * b + v3 * c;
if (twoColor) {
@ -236,7 +238,7 @@ export class SkeletonClipping {
}
s = clippedTriangles.length;
let clippedTrianglesItems = Utils.setArraySize(clippedTriangles, s + 3 * (clipOutputCount - 2));
const clippedTrianglesItems = Utils.setArraySize(clippedTriangles, s + 3 * (clipOutputCount - 2));
clipOutputCount--;
for (let ii = 1; ii < clipOutputCount; ii++, s += 3) {
clippedTrianglesItems[s] = index;
@ -246,7 +248,7 @@ export class SkeletonClipping {
index += clipOutputCount + 1;
} else {
let clippedVerticesItems = Utils.setArraySize(clippedVertices, s + 3 * stride);
const clippedVerticesItems = Utils.setArraySize(clippedVertices, s + 3 * stride);
clippedVerticesItems[s] = x1;
clippedVerticesItems[s + 1] = y1;
clippedVerticesItems[s + 2] = light.r;
@ -310,7 +312,7 @@ export class SkeletonClipping {
}
s = clippedTriangles.length;
let clippedTrianglesItems = Utils.setArraySize(clippedTriangles, s + 3);
const clippedTrianglesItems = Utils.setArraySize(clippedTriangles, s + 3);
clippedTrianglesItems[s] = index;
clippedTrianglesItems[s + 1] = (index + 1);
clippedTrianglesItems[s + 2] = (index + 2);
@ -325,6 +327,7 @@ export class SkeletonClipping {
public clipTrianglesUnpacked (vertices: NumberArrayLike, triangles: NumberArrayLike | Uint32Array, trianglesLength: number, uvs: NumberArrayLike) {
const clipOutput = this.clipOutput;
let clippedVertices = this._clippedVerticesTyped, clippedUVs = this._clippedUVsTyped, clippedTriangles = this._clippedTrianglesTyped;
// biome-ignore lint/style/noNonNullAssertion: clipStart define it
const polygons = this.clippingPolygons!;
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
* 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>) {
let originalOutput = output;
const originalOutput = output;
let clipped = false;
// Avoid copy at the end.
@ -486,20 +489,20 @@ export class SkeletonClipping {
input.push(y1);
output.length = 0;
let clippingVerticesLast = clippingArea.length - 4;
let clippingVertices = clippingArea;
const clippingVerticesLast = clippingArea.length - 4;
const clippingVertices = clippingArea;
for (let i = 0; ; i += 2) {
let edgeX = clippingVertices[i], edgeY = clippingVertices[i + 1];
let ex = edgeX - clippingVertices[i + 2], ey = edgeY - clippingVertices[i + 3];
const edgeX = clippingVertices[i], edgeY = clippingVertices[i + 1];
const ex = edgeX - clippingVertices[i + 2], ey = edgeY - clippingVertices[i + 3];
let outputStart = output.length;
let inputVertices = input;
const outputStart = output.length;
const inputVertices = input;
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;
let inputX2 = inputVertices[ii], inputY2 = inputVertices[ii + 1];
let s2 = ey * (edgeX - inputX2) > ex * (edgeY - inputY2);
let s1 = ey * (edgeX - inputX) - ex * (edgeY - inputY);
const inputX2 = inputVertices[ii], inputY2 = inputVertices[ii + 1];
const s2 = ey * (edgeX - inputX2) > ex * (edgeY - inputY2);
const s1 = ey * (edgeX - inputX) - ex * (edgeY - inputY);
if (s1 > 0) {
if (s2) { // v1 inside, v2 inside
output.push(inputX2);
@ -507,7 +510,7 @@ export class SkeletonClipping {
continue;
}
// 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) {
output.push(inputX + ix * t);
output.push(inputY + iy * t);
@ -517,7 +520,7 @@ export class SkeletonClipping {
continue;
}
} 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) {
output.push(inputX + ix * t);
output.push(inputY + iy * t);
@ -532,7 +535,7 @@ export class SkeletonClipping {
clipped = true;
}
if (outputStart == output.length) { // All edges outside.
if (outputStart === output.length) { // All edges outside.
originalOutput.length = 0;
return true;
}
@ -540,14 +543,14 @@ export class SkeletonClipping {
output.push(output[0]);
output.push(output[1]);
if (i == clippingVerticesLast) break;
let temp = output;
if (i === clippingVerticesLast) break;
const temp = output;
output = input;
output.length = 0;
input = temp;
}
if (originalOutput != output) {
if (originalOutput !== output) {
originalOutput.length = 0;
for (let i = 0, n = output.length - 2; i < n; i++)
originalOutput[i] = output[i];
@ -558,8 +561,8 @@ export class SkeletonClipping {
}
public static makeClockwise (polygon: NumberArrayLike) {
let vertices = polygon;
let verticeslength = polygon.length;
const vertices = polygon;
const verticeslength = polygon.length;
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) {
@ -572,8 +575,8 @@ export class SkeletonClipping {
if (area < 0) return;
for (let i = 0, lastX = verticeslength - 2, n = verticeslength >> 1; i < n; i += 2) {
let x = vertices[i], y = vertices[i + 1];
let other = lastX - i;
const x = vertices[i], y = vertices[i + 1];
const other = lastX - i;
vertices[i] = vertices[other];
vertices[i + 1] = vertices[other + 1];
vertices[other] = x;

View File

@ -27,12 +27,12 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
import { Animation } from "./Animation"
import { BoneData } from "./BoneData.js";
import { ConstraintData } from "./ConstraintData";
import { EventData } from "./EventData.js";
import { Skin } from "./Skin.js";
import { SlotData } from "./SlotData.js";
import type { Animation } from "./Animation"
import type { BoneData } from "./BoneData.js";
import type { ConstraintData } from "./ConstraintData";
import type { EventData } from "./EventData.js";
import type { Skin } from "./Skin.js";
import type { SlotData } from "./SlotData.js";
/** Stores the setup pose and all of the stateless data for a skeleton.
*
@ -44,12 +44,12 @@ export class SkeletonData {
name: string | null = null;
/** 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. */
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.
*
@ -58,13 +58,14 @@ export class SkeletonData {
defaultSkin: Skin | null = null;
/** The skeleton's events. */
events = new Array<EventData>();
events = [] as EventData[];
/** The skeleton's animations. */
animations = new Array<Animation>();
animations = [] as Animation[];
/** 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. */
x: number = 0;
@ -103,9 +104,9 @@ export class SkeletonData {
* @returns May be null. */
findBone (boneName: string) {
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++)
if (bones[i].name == boneName) return bones[i];
if (bones[i].name === boneName) return bones[i];
return null;
}
@ -114,9 +115,9 @@ export class SkeletonData {
* @returns May be null. */
findSlot (slotName: string) {
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++)
if (slots[i].name == slotName) return slots[i];
if (slots[i].name === slotName) return slots[i];
return null;
}
@ -125,9 +126,9 @@ export class SkeletonData {
* @returns May be null. */
findSkin (skinName: string) {
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++)
if (skins[i].name == skinName) return skins[i];
if (skins[i].name === skinName) return skins[i];
return null;
}
@ -136,9 +137,9 @@ export class SkeletonData {
* @returns May be null. */
findEvent (eventDataName: string) {
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++)
if (events[i].name == eventDataName) return events[i];
if (events[i].name === eventDataName) return events[i];
return null;
}
@ -147,20 +148,21 @@ export class SkeletonData {
* @returns May be null. */
findAnimation (animationName: string) {
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++)
if (animations[i].name == animationName) return animations[i];
if (animations[i].name === animationName) return animations[i];
return null;
}
// --- 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 {
if (!constraintName) throw new Error("constraintName cannot be null.");
if (type == null) throw new Error("type cannot be null.");
const constraints = this.constraints;
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;
}
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 indicesCount: number;
let attachmentColor: Color;
// biome-ignore lint/suspicious/noExplicitAny: texture depends on the runtime
let texture: any;
if (attachment instanceof RegionAttachment) {
@ -270,6 +271,7 @@ interface RenderCommand {
numVertices: number;
numIndices: number;
blendMode: BlendMode;
// biome-ignore lint/suspicious/noExplicitAny: texture depends on the runtime
texture: any;
next?: RenderCommand;
}

View File

@ -27,12 +27,12 @@
* 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 { BoneData } from "./BoneData.js";
import { ConstraintData } from "./ConstraintData.js";
import { Skeleton } from "./Skeleton.js";
import { Color, StringMap } from "./Utils.js";
import type { BoneData } from "./BoneData.js";
import type { ConstraintData } from "./ConstraintData.js";
import type { Skeleton } from "./Skeleton.js";
import { Color, type StringMap } from "./Utils.js";
/** Stores an entry in the skin consisting of the slot index, name, and attachment **/
export class SkinEntry {
@ -47,9 +47,10 @@ export class Skin {
/** The skin's name, which is unique across all skins in the skeleton. */
name: string;
attachments = new Array<StringMap<Attachment>>();
bones = Array<BoneData>();
constraints = new Array<ConstraintData<any, any>>();
attachments = [] as StringMap<Attachment>[];
bones = [] as BoneData[];
// 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. */
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. */
setAttachment (slotIndex: number, name: string, attachment: Attachment) {
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 (!attachments[slotIndex]) attachments[slotIndex] = {};
attachments[slotIndex][name] = attachment;
@ -71,10 +72,10 @@ export class Skin {
/** Adds all attachments, bones, and constraints from the specified skin to this skin. */
addSkin (skin: Skin) {
for (let i = 0; i < skin.bones.length; i++) {
let bone = skin.bones[i];
const bone = skin.bones[i];
let contained = false;
for (let ii = 0; ii < this.bones.length; ii++) {
if (this.bones[ii] == bone) {
if (this.bones[ii] === bone) {
contained = true;
break;
}
@ -83,10 +84,10 @@ export class Skin {
}
for (let i = 0; i < skin.constraints.length; i++) {
let constraint = skin.constraints[i];
const constraint = skin.constraints[i];
let contained = false;
for (let ii = 0; ii < this.constraints.length; ii++) {
if (this.constraints[ii] == constraint) {
if (this.constraints[ii] === constraint) {
contained = true;
break;
}
@ -94,9 +95,9 @@ export class Skin {
if (!contained) this.constraints.push(constraint);
}
let attachments = skin.getAttachments();
const attachments = skin.getAttachments();
for (let i = 0; i < attachments.length; i++) {
var attachment = attachments[i];
const attachment = attachments[i];
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. */
copySkin (skin: Skin) {
for (let i = 0; i < skin.bones.length; i++) {
let bone = skin.bones[i];
const bone = skin.bones[i];
let contained = false;
for (let ii = 0; ii < this.bones.length; ii++) {
if (this.bones[ii] == bone) {
if (this.bones[ii] === bone) {
contained = true;
break;
}
@ -117,10 +118,10 @@ export class Skin {
}
for (let i = 0; i < skin.constraints.length; i++) {
let constraint = skin.constraints[i];
const constraint = skin.constraints[i];
let contained = false;
for (let ii = 0; ii < this.constraints.length; ii++) {
if (this.constraints[ii] == constraint) {
if (this.constraints[ii] === constraint) {
contained = true;
break;
}
@ -128,9 +129,9 @@ export class Skin {
if (!contained) this.constraints.push(constraint);
}
let attachments = skin.getAttachments();
const attachments = skin.getAttachments();
for (let i = 0; i < attachments.length; i++) {
var attachment = attachments[i];
const attachment = attachments[i];
if (!attachment.attachment) continue;
if (attachment.attachment instanceof MeshAttachment) {
attachment.attachment = attachment.attachment.newLinkedMesh();
@ -144,24 +145,24 @@ export class Skin {
/** Returns the attachment for the specified slot index and name, or null. */
getAttachment (slotIndex: number, name: string): Attachment | null {
let dictionary = this.attachments[slotIndex];
const dictionary = this.attachments[slotIndex];
return dictionary ? dictionary[name] : null;
}
/** Removes the attachment in the skin for the specified slot index and name, if any. */
removeAttachment (slotIndex: number, name: string) {
let dictionary = this.attachments[slotIndex];
const dictionary = this.attachments[slotIndex];
if (dictionary) delete dictionary[name];
}
/** Returns all attachments in this skin. */
getAttachments (): Array<SkinEntry> {
let entries = new Array<SkinEntry>();
for (var i = 0; i < this.attachments.length; i++) {
let slotAttachments = this.attachments[i];
const entries: SkinEntry[] = [];
for (let i = 0; i < this.attachments.length; i++) {
const slotAttachments = this.attachments[i];
if (slotAttachments) {
for (let name in slotAttachments) {
let attachment = slotAttachments[name];
for (const name in slotAttachments) {
const attachment = slotAttachments[name];
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. */
getAttachmentsForSlot (slotIndex: number, attachments: Array<SkinEntry>) {
let slotAttachments = this.attachments[slotIndex];
const slotAttachments = this.attachments[slotIndex];
if (slotAttachments) {
for (let name in slotAttachments) {
let attachment = slotAttachments[name];
for (const name in slotAttachments) {
const attachment = slotAttachments[name];
if (attachment) attachments.push(new SkinEntry(slotIndex, name, attachment));
}
}
@ -191,14 +192,14 @@ export class Skin {
attachAll (skeleton: Skeleton, oldSkin: Skin) {
let slotIndex = 0;
for (let i = 0; i < skeleton.slots.length; i++) {
let slot = skeleton.slots[i];
let slotAttachment = slot.pose.getAttachment();
const slot = skeleton.slots[i];
const slotAttachment = slot.pose.getAttachment();
if (slotAttachment && slotIndex < oldSkin.attachments.length) {
let dictionary = oldSkin.attachments[slotIndex];
for (let key in dictionary) {
let skinAttachment: Attachment = dictionary[key];
if (slotAttachment == skinAttachment) {
let attachment = this.getAttachment(slotIndex, key);
const dictionary = oldSkin.attachments[slotIndex];
for (const key in dictionary) {
const skinAttachment: Attachment = dictionary[key];
if (slotAttachment === skinAttachment) {
const attachment = this.getAttachment(slotIndex, key);
if (attachment) slot.pose.setAttachment(attachment);
break;
}

View File

@ -27,13 +27,13 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
import { Animation } from "./Animation.js";
import { BoneData } from "./BoneData.js";
import type { Animation } from "./Animation.js";
import type { BoneData } from "./BoneData.js";
import { ConstraintData } from "./ConstraintData.js";
import { Skeleton } from "./Skeleton.js";
import type { Skeleton } from "./Skeleton.js";
import { Slider } from "./Slider.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}.
*

View File

@ -27,7 +27,7 @@
* 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. */
export class SliderPose implements Pose<SliderPose> {

View File

@ -27,10 +27,10 @@
* 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 { Skeleton } from "./Skeleton.js";
import { SlotData } from "./SlotData.js";
import type { Skeleton } from "./Skeleton.js";
import type { SlotData } from "./SlotData.js";
import { SlotPose } from "./SlotPose.js";
import { Color } from "./Utils.js";
@ -59,6 +59,7 @@ export class Slot extends Posed<SlotData, SlotPose, SlotPose> {
setupPose () {
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!);
this.pose.sequenceIndex = this.data.setup.sequenceIndex;
if (!this.data.attachmentName)

View File

@ -27,11 +27,10 @@
* 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 { SlotPose } from "./SlotPose.js";
import type { Skeleton } from "./Skeleton.js";
import { SlotPose } from "./SlotPose.js";
/** Stores the setup pose for a {@link Slot}. */
export class SlotData extends PosedData<SlotPose> {

View File

@ -27,11 +27,11 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
import { Pose } from "./Pose.js";
import { Color } from "./Utils.js";
import type { Attachment } 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 { 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
* 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
* {@link DeformTimeline}. */
readonly deform = new Array<number>();
readonly deform = [] as number[];
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
* specified attachment. */
setAttachment (attachment: Attachment | null): void {
if (this.attachment == attachment) return;
if (this.attachment === attachment) return;
if (!(attachment instanceof VertexAttachment) || !(this.attachment instanceof VertexAttachment)
|| attachment.timelineAttachment != this.attachment.timelineAttachment) {
|| attachment.timelineAttachment !== this.attachment.timelineAttachment) {
this.deform.length = 0;
}
this.attachment = attachment;

View File

@ -27,6 +27,8 @@
* 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 {
protected _image: HTMLImageElement | ImageBitmap | any;

View File

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

View File

@ -27,12 +27,12 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
import { Bone } from "./Bone.js";
import { BonePose } from "./BonePose.js";
import type { Bone } from "./Bone.js";
import type { BonePose } from "./BonePose.js";
import { Constraint } from "./Constraint.js";
import { Physics } from "./Physics.js";
import { Skeleton } from "./Skeleton.js";
import { TransformConstraintData } from "./TransformConstraintData.js";
import type { Physics } from "./Physics.js";
import type { Skeleton } from "./Skeleton.js";
import type { TransformConstraintData } from "./TransformConstraintData.js";
import { TransformConstraintPose } from "./TransformConstraintPose.js";
import { MathUtils } from "./Utils.js";
@ -53,7 +53,7 @@ export class TransformConstraint extends Constraint<TransformConstraint, Transfo
super(data, new TransformConstraintPose(), new TransformConstraintPose());
if (!skeleton) throw new Error("skeleton cannot be null.");
this.bones = new Array<BonePose>();
this.bones = [] as BonePose[];
for (const boneData of data.bones)
this.bones.push(skeleton.bones[boneData.index].constrained);
@ -70,7 +70,7 @@ export class TransformConstraint extends Constraint<TransformConstraint, Transfo
update (skeleton: Skeleton, physics: Physics) {
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 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.
*****************************************************************************/
import type { BoneData } from "./BoneData.js";
import type { BonePose } from "./BonePose.js";
import { ConstraintData } from "./ConstraintData.js";
import { BoneData } from "./BoneData.js";
import type { Skeleton } from "./Skeleton.js";
import { TransformConstraint } from "./TransformConstraint.js";
import { MathUtils } from "./Utils.js";
import { Skeleton } from "./Skeleton.js";
import { TransformConstraintPose } from "./TransformConstraintPose.js";
import { BonePose } from "./BonePose.js";
import { MathUtils } from "./Utils.js";
/** Stores the setup pose for a {@link TransformConstraint}.
*
@ -47,7 +47,7 @@ export class TransformConstraintData extends ConstraintData<TransformConstraint,
public static readonly SHEARY = 5;
/** 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. */
public set source (source: BoneData) { this._source = source; }
@ -275,7 +275,7 @@ export class ToScaleX extends ToProperty {
if (local) {
if (additive)
bone.scaleX *= 1 + (value - 1) * pose.mixScaleX;
else if (bone.scaleX != 0) //
else if (bone.scaleX !== 0) //
bone.scaleX += (value - bone.scaleX) * pose.mixScaleX;
} else if (additive) {
const s = 1 + (value - 1) * pose.mixScaleX;
@ -283,7 +283,7 @@ export class ToScaleX extends ToProperty {
bone.c *= s;
} else {
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;
bone.a *= s;
bone.c *= s;
@ -309,7 +309,7 @@ export class ToScaleY extends ToProperty {
if (local) {
if (additive)
bone.scaleY *= 1 + (value - 1) * pose.mixScaleY;
else if (bone.scaleY != 0) //
else if (bone.scaleY !== 0) //
bone.scaleY += (value - bone.scaleY) * pose.mixScaleY;
} else if (additive) {
const s = 1 + (value - 1) * pose.mixScaleY;
@ -317,7 +317,7 @@ export class ToScaleY extends ToProperty {
bone.d *= s;
} else {
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;
bone.b *= s;
bone.d *= s;

View File

@ -27,7 +27,7 @@
* 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. */
export class TransformConstraintPose implements Pose<TransformConstraintPose> {

View File

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

View File

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

View File

@ -27,9 +27,9 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
import { Skeleton } from "src/Skeleton.js";
import { Slot } from "../Slot.js";
import { NumberArrayLike, Utils } from "../Utils.js";
import type { Skeleton } from "src/Skeleton.js";
import type { Slot } from "../Slot.js";
import { type NumberArrayLike, Utils } from "../Utils.js";
/** The base class for all attachments. */
export abstract class Attachment {
@ -88,17 +88,17 @@ export abstract class VertexAttachment extends Attachment {
stride: number) {
count = offset + (count >> 1) * stride;
let deformArray = slot.applied.deform;
const deformArray = slot.applied.deform;
let vertices = this.vertices;
let bones = this.bones;
const bones = this.bones;
if (!bones) {
if (deformArray.length > 0) vertices = deformArray;
let bone = slot.bone.applied;
let x = bone.worldX;
let y = bone.worldY;
let a = bone.a, b = bone.b, c = bone.c, d = bone.d;
const bone = slot.bone.applied;
const x = bone.worldX;
const y = bone.worldY;
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) {
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 + 1] = vx * c + vy * d + y;
}
@ -106,19 +106,19 @@ export abstract class VertexAttachment extends Attachment {
}
let v = 0, skip = 0;
for (let i = 0; i < start; i += 2) {
let n = bones[v];
const n = bones[v];
v += n + 1;
skip += n;
}
let skeletonBones = skeleton.bones;
if (deformArray.length == 0) {
const skeletonBones = skeleton.bones;
if (deformArray.length === 0) {
for (let w = offset, b = skip * 3; w < count; w += stride) {
let wx = 0, wy = 0;
let n = bones[v++];
n += v;
for (; v < n; v++, b += 3) {
let bone = skeletonBones[bones[v]].applied;
let vx = vertices[b], vy = vertices[b + 1], weight = vertices[b + 2];
const bone = skeletonBones[bones[v]].applied;
const vx = vertices[b], vy = vertices[b + 1], weight = vertices[b + 2];
wx += (vx * bone.a + vy * bone.b + bone.worldX) * 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;
}
} else {
let deform = deformArray;
const deform = deformArray;
for (let w = offset, b = skip * 3, f = skip << 1; w < count; w += stride) {
let wx = 0, wy = 0;
let n = bones[v++];
n += v;
for (; v < n; v++, b += 3, f += 2) {
let bone = skeletonBones[bones[v]].applied;
let vx = vertices[b] + deform[f], vy = vertices[b + 1] + deform[f + 1], weight = vertices[b + 2];
const bone = skeletonBones[bones[v]].applied;
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;
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.
*****************************************************************************/
import { Skin } from "../Skin.js";
import { BoundingBoxAttachment } from "./BoundingBoxAttachment.js";
import { ClippingAttachment } from "./ClippingAttachment.js";
import { MeshAttachment } from "./MeshAttachment.js";
import { PathAttachment } from "./PathAttachment.js";
import { PointAttachment } from "./PointAttachment.js";
import { RegionAttachment } from "./RegionAttachment.js";
import { Sequence } from "./Sequence.js";
import type { Skin } from "../Skin.js";
import type { BoundingBoxAttachment } from "./BoundingBoxAttachment.js";
import type { ClippingAttachment } from "./ClippingAttachment.js";
import type { MeshAttachment } from "./MeshAttachment.js";
import type { PathAttachment } from "./PathAttachment.js";
import type { PointAttachment } from "./PointAttachment.js";
import type { RegionAttachment } from "./RegionAttachment.js";
import type { Sequence } from "./Sequence.js";
/** The interface which can be implemented to customize creating and populating attachments.
*

View File

@ -28,7 +28,7 @@
*****************************************************************************/
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
* effects, and more.
@ -43,7 +43,7 @@ export class BoundingBoxAttachment extends VertexAttachment {
}
copy (): Attachment {
let copy = new BoundingBoxAttachment(this.name);
const copy = new BoundingBoxAttachment(this.name);
this.copyTo(copy);
copy.color.setFromColor(this.color);
return copy;

View File

@ -27,9 +27,9 @@
* 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 { 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. */
export class ClippingAttachment extends VertexAttachment {
@ -47,7 +47,7 @@ export class ClippingAttachment extends VertexAttachment {
}
copy (): Attachment {
let copy = new ClippingAttachment(this.name);
const copy = new ClippingAttachment(this.name);
this.copyTo(copy);
copy.endSlot = this.endSlot;
copy.color.setFromColor(this.color);

View File

@ -27,9 +27,9 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
import { TextureRegion } from "../Texture.js"
import { Color } from "../Utils.js"
import { Sequence } from "./Sequence.js"
import type { TextureRegion } from "../Texture.js"
import type { Color } from "../Utils.js"
import type { Sequence } from "./Sequence.js"
export interface HasTextureRegion {
/** 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.
*****************************************************************************/
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 { Color, NumberArrayLike, Utils } from "../Utils.js";
import { VertexAttachment, Attachment } from "./Attachment.js";
import { HasTextureRegion } from "./HasTextureRegion.js";
import { Sequence } from "./Sequence.js";
import { Slot } from "../Slot.js";
import { Skeleton } from "src/Skeleton.js";
import { Color, type NumberArrayLike, Utils } from "../Utils.js";
import { type Attachment, VertexAttachment } from "./Attachment.js";
import type { HasTextureRegion } from "./HasTextureRegion.js";
import type { Sequence } from "./Sequence.js";
/** 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.
@ -88,14 +88,14 @@ export class MeshAttachment extends VertexAttachment implements HasTextureRegion
* the {@link #regionUVs} are changed. */
updateRegion () {
if (!this.region) throw new Error("Region not set.");
let regionUVs = this.regionUVs;
if (!this.uvs || this.uvs.length != regionUVs.length) this.uvs = Utils.newFloatArray(regionUVs.length);
let uvs = this.uvs;
let n = this.uvs.length;
const regionUVs = this.regionUVs;
if (!this.uvs || this.uvs.length !== regionUVs.length) this.uvs = Utils.newFloatArray(regionUVs.length);
const uvs = this.uvs;
const n = this.uvs.length;
let u = this.region.u, v = this.region.v, width = 0, height = 0;
if (this.region instanceof TextureAtlasRegion) {
let region = this.region, page = region.page;
let textureWidth = page.width, textureHeight = page.height;
const region = this.region, page = region.page;
const textureWidth = page.width, textureHeight = page.height;
switch (region.degrees) {
case 90:
u -= (region.originalHeight - region.offsetY - region.height) / textureWidth;
@ -171,7 +171,7 @@ export class MeshAttachment extends VertexAttachment implements HasTextureRegion
copy (): Attachment {
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.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. **/
newLinkedMesh (): MeshAttachment {
let copy = new MeshAttachment(this.name, this.path);
const copy = new MeshAttachment(this.name, this.path);
copy.region = this.region;
copy.color.setFromColor(this.color);
copy.timelineAttachment = this.timelineAttachment;

View File

@ -28,7 +28,7 @@
*****************************************************************************/
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.
*
@ -54,7 +54,7 @@ export class PathAttachment extends VertexAttachment {
}
copy (): Attachment {
let copy = new PathAttachment(this.name);
const copy = new PathAttachment(this.name);
this.copyTo(copy);
copy.lengths = new Array<number>(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.
*****************************************************************************/
import { BonePose } from "src/BonePose.js";
import { Color, Vector2, MathUtils } from "../Utils.js";
import { VertexAttachment, Attachment } from "./Attachment.js";
import type { BonePose } from "src/BonePose.js";
import { Color, MathUtils, type Vector2 } from "../Utils.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
* 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 {
let copy = new PointAttachment(this.name);
const copy = new PointAttachment(this.name);
copy.x = this.x;
copy.y = this.y;
copy.rotation = this.rotation;

View File

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

View File

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

View File

@ -919,7 +919,7 @@ export class SpinePlayer implements Disposable {
// Draw the background image.
let bgImage = config.backgroundImage;
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)
renderer.drawTexture(texture, bgImage.x, bgImage.y, bgImage.width, bgImage.height);
else

View File

@ -978,7 +978,8 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable
const skeletonLoader = isBinary ? new SkeletonBinary(atlasLoader) : new SkeletonJson(atlasLoader);
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 skeletonData = (skeletonDataInput || this.skeleton?.data) ?? skeletonLoader.readSkeletonData(skeletonFile);