[ts] Processed spine-ts (not player) with biome.

This commit is contained in:
Davide Tantillo 2025-10-27 10:57:30 +01:00
parent 9ea694b16f
commit c3ac62d1dd
62 changed files with 956 additions and 809 deletions

View File

@ -0,0 +1,64 @@
{
"skeleton": {
"hash": "kUVJGzLCHio",
"spine": "4.2.43",
"x": -66,
"y": -141.5,
"width": 142,
"height": 232,
"images": "./images/",
"audio": "./audio"
},
"bones": [
{ "name": "root" },
{ "name": "empty_1", "parent": "root", "x": -44, "y": 53.5 },
{ "name": "empty_8", "parent": "root", "x": 5, "y": -104.5 },
{ "name": "empty_7", "parent": "root", "x": -44, "y": -104.5 },
{ "name": "empty_6", "parent": "root", "x": 54, "y": -25.5 },
{ "name": "empty_5", "parent": "root", "x": 5, "y": -25.5 },
{ "name": "empty_4", "parent": "root", "x": -44, "y": -25.5 },
{ "name": "empty_3", "parent": "root", "x": 54, "y": 53.5 },
{ "name": "empty_2", "parent": "root", "x": 5, "y": 53.5 }
],
"slots": [
{ "name": "mask", "bone": "root", "attachment": "mask" },
{ "name": "empty_1", "bone": "empty_1", "attachment": "empty" },
{ "name": "empty_2", "bone": "empty_2", "attachment": "empty" },
{ "name": "empty_3", "bone": "empty_3", "attachment": "empty" },
{ "name": "empty_4", "bone": "empty_4", "attachment": "empty" },
{ "name": "empty_5", "bone": "empty_5", "attachment": "empty" },
{ "name": "empty_6", "bone": "empty_6", "attachment": "empty" },
{ "name": "empty_7", "bone": "empty_7", "attachment": "empty" },
{ "name": "empty_8", "bone": "empty_8", "attachment": "empty" }
],
"skins": [
{
"name": "default",
"attachments": {
"empty_1": { "empty": { "width": 44, "height": 74 } },
"empty_2": { "empty": { "width": 44, "height": 74 } },
"empty_3": { "empty": { "width": 44, "height": 74 } },
"empty_4": { "empty": { "width": 44, "height": 74 } },
"empty_5": { "empty": { "width": 44, "height": 74 } },
"empty_6": { "empty": { "width": 44, "height": 74 } },
"empty_7": { "empty": { "width": 44, "height": 74 } },
"empty_8": { "empty": { "width": 44, "height": 74 } },
"mask": {
"mask": {
"type": "clipping",
"end": "mask",
"vertexCount": 16,
"vertices": [
-53.67, -27.41, -147.5, -13.5, -65, 33.5, -92.5, 70.5, -21.01,
32.95, 12.5, 114.5, 36.79, 30.32, 129.5, 66.5, 93, 30.5, 124.5,
-5.5, 98, -51.5, 139.5, -97.5, 38.8, -65.21, -9.5, -181.5, -26.27,
-70.62, -106.5, -105.5
],
"color": "ce3a3aff"
}
}
}
}
],
"animations": { "animation": {} }
}

View File

@ -16,7 +16,8 @@
"useExponentiationOperator": "off" "useExponentiationOperator": "off"
}, },
"suspicious": { "suspicious": {
"noAssignInExpressions": "off" "noAssignInExpressions": "off",
"noPrototypeBuiltins": "off"
} }
} }
} }

View File

@ -27,7 +27,7 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { Texture, TextureFilter, TextureWrap } from "@esotericsoftware/spine-core"; import { Texture, type TextureFilter, type TextureWrap } from "@esotericsoftware/spine-core";
export class CanvasTexture extends Texture { export class CanvasTexture extends Texture {
constructor (image: HTMLImageElement | ImageBitmap) { constructor (image: HTMLImageElement | ImageBitmap) {

View File

@ -27,8 +27,8 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { Utils, Color, Skeleton, RegionAttachment, BlendMode, MeshAttachment, Slot, TextureRegion, TextureAtlasRegion } from "@esotericsoftware/spine-core"; import { type BlendMode, Color, MeshAttachment, RegionAttachment, type Skeleton, type Slot, type TextureRegion, Utils } from "@esotericsoftware/spine-core";
import { CanvasTexture } from "./CanvasTexture.js"; import type { CanvasTexture } from "./CanvasTexture.js";
const worldVertices = Utils.newFloatArray(8); const worldVertices = Utils.newFloatArray(8);
@ -53,28 +53,28 @@ export class SkeletonRenderer {
} }
private drawImages (skeleton: Skeleton) { private drawImages (skeleton: Skeleton) {
let ctx = this.ctx; const ctx = this.ctx;
let color = this.tempColor; const color = this.tempColor;
let skeletonColor = skeleton.color; const skeletonColor = skeleton.color;
let drawOrder = skeleton.drawOrder; const drawOrder = skeleton.drawOrder;
if (this.debugRendering) ctx.strokeStyle = "green"; if (this.debugRendering) ctx.strokeStyle = "green";
for (let i = 0, n = drawOrder.length; i < n; i++) { for (let i = 0, n = drawOrder.length; i < n; i++) {
const slot = drawOrder[i]; const slot = drawOrder[i];
let bone = slot.bone; const bone = slot.bone;
if (!bone.active) continue; if (!bone.active) continue;
let pose = slot.applied; const pose = slot.applied;
let attachment = pose.attachment; const attachment = pose.attachment;
if (!(attachment instanceof RegionAttachment)) continue; if (!(attachment instanceof RegionAttachment)) continue;
attachment.computeWorldVertices(slot, worldVertices, 0, 2); attachment.computeWorldVertices(slot, worldVertices, 0, 2);
let region: TextureRegion = <TextureRegion>attachment.region; const region: TextureRegion = <TextureRegion>attachment.region;
let image: HTMLImageElement = (<CanvasTexture>region.texture).getImage() as HTMLImageElement; const image: HTMLImageElement = (<CanvasTexture>region.texture).getImage() as HTMLImageElement;
let slotColor = pose.color; const slotColor = pose.color;
let regionColor = attachment.color; const regionColor = attachment.color;
color.set(skeletonColor.r * slotColor.r * regionColor.r, color.set(skeletonColor.r * slotColor.r * regionColor.r,
skeletonColor.g * slotColor.g * regionColor.g, skeletonColor.g * slotColor.g * regionColor.g,
skeletonColor.b * slotColor.b * regionColor.b, skeletonColor.b * slotColor.b * regionColor.b,
@ -86,13 +86,13 @@ export class SkeletonRenderer {
ctx.translate(attachment.offset[0], attachment.offset[1]); ctx.translate(attachment.offset[0], attachment.offset[1]);
ctx.rotate(attachment.rotation * Math.PI / 180); ctx.rotate(attachment.rotation * Math.PI / 180);
let atlasScale = attachment.width / region.originalWidth; const atlasScale = attachment.width / region.originalWidth;
ctx.scale(atlasScale * attachment.scaleX, atlasScale * attachment.scaleY); ctx.scale(atlasScale * attachment.scaleX, atlasScale * attachment.scaleY);
let w = region.width, h = region.height; let w = region.width, h = region.height;
ctx.translate(w / 2, h / 2); ctx.translate(w / 2, h / 2);
if (attachment.region!.degrees == 90) { if (attachment.region?.degrees === 90) {
let t = w; const t = w;
w = h; w = h;
h = t; h = t;
ctx.rotate(-Math.PI / 2); ctx.rotate(-Math.PI / 2);
@ -108,10 +108,10 @@ export class SkeletonRenderer {
} }
private drawTriangles (skeleton: Skeleton) { private drawTriangles (skeleton: Skeleton) {
let ctx = this.ctx; const ctx = this.ctx;
let color = this.tempColor; const color = this.tempColor;
let skeletonColor = skeleton.color; const skeletonColor = skeleton.color;
let drawOrder = skeleton.drawOrder; const drawOrder = skeleton.drawOrder;
let blendMode: BlendMode | null = null; let blendMode: BlendMode | null = null;
let vertices: ArrayLike<number> = this.vertices; let vertices: ArrayLike<number> = this.vertices;
@ -119,28 +119,28 @@ export class SkeletonRenderer {
for (let i = 0, n = drawOrder.length; i < n; i++) { for (let i = 0, n = drawOrder.length; i < n; i++) {
const slot = drawOrder[i]; const slot = drawOrder[i];
let pose = slot.applied; const pose = slot.applied;
let attachment = pose.attachment; const attachment = pose.attachment;
let texture: HTMLImageElement; let texture: HTMLImageElement;
if (attachment instanceof RegionAttachment) { if (attachment instanceof RegionAttachment) {
let regionAttachment = <RegionAttachment>attachment; const regionAttachment = <RegionAttachment>attachment;
vertices = this.computeRegionVertices(slot, regionAttachment, false); vertices = this.computeRegionVertices(slot, regionAttachment, false);
triangles = SkeletonRenderer.QUAD_TRIANGLES; triangles = SkeletonRenderer.QUAD_TRIANGLES;
texture = (<CanvasTexture>regionAttachment.region!.texture).getImage() as HTMLImageElement; texture = (<CanvasTexture>regionAttachment.region?.texture).getImage() as HTMLImageElement;
} else if (attachment instanceof MeshAttachment) { } else if (attachment instanceof MeshAttachment) {
let mesh = <MeshAttachment>attachment; const mesh = <MeshAttachment>attachment;
vertices = this.computeMeshVertices(slot, mesh, false); vertices = this.computeMeshVertices(slot, mesh, false);
triangles = mesh.triangles; triangles = mesh.triangles;
texture = (<CanvasTexture>mesh.region!.texture).getImage() as HTMLImageElement; texture = (<CanvasTexture>mesh.region?.texture).getImage() as HTMLImageElement;
} else } else
continue; continue;
if (texture) { if (texture) {
if (slot.data.blendMode != blendMode) blendMode = slot.data.blendMode; if (slot.data.blendMode !== blendMode) blendMode = slot.data.blendMode;
let slotColor = pose.color; const slotColor = pose.color;
let attachmentColor = attachment.color; const attachmentColor = attachment.color;
color.set(skeletonColor.r * slotColor.r * attachmentColor.r, color.set(skeletonColor.r * slotColor.r * attachmentColor.r,
skeletonColor.g * slotColor.g * attachmentColor.g, skeletonColor.g * slotColor.g * attachmentColor.g,
skeletonColor.b * slotColor.b * attachmentColor.b, skeletonColor.b * slotColor.b * attachmentColor.b,
@ -148,12 +148,12 @@ export class SkeletonRenderer {
ctx.globalAlpha = color.a; ctx.globalAlpha = color.a;
for (var j = 0; j < triangles.length; j += 3) { for (let j = 0; j < triangles.length; j += 3) {
let t1 = triangles[j] * 8, t2 = triangles[j + 1] * 8, t3 = triangles[j + 2] * 8; const t1 = triangles[j] * 8, t2 = triangles[j + 1] * 8, t3 = triangles[j + 2] * 8;
let x0 = vertices[t1], y0 = vertices[t1 + 1], u0 = vertices[t1 + 6], v0 = vertices[t1 + 7]; const x0 = vertices[t1], y0 = vertices[t1 + 1], u0 = vertices[t1 + 6], v0 = vertices[t1 + 7];
let x1 = vertices[t2], y1 = vertices[t2 + 1], u1 = vertices[t2 + 6], v1 = vertices[t2 + 7]; const x1 = vertices[t2], y1 = vertices[t2 + 1], u1 = vertices[t2 + 6], v1 = vertices[t2 + 7];
let x2 = vertices[t3], y2 = vertices[t3 + 1], u2 = vertices[t3 + 6], v2 = vertices[t3 + 7]; const x2 = vertices[t3], y2 = vertices[t3 + 1], u2 = vertices[t3 + 6], v2 = vertices[t3 + 7];
this.drawTriangle(texture, x0, y0, u0, v0, x1, y1, u1, v1, x2, y2, u2, v2); this.drawTriangle(texture, x0, y0, u0, v0, x1, y1, u1, v1, x2, y2, u2, v2);
@ -178,7 +178,7 @@ export class SkeletonRenderer {
private drawTriangle (img: HTMLImageElement, x0: number, y0: number, u0: number, v0: number, private drawTriangle (img: HTMLImageElement, x0: number, y0: number, u0: number, v0: number,
x1: number, y1: number, u1: number, v1: number, x1: number, y1: number, u1: number, v1: number,
x2: number, y2: number, u2: number, v2: number) { x2: number, y2: number, u2: number, v2: number) {
let ctx = this.ctx; const ctx = this.ctx;
const width = img.width - 1; const width = img.width - 1;
const height = img.height - 1; const height = img.height - 1;
@ -206,7 +206,7 @@ export class SkeletonRenderer {
v2 -= v0; v2 -= v0;
let det = u1 * v2 - u2 * v1; let det = u1 * v2 - u2 * v1;
if (det == 0) return; if (det === 0) return;
det = 1 / det; det = 1 / det;
// linear transformation // linear transformation
@ -227,12 +227,12 @@ export class SkeletonRenderer {
} }
private computeRegionVertices (slot: Slot, region: RegionAttachment, pma: boolean) { private computeRegionVertices (slot: Slot, region: RegionAttachment, pma: boolean) {
let skeletonColor = slot.skeleton.color; const skeletonColor = slot.skeleton.color;
let slotColor = slot.applied.color; const slotColor = slot.applied.color;
let regionColor = region.color; const regionColor = region.color;
let alpha = skeletonColor.a * slotColor.a * regionColor.a; const alpha = skeletonColor.a * slotColor.a * regionColor.a;
let multiplier = pma ? alpha : 1; const multiplier = pma ? alpha : 1;
let color = this.tempColor; const color = this.tempColor;
color.set(skeletonColor.r * slotColor.r * regionColor.r * multiplier, color.set(skeletonColor.r * slotColor.r * regionColor.r * multiplier,
skeletonColor.g * slotColor.g * regionColor.g * multiplier, skeletonColor.g * slotColor.g * regionColor.g * multiplier,
skeletonColor.b * slotColor.b * regionColor.b * multiplier, skeletonColor.b * slotColor.b * regionColor.b * multiplier,
@ -240,8 +240,8 @@ export class SkeletonRenderer {
region.computeWorldVertices(slot, this.vertices, 0, SkeletonRenderer.VERTEX_SIZE); region.computeWorldVertices(slot, this.vertices, 0, SkeletonRenderer.VERTEX_SIZE);
let vertices = this.vertices; const vertices = this.vertices;
let uvs = region.uvs; const uvs = region.uvs;
vertices[RegionAttachment.C1R] = color.r; vertices[RegionAttachment.C1R] = color.r;
vertices[RegionAttachment.C1G] = color.g; vertices[RegionAttachment.C1G] = color.g;
@ -275,24 +275,24 @@ export class SkeletonRenderer {
} }
private computeMeshVertices (slot: Slot, mesh: MeshAttachment, pma: boolean) { private computeMeshVertices (slot: Slot, mesh: MeshAttachment, pma: boolean) {
let skeleton = slot.skeleton; const skeleton = slot.skeleton;
let skeletonColor = skeleton.color; const skeletonColor = skeleton.color;
let slotColor = slot.applied.color; const slotColor = slot.applied.color;
let regionColor = mesh.color; const regionColor = mesh.color;
let alpha = skeletonColor.a * slotColor.a * regionColor.a; const alpha = skeletonColor.a * slotColor.a * regionColor.a;
let multiplier = pma ? alpha : 1; const multiplier = pma ? alpha : 1;
let color = this.tempColor; const color = this.tempColor;
color.set(skeletonColor.r * slotColor.r * regionColor.r * multiplier, color.set(skeletonColor.r * slotColor.r * regionColor.r * multiplier,
skeletonColor.g * slotColor.g * regionColor.g * multiplier, skeletonColor.g * slotColor.g * regionColor.g * multiplier,
skeletonColor.b * slotColor.b * regionColor.b * multiplier, skeletonColor.b * slotColor.b * regionColor.b * multiplier,
alpha); alpha);
let vertexCount = mesh.worldVerticesLength / 2; const vertexCount = mesh.worldVerticesLength / 2;
let vertices = this.vertices; let vertices = this.vertices;
if (vertices.length < mesh.worldVerticesLength) this.vertices = vertices = Utils.newFloatArray(mesh.worldVerticesLength); if (vertices.length < mesh.worldVerticesLength) this.vertices = vertices = Utils.newFloatArray(mesh.worldVerticesLength);
mesh.computeWorldVertices(skeleton, slot, 0, mesh.worldVerticesLength, vertices, 0, SkeletonRenderer.VERTEX_SIZE); mesh.computeWorldVertices(skeleton, slot, 0, mesh.worldVerticesLength, vertices, 0, SkeletonRenderer.VERTEX_SIZE);
let uvs = mesh.uvs; const uvs = mesh.uvs;
for (let i = 0, u = 0, v = 2; i < vertexCount; i++) { for (let i = 0, u = 0, v = 2; i < vertexCount; i++) {
vertices[v++] = color.r; vertices[v++] = color.r;
vertices[v++] = color.g; vertices[v++] = color.g;

View File

@ -1,4 +1,4 @@
export * from "@esotericsoftware/spine-core"
export * from "./AssetManager.js"; export * from "./AssetManager.js";
export * from "./CanvasTexture.js"; export * from "./CanvasTexture.js";
export * from "./SkeletonRenderer.js"; export * from "./SkeletonRenderer.js";
export * from "@esotericsoftware/spine-core"

View File

@ -245,6 +245,7 @@ export class SkeletonRenderer {
positions, positions,
uvs, uvs,
colors, colors,
// biome-ignore lint/suspicious/noExplicitAny: canvaskit wants indices as an array of number
indices as any as number[], indices as any as number[],
false false
); );

View File

@ -27,29 +27,29 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { SPINE_GAME_OBJECT_TYPE } from "./keys.js";
import { SpinePlugin } from "./SpinePlugin.js";
import {
ComputedSizeMixin,
DepthMixin,
FlipMixin,
ScrollFactorMixin,
TransformMixin,
VisibleMixin,
AlphaMixin,
OriginMixin,
} from "./mixins.js";
import { import {
AnimationState, AnimationState,
AnimationStateData, AnimationStateData,
Bone, type Bone,
MathUtils, MathUtils,
Physics, Physics,
Skeleton, Skeleton,
SkeletonClipping, SkeletonClipping,
Skin, Skin,
Vector2, type Vector2,
} from "@esotericsoftware/spine-core"; } from "@esotericsoftware/spine-core";
import { SPINE_GAME_OBJECT_TYPE } from "./keys.js";
import {
AlphaMixin,
ComputedSizeMixin,
DepthMixin,
FlipMixin,
OriginMixin,
ScrollFactorMixin,
TransformMixin,
VisibleMixin,
} from "./mixins.js";
import type { SpinePlugin } from "./SpinePlugin.js";
class BaseSpineGameObject extends Phaser.GameObjects.GameObject { class BaseSpineGameObject extends Phaser.GameObjects.GameObject {
constructor (scene: Phaser.Scene, type: string) { constructor (scene: Phaser.Scene, type: string) {
@ -99,7 +99,7 @@ export class SetupPoseBoundsProvider implements SpineGameObjectBoundsProvider {
skeleton.setupPose(); skeleton.setupPose();
skeleton.updateWorldTransform(Physics.update); skeleton.updateWorldTransform(Physics.update);
const bounds = skeleton.getBoundsRect(this.clipping ? new SkeletonClipping() : undefined); const bounds = skeleton.getBoundsRect(this.clipping ? new SkeletonClipping() : undefined);
return bounds.width == Number.NEGATIVE_INFINITY return bounds.width === Number.NEGATIVE_INFINITY
? { x: 0, y: 0, width: 0, height: 0 } ? { x: 0, y: 0, width: 0, height: 0 }
: bounds; : bounds;
} }
@ -137,7 +137,7 @@ export class SkinsAndAnimationBoundsProvider
const clipper = this.clipping ? new SkeletonClipping() : undefined; const clipper = this.clipping ? new SkeletonClipping() : undefined;
const data = skeleton.data; const data = skeleton.data;
if (this.skins.length > 0) { if (this.skins.length > 0) {
let customSkin = new Skin("custom-skin"); const customSkin = new Skin("custom-skin");
for (const skinName of this.skins) { for (const skinName of this.skins) {
const skin = data.findSkin(skinName); const skin = data.findSkin(skinName);
if (skin == null) continue; if (skin == null) continue;
@ -147,12 +147,11 @@ export class SkinsAndAnimationBoundsProvider
} }
skeleton.setupPose(); skeleton.setupPose();
const animation = const animation = this.animation != null ? data.findAnimation(this.animation) : null;
this.animation != null ? data.findAnimation(this.animation!) : null;
if (animation == null) { if (animation == null) {
skeleton.updateWorldTransform(Physics.update); skeleton.updateWorldTransform(Physics.update);
const bounds = skeleton.getBoundsRect(clipper); const bounds = skeleton.getBoundsRect(clipper);
return bounds.width == Number.NEGATIVE_INFINITY return bounds.width === Number.NEGATIVE_INFINITY
? { x: 0, y: 0, width: 0, height: 0 } ? { x: 0, y: 0, width: 0, height: 0 }
: bounds; : bounds;
} else { } else {
@ -182,7 +181,7 @@ export class SkinsAndAnimationBoundsProvider
width: maxX - minX, width: maxX - minX,
height: maxY - minY, height: maxY - minY,
}; };
return bounds.width == Number.NEGATIVE_INFINITY return bounds.width === Number.NEGATIVE_INFINITY
? { x: 0, y: 0, width: 0, height: 0 } ? { x: 0, y: 0, width: 0, height: 0 }
: bounds; : bounds;
} }
@ -240,6 +239,7 @@ export class SpineGameObject extends DepthMixin(
atlasKey: string, atlasKey: string,
public boundsProvider: SpineGameObjectBoundsProvider = new SetupPoseBoundsProvider() public boundsProvider: SpineGameObjectBoundsProvider = new SetupPoseBoundsProvider()
) { ) {
// biome-ignore lint/suspicious/noExplicitAny: necessary
super(scene, (window as any).SPINE_GAME_OBJECT_TYPE ? (window as any).SPINE_GAME_OBJECT_TYPE : SPINE_GAME_OBJECT_TYPE); super(scene, (window as any).SPINE_GAME_OBJECT_TYPE ? (window as any).SPINE_GAME_OBJECT_TYPE : SPINE_GAME_OBJECT_TYPE);
this.setPosition(x, y); this.setPosition(x, y);
@ -253,7 +253,7 @@ export class SpineGameObject extends DepthMixin(
updateSize () { updateSize () {
if (!this.skeleton) return; if (!this.skeleton) return;
let bounds = this.boundsProvider.calculateBounds(this); const bounds = this.boundsProvider.calculateBounds(this);
this.width = bounds.width; this.width = bounds.width;
this.height = bounds.height; this.height = bounds.height;
this.setDisplayOrigin(-bounds.x, -bounds.y); this.setDisplayOrigin(-bounds.x, -bounds.y);
@ -263,15 +263,15 @@ export class SpineGameObject extends DepthMixin(
/** Converts a point from the skeleton coordinate system to the Phaser world coordinate system. */ /** Converts a point from the skeleton coordinate system to the Phaser world coordinate system. */
skeletonToPhaserWorldCoordinates (point: { x: number; y: number }) { skeletonToPhaserWorldCoordinates (point: { x: number; y: number }) {
let transform = this.getWorldTransformMatrix(); const transform = this.getWorldTransformMatrix();
let a = transform.a, const a = transform.a,
b = transform.b, b = transform.b,
c = transform.c, c = transform.c,
d = transform.d, d = transform.d,
tx = transform.tx, tx = transform.tx,
ty = transform.ty; ty = transform.ty;
let x = point.x; const x = point.x;
let y = point.y; const y = point.y;
point.x = x * a + y * c + tx; point.x = x * a + y * c + tx;
point.y = x * b + y * d + ty; point.y = x * b + y * d + ty;
} }
@ -280,14 +280,14 @@ export class SpineGameObject extends DepthMixin(
phaserWorldCoordinatesToSkeleton (point: { x: number; y: number }) { phaserWorldCoordinatesToSkeleton (point: { x: number; y: number }) {
let transform = this.getWorldTransformMatrix(); let transform = this.getWorldTransformMatrix();
transform = transform.invert(); transform = transform.invert();
let a = transform.a, const a = transform.a,
b = transform.b, b = transform.b,
c = transform.c, c = transform.c,
d = transform.d, d = transform.d,
tx = transform.tx, tx = transform.tx,
ty = transform.ty; ty = transform.ty;
let x = point.x; const x = point.x;
let y = point.y; const y = point.y;
point.x = x * a + y * c + tx; point.x = x * a + y * c + tx;
point.y = x * b + y * d + ty; point.y = x * b + y * d + ty;
} }
@ -330,7 +330,7 @@ export class SpineGameObject extends DepthMixin(
if (!this.visible) result = false; if (!this.visible) result = false;
if (!result && this.parentContainer && this.plugin.webGLRenderer) { if (!result && this.parentContainer && this.plugin.webGLRenderer) {
var sceneRenderer = this.plugin.webGLRenderer; const sceneRenderer = this.plugin.webGLRenderer;
if (this.plugin.gl && this.plugin.phaserRenderer instanceof Phaser.Renderer.WebGL.WebGLRenderer && sceneRenderer.batcher.isDrawing) { if (this.plugin.gl && this.plugin.phaserRenderer instanceof Phaser.Renderer.WebGL.WebGLRenderer && sceneRenderer.batcher.isDrawing) {
sceneRenderer.end(); sceneRenderer.end();
@ -350,27 +350,27 @@ export class SpineGameObject extends DepthMixin(
if (!this.skeleton || !this.animationState || !this.plugin.webGLRenderer) if (!this.skeleton || !this.animationState || !this.plugin.webGLRenderer)
return; return;
let sceneRenderer = this.plugin.webGLRenderer; const sceneRenderer = this.plugin.webGLRenderer;
if (renderer.newType) { if (renderer.newType) {
renderer.pipelines.clear(); renderer.pipelines.clear();
sceneRenderer.begin(); sceneRenderer.begin();
} }
camera.addToRenderList(src); camera.addToRenderList(src);
let transform = Phaser.GameObjects.GetCalcMatrix( const transform = Phaser.GameObjects.GetCalcMatrix(
src, src,
camera, camera,
parentMatrix parentMatrix
).calc; ).calc;
let a = transform.a, const a = transform.a,
b = transform.b, b = transform.b,
c = transform.c, c = transform.c,
d = transform.d, d = transform.d,
tx = transform.tx, tx = transform.tx,
ty = transform.ty; ty = transform.ty;
let offsetX = src.offsetX - src.displayOriginX; const offsetX = src.offsetX - src.displayOriginX;
let offsetY = src.offsetY - src.displayOriginY; const offsetY = src.offsetY - src.displayOriginY;
sceneRenderer.drawSkeleton( sceneRenderer.drawSkeleton(
src.skeleton, src.skeleton,
@ -379,8 +379,8 @@ export class SpineGameObject extends DepthMixin(
-1, -1,
(vertices, numVertices, stride) => { (vertices, numVertices, stride) => {
for (let i = 0; i < numVertices; i += stride) { for (let i = 0; i < numVertices; i += stride) {
let vx = vertices[i] + offsetX; const vx = vertices[i] + offsetX;
let vy = vertices[i + 1] + offsetY; const vy = vertices[i + 1] + offsetY;
vertices[i] = vx * a + vy * c + tx; vertices[i] = vx * a + vy * c + tx;
vertices[i + 1] = vx * b + vy * d + ty; vertices[i + 1] = vx * b + vy * d + ty;
} }
@ -402,22 +402,23 @@ export class SpineGameObject extends DepthMixin(
if (!this.skeleton || !this.animationState || !this.plugin.canvasRenderer) if (!this.skeleton || !this.animationState || !this.plugin.canvasRenderer)
return; return;
let context = renderer.currentContext; const context = renderer.currentContext;
let skeletonRenderer = this.plugin.canvasRenderer; const skeletonRenderer = this.plugin.canvasRenderer;
// biome-ignore lint/suspicious/noExplicitAny: necessary for phaser
(skeletonRenderer as any).ctx = context; (skeletonRenderer as any).ctx = context;
camera.addToRenderList(src); camera.addToRenderList(src);
let transform = Phaser.GameObjects.GetCalcMatrix( const transform = Phaser.GameObjects.GetCalcMatrix(
src, src,
camera, camera,
parentMatrix parentMatrix
).calc; ).calc;
let skeleton = this.skeleton; const skeleton = this.skeleton;
skeleton.x = transform.tx; skeleton.x = transform.tx;
skeleton.y = transform.ty; skeleton.y = transform.ty;
skeleton.scaleX = transform.scaleX; skeleton.scaleX = transform.scaleX;
skeleton.scaleY = transform.scaleY; skeleton.scaleY = transform.scaleY;
let root = skeleton.getRootBone()!; const root = skeleton.getRootBone() as Bone;
root.applied.rotation = -MathUtils.radiansToDegrees * transform.rotationNormalized; root.applied.rotation = -MathUtils.radiansToDegrees * transform.rotationNormalized;
this.skeleton.updateWorldTransform(Physics.update); this.skeleton.updateWorldTransform(Physics.update);

View File

@ -88,7 +88,6 @@ export class SpinePlugin extends Phaser.Plugins.ScenePlugin {
constructor (scene: Phaser.Scene, pluginManager: Phaser.Plugins.PluginManager, pluginKey: string) { constructor (scene: Phaser.Scene, pluginManager: Phaser.Plugins.PluginManager, pluginKey: string) {
super(scene, pluginManager, pluginKey); super(scene, pluginManager, pluginKey);
console.log(pluginKey);
this.game = pluginManager.game; this.game = pluginManager.game;
this.isWebGL = this.game.config.renderType === 2; this.isWebGL = this.game.config.renderType === 2;
this.gl = this.isWebGL ? (this.game.renderer as Phaser.Renderer.WebGL.WebGLRenderer).gl : null; this.gl = this.isWebGL ? (this.game.renderer as Phaser.Renderer.WebGL.WebGLRenderer).gl : null;

View File

@ -27,17 +27,21 @@
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
export * from "./require-shim.js"
export * from "./SpinePlugin.js"
export * from "./SpineGameObject.js"
export * from "./mixins.js"
export * from "@esotericsoftware/spine-core"; export * from "@esotericsoftware/spine-core";
export * from "@esotericsoftware/spine-webgl"; export * from "@esotericsoftware/spine-webgl";
import { SpineGameObjectConfig, SpinePlugin } from "./SpinePlugin.js"; export * from "./mixins.js"
export * from "./require-shim.js"
export * from "./SpineGameObject.js"
export * from "./SpinePlugin.js"
import { type SpineGameObjectConfig, SpinePlugin } from "./SpinePlugin.js";
// biome-ignore lint/suspicious/noExplicitAny: need to add spine to window
(window as any).spine = { SpinePlugin: SpinePlugin }; (window as any).spine = { SpinePlugin: SpinePlugin };
// biome-ignore lint/suspicious/noExplicitAny: need to add spine to window
(window as any)["spine.SpinePlugin"] = SpinePlugin; (window as any)["spine.SpinePlugin"] = SpinePlugin;
import { SpineGameObject, SpineGameObjectBoundsProvider } from "./SpineGameObject.js"; import type { SpineGameObject, SpineGameObjectBoundsProvider } from "./SpineGameObject.js";
declare global { declare global {
namespace Phaser.Loader { namespace Phaser.Loader {

View File

@ -24,7 +24,9 @@ SOFTWARE.
// Adapted from https://github.com/agogpixel/phaser3-ts-utils/tree/main // Adapted from https://github.com/agogpixel/phaser3-ts-utils/tree/main
let components = (Phaser.GameObjects.Components as any); // biome-ignore-all lint: ignore biome for this file
const components = (Phaser.GameObjects.Components as any);
export const ComputedSize = components.ComputedSize; export const ComputedSize = components.ComputedSize;
export const Depth = components.Depth; export const Depth = components.Depth;
export const Flip = components.Flip; export const Flip = components.Flip;

View File

@ -27,8 +27,10 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
// biome-ignore-all lint: ignore biome for this file
if (typeof window !== 'undefined' && window.Phaser) { if (typeof window !== 'undefined' && window.Phaser) {
let prevRequire = window.require; const prevRequire = window.require;
(window as any).require = (x: string) => { (window as any).require = (x: string) => {
if (prevRequire) return prevRequire(x); if (prevRequire) return prevRequire(x);
else if (x === "Phaser") return window.Phaser; else if (x === "Phaser") return window.Phaser;

View File

@ -27,29 +27,29 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { SPINE_GAME_OBJECT_TYPE } from "./keys.js";
import { SpinePlugin } from "./SpinePlugin.js";
import {
ComputedSizeMixin,
DepthMixin,
FlipMixin,
ScrollFactorMixin,
TransformMixin,
VisibleMixin,
AlphaMixin,
OriginMixin,
} from "./mixins.js";
import { import {
AnimationState, AnimationState,
AnimationStateData, AnimationStateData,
Bone, type Bone,
MathUtils, MathUtils,
Physics, Physics,
Skeleton, Skeleton,
SkeletonClipping, SkeletonClipping,
Skin, Skin,
Vector2, type Vector2,
} from "@esotericsoftware/spine-core"; } from "@esotericsoftware/spine-core";
import { SPINE_GAME_OBJECT_TYPE } from "./keys.js";
import {
AlphaMixin,
ComputedSizeMixin,
DepthMixin,
FlipMixin,
OriginMixin,
ScrollFactorMixin,
TransformMixin,
VisibleMixin,
} from "./mixins.js";
import type { SpinePlugin } from "./SpinePlugin.js";
class BaseSpineGameObject extends Phaser.GameObjects.GameObject { class BaseSpineGameObject extends Phaser.GameObjects.GameObject {
constructor (scene: Phaser.Scene, type: string) { constructor (scene: Phaser.Scene, type: string) {
@ -99,7 +99,7 @@ export class SetupPoseBoundsProvider implements SpineGameObjectBoundsProvider {
skeleton.setupPose(); skeleton.setupPose();
skeleton.updateWorldTransform(Physics.update); skeleton.updateWorldTransform(Physics.update);
const bounds = skeleton.getBoundsRect(this.clipping ? new SkeletonClipping() : undefined); const bounds = skeleton.getBoundsRect(this.clipping ? new SkeletonClipping() : undefined);
return bounds.width == Number.NEGATIVE_INFINITY return bounds.width === Number.NEGATIVE_INFINITY
? { x: 0, y: 0, width: 0, height: 0 } ? { x: 0, y: 0, width: 0, height: 0 }
: bounds; : bounds;
} }
@ -137,7 +137,7 @@ export class SkinsAndAnimationBoundsProvider
const clipper = this.clipping ? new SkeletonClipping() : undefined; const clipper = this.clipping ? new SkeletonClipping() : undefined;
const data = skeleton.data; const data = skeleton.data;
if (this.skins.length > 0) { if (this.skins.length > 0) {
let customSkin = new Skin("custom-skin"); const customSkin = new Skin("custom-skin");
for (const skinName of this.skins) { for (const skinName of this.skins) {
const skin = data.findSkin(skinName); const skin = data.findSkin(skinName);
if (skin == null) continue; if (skin == null) continue;
@ -147,12 +147,11 @@ export class SkinsAndAnimationBoundsProvider
} }
skeleton.setupPose(); skeleton.setupPose();
const animation = const animation = this.animation != null ? data.findAnimation(this.animation) : null;
this.animation != null ? data.findAnimation(this.animation!) : null;
if (animation == null) { if (animation == null) {
skeleton.updateWorldTransform(Physics.update); skeleton.updateWorldTransform(Physics.update);
const bounds = skeleton.getBoundsRect(clipper); const bounds = skeleton.getBoundsRect(clipper);
return bounds.width == Number.NEGATIVE_INFINITY return bounds.width === Number.NEGATIVE_INFINITY
? { x: 0, y: 0, width: 0, height: 0 } ? { x: 0, y: 0, width: 0, height: 0 }
: bounds; : bounds;
} else { } else {
@ -182,7 +181,7 @@ export class SkinsAndAnimationBoundsProvider
width: maxX - minX, width: maxX - minX,
height: maxY - minY, height: maxY - minY,
}; };
return bounds.width == Number.NEGATIVE_INFINITY return bounds.width === Number.NEGATIVE_INFINITY
? { x: 0, y: 0, width: 0, height: 0 } ? { x: 0, y: 0, width: 0, height: 0 }
: bounds; : bounds;
} }
@ -240,6 +239,7 @@ export class SpineGameObject extends DepthMixin(
atlasKey: string, atlasKey: string,
public boundsProvider: SpineGameObjectBoundsProvider = new SetupPoseBoundsProvider() public boundsProvider: SpineGameObjectBoundsProvider = new SetupPoseBoundsProvider()
) { ) {
// biome-ignore lint/suspicious/noExplicitAny: necessary for phaser
super(scene, (window as any).SPINE_GAME_OBJECT_TYPE ? (window as any).SPINE_GAME_OBJECT_TYPE : SPINE_GAME_OBJECT_TYPE); super(scene, (window as any).SPINE_GAME_OBJECT_TYPE ? (window as any).SPINE_GAME_OBJECT_TYPE : SPINE_GAME_OBJECT_TYPE);
this.setPosition(x, y); this.setPosition(x, y);
@ -253,7 +253,7 @@ export class SpineGameObject extends DepthMixin(
updateSize () { updateSize () {
if (!this.skeleton) return; if (!this.skeleton) return;
let bounds = this.boundsProvider.calculateBounds(this); const bounds = this.boundsProvider.calculateBounds(this);
this.width = bounds.width; this.width = bounds.width;
this.height = bounds.height; this.height = bounds.height;
this.setDisplayOrigin(-bounds.x, -bounds.y); this.setDisplayOrigin(-bounds.x, -bounds.y);
@ -263,15 +263,15 @@ export class SpineGameObject extends DepthMixin(
/** Converts a point from the skeleton coordinate system to the Phaser world coordinate system. */ /** Converts a point from the skeleton coordinate system to the Phaser world coordinate system. */
skeletonToPhaserWorldCoordinates (point: { x: number; y: number }) { skeletonToPhaserWorldCoordinates (point: { x: number; y: number }) {
let transform = this.getWorldTransformMatrix(); const transform = this.getWorldTransformMatrix();
let a = transform.a, const a = transform.a,
b = transform.b, b = transform.b,
c = transform.c, c = transform.c,
d = transform.d, d = transform.d,
tx = transform.tx, tx = transform.tx,
ty = transform.ty; ty = transform.ty;
let x = point.x; const x = point.x;
let y = point.y; const y = point.y;
point.x = x * a + y * c + tx; point.x = x * a + y * c + tx;
point.y = x * b + y * d + ty; point.y = x * b + y * d + ty;
} }
@ -280,14 +280,14 @@ export class SpineGameObject extends DepthMixin(
phaserWorldCoordinatesToSkeleton (point: { x: number; y: number }) { phaserWorldCoordinatesToSkeleton (point: { x: number; y: number }) {
let transform = this.getWorldTransformMatrix(); let transform = this.getWorldTransformMatrix();
transform = transform.invert(); transform = transform.invert();
let a = transform.a, const a = transform.a,
b = transform.b, b = transform.b,
c = transform.c, c = transform.c,
d = transform.d, d = transform.d,
tx = transform.tx, tx = transform.tx,
ty = transform.ty; ty = transform.ty;
let x = point.x; const x = point.x;
let y = point.y; const y = point.y;
point.x = x * a + y * c + tx; point.x = x * a + y * c + tx;
point.y = x * b + y * d + ty; point.y = x * b + y * d + ty;
} }
@ -325,12 +325,12 @@ export class SpineGameObject extends DepthMixin(
} }
willRender (camera: Phaser.Cameras.Scene2D.Camera) { willRender (camera: Phaser.Cameras.Scene2D.Camera) {
var GameObjectRenderMask = 0xf; const GameObjectRenderMask = 0xf;
var result = !this.skeleton || !(GameObjectRenderMask !== this.renderFlags || (this.cameraFilter !== 0 && this.cameraFilter & camera.id)); let result = !this.skeleton || !(GameObjectRenderMask !== this.renderFlags || (this.cameraFilter !== 0 && this.cameraFilter & camera.id));
if (!this.visible) result = false; if (!this.visible) result = false;
if (!result && this.parentContainer && this.plugin.webGLRenderer) { if (!result && this.parentContainer && this.plugin.webGLRenderer) {
var sceneRenderer = this.plugin.webGLRenderer; const sceneRenderer = this.plugin.webGLRenderer;
if (this.plugin.gl && this.plugin.phaserRenderer instanceof Phaser.Renderer.WebGL.WebGLRenderer && sceneRenderer.batcher.isDrawing) { if (this.plugin.gl && this.plugin.phaserRenderer instanceof Phaser.Renderer.WebGL.WebGLRenderer && sceneRenderer.batcher.isDrawing) {
sceneRenderer.end(); sceneRenderer.end();
@ -354,7 +354,7 @@ export class SpineGameObject extends DepthMixin(
if (!camera || !src.skeleton || !src.animationState || !src.plugin.webGLRenderer) if (!camera || !src.skeleton || !src.animationState || !src.plugin.webGLRenderer)
return; return;
let sceneRenderer = src.plugin.webGLRenderer; const sceneRenderer = src.plugin.webGLRenderer;
// Determine object type in context. // Determine object type in context.
const previousGameObject = displayList[displayListIndex - 1]; const previousGameObject = displayList[displayListIndex - 1];
@ -376,20 +376,20 @@ export class SpineGameObject extends DepthMixin(
} }
camera.addToRenderList(src); camera.addToRenderList(src);
let transform = Phaser.GameObjects.GetCalcMatrix( const transform = Phaser.GameObjects.GetCalcMatrix(
src, src,
camera, camera,
parentMatrix parentMatrix
).calc; ).calc;
let a = transform.a, const a = transform.a,
b = transform.b, b = transform.b,
c = transform.c, c = transform.c,
d = transform.d, d = transform.d,
tx = transform.tx, tx = transform.tx,
ty = transform.ty; ty = transform.ty;
let offsetX = src.offsetX - src.displayOriginX; const offsetX = src.offsetX - src.displayOriginX;
let offsetY = src.offsetY - src.displayOriginY; const offsetY = src.offsetY - src.displayOriginY;
sceneRenderer.drawSkeleton( sceneRenderer.drawSkeleton(
src.skeleton, src.skeleton,
@ -398,8 +398,8 @@ export class SpineGameObject extends DepthMixin(
-1, -1,
(vertices, numVertices, stride) => { (vertices, numVertices, stride) => {
for (let i = 0; i < numVertices; i += stride) { for (let i = 0; i < numVertices; i += stride) {
let vx = vertices[i] + offsetX; const vx = vertices[i] + offsetX;
let vy = vertices[i + 1] + offsetY; const vy = vertices[i + 1] + offsetY;
vertices[i] = vx * a + vy * c + tx; vertices[i] = vx * a + vy * c + tx;
vertices[i + 1] = vx * b + vy * d + ty; vertices[i + 1] = vx * b + vy * d + ty;
} }
@ -424,22 +424,23 @@ export class SpineGameObject extends DepthMixin(
if (!this.skeleton || !this.animationState || !this.plugin.canvasRenderer) if (!this.skeleton || !this.animationState || !this.plugin.canvasRenderer)
return; return;
let context = renderer.currentContext; const context = renderer.currentContext;
let skeletonRenderer = this.plugin.canvasRenderer; const skeletonRenderer = this.plugin.canvasRenderer;
// biome-ignore lint/suspicious/noExplicitAny: necessary for phaser
(skeletonRenderer as any).ctx = context; (skeletonRenderer as any).ctx = context;
camera.addToRenderList(src); camera.addToRenderList(src);
let transform = Phaser.GameObjects.GetCalcMatrix( const transform = Phaser.GameObjects.GetCalcMatrix(
src, src,
camera, camera,
parentMatrix parentMatrix
).calc; ).calc;
let skeleton = this.skeleton; const skeleton = this.skeleton;
skeleton.x = transform.tx; skeleton.x = transform.tx;
skeleton.y = transform.ty; skeleton.y = transform.ty;
skeleton.scaleX = transform.scaleX; skeleton.scaleX = transform.scaleX;
skeleton.scaleY = transform.scaleY; skeleton.scaleY = transform.scaleY;
let root = skeleton.getRootBone()!; const root = skeleton.getRootBone() as Bone;
root.applied.rotation = -MathUtils.radiansToDegrees * transform.rotationNormalized; root.applied.rotation = -MathUtils.radiansToDegrees * transform.rotationNormalized;
this.skeleton.updateWorldTransform(Physics.update); this.skeleton.updateWorldTransform(Physics.update);

View File

@ -1,14 +1,47 @@
export * from "./require-shim.js" /******************************************************************************
export * from "./SpinePlugin.js" * Spine Runtimes License Agreement
export * from "./SpineGameObject.js" * Last updated July 28, 2023. Replaces all prior versions.
export * from "./mixins.js" *
* Copyright (c) 2013-2023, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
* otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
export * from "@esotericsoftware/spine-core"; export * from "@esotericsoftware/spine-core";
export * from "@esotericsoftware/spine-webgl"; export * from "@esotericsoftware/spine-webgl";
import { SpineGameObjectConfig, SpinePlugin } from "./SpinePlugin.js"; export * from "./mixins.js"
export * from "./require-shim.js"
export * from "./SpineGameObject.js"
export * from "./SpinePlugin.js"
import { type SpineGameObjectConfig, SpinePlugin } from "./SpinePlugin.js";
// biome-ignore lint/suspicious/noExplicitAny: need to add spine to window
(window as any).spine = { SpinePlugin: SpinePlugin }; (window as any).spine = { SpinePlugin: SpinePlugin };
// biome-ignore lint/suspicious/noExplicitAny: need to add spine to window
(window as any)["spine.SpinePlugin"] = SpinePlugin; (window as any)["spine.SpinePlugin"] = SpinePlugin;
import { SpineGameObject, SpineGameObjectBoundsProvider } from "./SpineGameObject.js"; import type { SpineGameObject, SpineGameObjectBoundsProvider } from "./SpineGameObject.js";
declare global { declare global {
namespace Phaser.Loader { namespace Phaser.Loader {

View File

@ -24,7 +24,9 @@ SOFTWARE.
// Adapted from https://github.com/agogpixel/phaser3-ts-utils/tree/main // Adapted from https://github.com/agogpixel/phaser3-ts-utils/tree/main
let components = (Phaser.GameObjects.Components as any); // biome-ignore-all lint: ignore biome for this file
const components = (Phaser.GameObjects.Components as any);
export const ComputedSize = components.ComputedSize; export const ComputedSize = components.ComputedSize;
export const Depth = components.Depth; export const Depth = components.Depth;
export const Flip = components.Flip; export const Flip = components.Flip;

View File

@ -27,8 +27,10 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
// biome-ignore-all lint: ignore biome for this file
if (typeof window !== 'undefined' && window.Phaser) { if (typeof window !== 'undefined' && window.Phaser) {
let prevRequire = window.require; const prevRequire = window.require;
(window as any).require = (x: string) => { (window as any).require = (x: string) => {
if (prevRequire) return prevRequire(x); if (prevRequire) return prevRequire(x);
else if (x === "Phaser") return window.Phaser; else if (x === "Phaser") return window.Phaser;

View File

@ -27,10 +27,10 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { SpineTexture } from "./SpineTexture.js";
import type { BlendMode, NumberArrayLike } from "@esotericsoftware/spine-core"; import type { BlendMode, NumberArrayLike } from "@esotericsoftware/spine-core";
import { DarkTintMesh } from "./darkTintMesh/DarkTintMesh.js"; import { DarkTintMesh } from "./darkTintMesh/DarkTintMesh.js";
import type { ISlotMesh } from "./Spine.js"; import type { ISlotMesh } from "./Spine.js";
import { SpineTexture } from "./SpineTexture.js";
export class DarkSlotMesh extends DarkTintMesh implements ISlotMesh { export class DarkSlotMesh extends DarkTintMesh implements ISlotMesh {
public name: string = ""; public name: string = "";
@ -66,8 +66,8 @@ export class DarkSlotMesh extends DarkTintMesh implements ISlotMesh {
let vertIndex = 0; let vertIndex = 0;
let textureCoordData = textureCoord.data; const textureCoordData = textureCoord.data;
let vertexCoordData = vertexCoord.data; const vertexCoordData = vertexCoord.data;
for (let i = 0; i < finalVerticesLength; i += darkTint ? 12 : 8) { for (let i = 0; i < finalVerticesLength; i += darkTint ? 12 : 8) {
let auxi = i; let auxi = i;

View File

@ -27,11 +27,11 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { SpineTexture } from "./SpineTexture.js";
import type { BlendMode, NumberArrayLike } from "@esotericsoftware/spine-core"; import type { BlendMode, NumberArrayLike } from "@esotericsoftware/spine-core";
import type { ISlotMesh } from "./Spine.js";
import { Mesh, MeshGeometry, MeshMaterial } from "@pixi/mesh";
import { Texture } from "@pixi/core"; import { Texture } from "@pixi/core";
import { Mesh, MeshGeometry, MeshMaterial } from "@pixi/mesh";
import type { ISlotMesh } from "./Spine.js";
import { SpineTexture } from "./SpineTexture.js";
export class SlotMesh extends Mesh implements ISlotMesh { export class SlotMesh extends Mesh implements ISlotMesh {
public name: string = ""; public name: string = "";
@ -74,8 +74,8 @@ export class SlotMesh extends Mesh implements ISlotMesh {
let vertIndex = 0; let vertIndex = 0;
let textureCoordData = textureCoord.data; const textureCoordData = textureCoord.data;
let vertexCoordData = vertexCoord.data; const vertexCoordData = vertexCoord.data;
for (let i = 0; i < finalVerticesLength; i += darkTint ? 12 : 8) { for (let i = 0; i < finalVerticesLength; i += darkTint ? 12 : 8) {
let auxi = i; let auxi = i;

View File

@ -40,7 +40,7 @@ import {
Skeleton, Skeleton,
SkeletonBinary, SkeletonBinary,
SkeletonClipping, SkeletonClipping,
type SkeletonData, SkeletonData,
SkeletonJson, SkeletonJson,
Skin, Skin,
Utils, Utils,
@ -329,10 +329,12 @@ export class Spine extends Container {
private _boundsSpineID = -1; private _boundsSpineID = -1;
private _boundsSpineDirty = true; private _boundsSpineDirty = true;
constructor (options: SpineOptions | SpineFromOptions) { constructor (options: SkeletonData | SpineOptions | SpineFromOptions) {
super(); super();
if ("skeleton" in options) if (options instanceof SkeletonData)
options = { skeletonData: options };
else if ("skeleton" in options)
options = new.target.createOptions(options); options = new.target.createOptions(options);
const { autoUpdate = true, boundsProvider, darkTint, skeletonData } = options; const { autoUpdate = true, boundsProvider, darkTint, skeletonData } = options;

View File

@ -27,12 +27,12 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import type { AnimationStateListener } from "@esotericsoftware/spine-core";
import { ClippingAttachment, MeshAttachment, PathAttachment, RegionAttachment, SkeletonBounds } from "@esotericsoftware/spine-core";
import { Container } from "@pixi/display"; import { Container } from "@pixi/display";
import { Graphics } from "@pixi/graphics"; import { Graphics } from "@pixi/graphics";
import { Text } from "@pixi/text"; import { Text } from "@pixi/text";
import type { Spine } from "./Spine.js"; import type { Spine } from "./Spine.js";
import type { AnimationStateListener } from "@esotericsoftware/spine-core";
import { ClippingAttachment, MeshAttachment, PathAttachment, RegionAttachment, SkeletonBounds } from "@esotericsoftware/spine-core";
/** /**
* Make a class that extends from this interface to create your own debug renderer. * Make a class that extends from this interface to create your own debug renderer.
@ -159,9 +159,10 @@ export class SpineDebugRenderer implements ISpineDebugRenderer {
debugDisplayObjects.parentDebugContainer.zIndex = 9999999; debugDisplayObjects.parentDebugContainer.zIndex = 9999999;
// Disable screen reader and mouse input on debug objects. // Disable screen reader and mouse input on debug objects.
// biome-ignore lint/suspicious/noExplicitAny: this prop is available in later versions
(debugDisplayObjects.parentDebugContainer as any).accessibleChildren = false; (debugDisplayObjects.parentDebugContainer as any).accessibleChildren = false;
(debugDisplayObjects.parentDebugContainer as any).eventMode = "none"; (debugDisplayObjects.parentDebugContainer).eventMode = "none";
(debugDisplayObjects.parentDebugContainer as any).interactiveChildren = false; (debugDisplayObjects.parentDebugContainer).interactiveChildren = false;
spine.addChild(debugDisplayObjects.parentDebugContainer); spine.addChild(debugDisplayObjects.parentDebugContainer);

View File

@ -28,15 +28,15 @@
*****************************************************************************/ *****************************************************************************/
import { BlendMode, Texture, TextureFilter, TextureWrap } from "@esotericsoftware/spine-core"; import { BlendMode, Texture, TextureFilter, TextureWrap } from "@esotericsoftware/spine-core";
import type { BaseTexture as PixiBaseTexture, BaseImageResource } from "@pixi/core"; import type { BaseImageResource, BaseTexture as PixiBaseTexture } from "@pixi/core";
import { Texture as PixiTexture, SCALE_MODES, MIPMAP_MODES, WRAP_MODES, BLEND_MODES } from "@pixi/core"; import { BLEND_MODES, MIPMAP_MODES, Texture as PixiTexture, SCALE_MODES, WRAP_MODES } from "@pixi/core";
export class SpineTexture extends Texture { export class SpineTexture extends Texture {
private static textureMap: Map<PixiBaseTexture, SpineTexture> = new Map<PixiBaseTexture, SpineTexture>(); private static textureMap: Map<PixiBaseTexture, SpineTexture> = new Map<PixiBaseTexture, SpineTexture>();
public static from (texture: PixiBaseTexture): SpineTexture { public static from (texture: PixiBaseTexture): SpineTexture {
if (SpineTexture.textureMap.has(texture)) { if (SpineTexture.textureMap.has(texture)) {
return SpineTexture.textureMap.get(texture)!; return SpineTexture.textureMap.get(texture) as SpineTexture;
} }
return new SpineTexture(texture); return new SpineTexture(texture);
} }
@ -45,7 +45,7 @@ export class SpineTexture extends Texture {
private constructor (image: PixiBaseTexture) { private constructor (image: PixiBaseTexture) {
// Todo: maybe add error handling if you feed a video texture to spine? // Todo: maybe add error handling if you feed a video texture to spine?
super((image.resource as BaseImageResource).source as any); super((image.resource as BaseImageResource).source);
this.texture = PixiTexture.from(image); this.texture = PixiTexture.from(image);
} }

View File

@ -28,12 +28,11 @@
*****************************************************************************/ *****************************************************************************/
import { TextureAtlas } from "@esotericsoftware/spine-core"; import { TextureAtlas } from "@esotericsoftware/spine-core";
import { SpineTexture } from "../SpineTexture.js";
import type { AssetExtension, Loader, ResolvedAsset, UnresolvedAsset } from "@pixi/assets"; import type { AssetExtension, Loader, ResolvedAsset, UnresolvedAsset } from "@pixi/assets";
import { Assets, copySearchParams } from "@pixi/assets"; import { Assets, checkExtension, copySearchParams, LoaderParserPriority } from "@pixi/assets";
import { LoaderParserPriority, checkExtension } from "@pixi/assets";
import type { Texture } from "@pixi/core"; import type { Texture } from "@pixi/core";
import { ALPHA_MODES, ExtensionType, settings, utils, BaseTexture, extensions } from "@pixi/core"; import { ALPHA_MODES, BaseTexture, ExtensionType, extensions, settings, utils } from "@pixi/core";
import { SpineTexture } from "../SpineTexture.js";
type RawAtlas = string; type RawAtlas = string;
@ -75,7 +74,7 @@ const spineTextureAtlasLoader: AssetExtension<RawAtlas | TextureAtlas, ISpineAtl
}, },
testParse (asset: unknown, options: ResolvedAsset): Promise<boolean> { testParse (asset: unknown, options: ResolvedAsset): Promise<boolean> {
const isExtensionRight = checkExtension(options.src!, ".atlas"); const isExtensionRight = checkExtension(options.src as string, ".atlas");
const isString = typeof asset === "string"; const isString = typeof asset === "string";
const isExplicitLoadParserSet = options.loadParser === loaderName; const isExplicitLoadParserSet = options.loadParser === loaderName;
@ -111,7 +110,7 @@ const spineTextureAtlasLoader: AssetExtension<RawAtlas | TextureAtlas, ISpineAtl
// setting preferCreateImageBitmap to false for loadTextures loader to allow loading PMA images // setting preferCreateImageBitmap to false for loadTextures loader to allow loading PMA images
let oldPreferCreateImageBitmap = true; let oldPreferCreateImageBitmap = true;
for (const parser of loader.parsers) { for (const parser of loader.parsers) {
if (parser.name == "loadTextures") { if (parser.name === "loadTextures") {
oldPreferCreateImageBitmap = parser.config?.preferCreateImageBitmap; oldPreferCreateImageBitmap = parser.config?.preferCreateImageBitmap;
break; break;
} }
@ -149,6 +148,7 @@ extensions.add(spineTextureAtlasLoader);
export interface ISpineAtlasMetadata { export interface ISpineAtlasMetadata {
// If you are downloading an .atlas file, this metadata will go to the Texture loader // If you are downloading an .atlas file, this metadata will go to the Texture loader
// biome-ignore lint/suspicious/noExplicitAny: user can pass any
imageMetadata?: any; imageMetadata?: any;
// If you already have atlas pages loaded as pixi textures and want to use that to create the atlas, you can pass them here // If you already have atlas pages loaded as pixi textures and want to use that to create the atlas, you can pass them here
images?: BaseTexture | string | Record<string, BaseTexture | string>; images?: BaseTexture | string | Record<string, BaseTexture | string>;

View File

@ -28,18 +28,21 @@
*****************************************************************************/ *****************************************************************************/
import type { AssetExtension, ResolvedAsset } from "@pixi/assets"; import type { AssetExtension, ResolvedAsset } from "@pixi/assets";
import { LoaderParserPriority, checkExtension } from "@pixi/assets"; import { checkExtension, LoaderParserPriority } from "@pixi/assets";
import { ExtensionType, settings, extensions } from "@pixi/core"; import { ExtensionType, extensions, settings } from "@pixi/core";
// biome-ignore lint/suspicious/noExplicitAny: any until we have a schema
type SkeletonJsonAsset = any; type SkeletonJsonAsset = any;
type SkeletonBinaryAsset = Uint8Array; type SkeletonBinaryAsset = Uint8Array;
const loaderName = "spineSkeletonLoader"; const loaderName = "spineSkeletonLoader";
// biome-ignore lint/suspicious/noExplicitAny: can receive any
function isJson (resource: any): resource is SkeletonJsonAsset { function isJson (resource: any): resource is SkeletonJsonAsset {
return resource.hasOwnProperty("bones"); return resource.hasOwnProperty("bones");
} }
// biome-ignore lint/suspicious/noExplicitAny: can receive any
function isBuffer (resource: any): resource is SkeletonBinaryAsset { function isBuffer (resource: any): resource is SkeletonBinaryAsset {
return resource instanceof Uint8Array; return resource instanceof Uint8Array;
} }
@ -67,8 +70,8 @@ const spineLoaderExtension: AssetExtension<SkeletonJsonAsset | SkeletonBinaryAss
return buffer; return buffer;
}, },
testParse (asset: unknown, options: ResolvedAsset): Promise<boolean> { testParse (asset: unknown, options: ResolvedAsset): Promise<boolean> {
const isJsonSpineModel = checkExtension(options.src!, ".json") && isJson(asset); const isJsonSpineModel = checkExtension(options.src as string, ".json") && isJson(asset);
const isBinarySpineModel = checkExtension(options.src!, ".skel") && isBuffer(asset); const isBinarySpineModel = checkExtension(options.src as string, ".skel") && isBuffer(asset);
const isExplicitLoadParserSet = options.loadParser === loaderName; const isExplicitLoadParserSet = options.loadParser === loaderName;
return Promise.resolve(isJsonSpineModel || isBinarySpineModel || isExplicitLoadParserSet); return Promise.resolve(isJsonSpineModel || isBinarySpineModel || isExplicitLoadParserSet);

View File

@ -27,7 +27,7 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { Geometry, Buffer, TYPES } from "@pixi/core"; import { Buffer, Geometry, TYPES } from "@pixi/core";
/** /**
* Geometry used to batch standard PIXI content (e.g. Mesh, Sprite, Graphics objects). * Geometry used to batch standard PIXI content (e.g. Mesh, Sprite, Graphics objects).

View File

@ -27,7 +27,7 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { Geometry, Buffer, TYPES } from "@pixi/core"; import { Buffer, Geometry, TYPES } from "@pixi/core";
/** /**
* Geometry used to batch standard PIXI content (e.g. Mesh, Sprite, Graphics objects). * Geometry used to batch standard PIXI content (e.g. Mesh, Sprite, Graphics objects).

View File

@ -28,7 +28,7 @@
*****************************************************************************/ *****************************************************************************/
import type { ColorSource } from "@pixi/core"; import type { ColorSource } from "@pixi/core";
import { Shader, TextureMatrix, Color, Texture, Matrix, Program } from "@pixi/core"; import { Color, Matrix, Program, Shader, Texture, TextureMatrix } from "@pixi/core";
const vertex = ` const vertex = `
attribute vec2 aVertexPosition; attribute vec2 aVertexPosition;
@ -163,7 +163,7 @@ export class DarkTintMaterial extends Shader {
this._colorDirty = true; this._colorDirty = true;
} }
public get tint (): ColorSource { public get tint (): ColorSource {
return this._tintColor.value!; return this._tintColor.value as ColorSource;
} }
public set darkTint (value: ColorSource) { public set darkTint (value: ColorSource) {
@ -176,7 +176,7 @@ export class DarkTintMaterial extends Shader {
this._colorDirty = true; this._colorDirty = true;
} }
public get darkTint (): ColorSource { public get darkTint (): ColorSource {
return this._darkTintColor.value!; return this._darkTintColor.value as ColorSource;
} }
public get tintValue (): number { public get tintValue (): number {

View File

@ -27,7 +27,7 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import type { Texture, ColorSource, Renderer, BLEND_MODES } from "@pixi/core"; import type { BLEND_MODES, ColorSource, Renderer, Texture } from "@pixi/core";
import { Mesh } from "@pixi/mesh"; import { Mesh } from "@pixi/mesh";
import { DarkTintGeometry } from "./DarkTintGeom.js"; import { DarkTintGeometry } from "./DarkTintGeom.js";
import { DarkTintMaterial } from "./DarkTintMaterial.js"; import { DarkTintMaterial } from "./DarkTintMaterial.js";
@ -60,11 +60,11 @@ export class DarkTintMesh extends Mesh<DarkTintMaterial> {
} }
public set darkTint (value: ColorSource | null) { public set darkTint (value: ColorSource | null) {
(this.shader as unknown as DarkTintMaterial).darkTint = value!; this.shader.darkTint = value as ColorSource;
} }
public get darkTintValue (): number { public get darkTintValue (): number {
return (this.shader as unknown as DarkTintMaterial).darkTintValue; return this.shader.darkTintValue;
} }
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention

View File

@ -27,10 +27,10 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import type { IDarkTintElement } from "./DarkTintMesh.js";
import { DarkTintBatchGeometry } from "./DarkTintBatchGeom.js";
import type { ExtensionMetadata, Renderer, ViewableBuffer } from "@pixi/core"; import type { ExtensionMetadata, Renderer, ViewableBuffer } from "@pixi/core";
import { extensions, BatchRenderer, ExtensionType, BatchShaderGenerator, Color } from "@pixi/core"; import { BatchRenderer, BatchShaderGenerator, Color, ExtensionType, extensions } from "@pixi/core";
import { DarkTintBatchGeometry } from "./DarkTintBatchGeom.js";
import type { IDarkTintElement } from "./DarkTintMesh.js";
const vertex = ` const vertex = `
precision highp float; precision highp float;

View File

@ -1,18 +1,48 @@
export * from './require-shim.js'; /******************************************************************************
export * from './Spine.js'; * Spine Runtimes License Agreement
export * from './SpineDebugRenderer.js'; * Last updated September 24, 2021. Replaces all prior versions.
export * from './SpineTexture.js'; *
export * from './SlotMesh.js'; * Copyright (c) 2013-2021, Esoteric Software LLC
export * from './DarkSlotMesh.js'; *
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software
* or otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
import './require-shim.js';
import './assets/AtlasLoader.js'; // Side effects install the loaders into pixi
import './assets/SkeletonLoader.js'; // Side effects install the loaders into pixi
export * from "@esotericsoftware/spine-core";
export * from './assets/AtlasLoader.js'; export * from './assets/AtlasLoader.js';
export * from './assets/SkeletonLoader.js'; export * from './assets/SkeletonLoader.js';
export * from './DarkSlotMesh.js';
export * from './darkTintMesh/DarkTintBatchGeom.js'; export * from './darkTintMesh/DarkTintBatchGeom.js';
export * from './darkTintMesh/DarkTintGeom.js'; export * from './darkTintMesh/DarkTintGeom.js';
export * from './darkTintMesh/DarkTintMaterial.js'; export * from './darkTintMesh/DarkTintMaterial.js';
export * from './darkTintMesh/DarkTintMesh.js'; export * from './darkTintMesh/DarkTintMesh.js';
export * from './darkTintMesh/DarkTintRenderer.js'; export * from './darkTintMesh/DarkTintRenderer.js';
export * from "@esotericsoftware/spine-core"; export * from './SlotMesh.js';
export * from './Spine.js';
export * from './SpineDebugRenderer.js';
export * from './SpineTexture.js';
import './assets/AtlasLoader.js'; // Side effects install the loaders into pixi
import './assets/SkeletonLoader.js'; // Side effects install the loaders into pixi

View File

@ -27,8 +27,10 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
// biome-ignore-all lint: ignore biome for this file
if (typeof window !== 'undefined' && (window as any).PIXI) { if (typeof window !== 'undefined' && (window as any).PIXI) {
let prevRequire = window.require; const prevRequire = window.require;
(window as any).require = (x: string) => { (window as any).require = (x: string) => {
if (prevRequire) return prevRequire(x); if (prevRequire) return prevRequire(x);
else if (x.startsWith("@pixi/")) return (window as any).PIXI; else if (x.startsWith("@pixi/")) return (window as any).PIXI;

View File

@ -27,9 +27,9 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { AttachmentCacheData, Spine } from './Spine.js';
import type { Batch, Batcher, BLEND_MODES, DefaultBatchableMeshElement, Matrix, Texture, Topology } from 'pixi.js'; import type { Batch, Batcher, BLEND_MODES, DefaultBatchableMeshElement, Matrix, Texture, Topology } from 'pixi.js';
import type { AttachmentCacheData, ClippedData, Spine } from './Spine.js';
export class BatchableSpineSlot implements DefaultBatchableMeshElement { export class BatchableSpineSlot implements DefaultBatchableMeshElement {
indexOffset = 0; indexOffset = 0;
@ -112,13 +112,13 @@ export class BatchableSpineSlot implements DefaultBatchableMeshElement {
this.data = data; this.data = data;
if (data.clipped) { if (data.clipped) {
const clippedData = data.clippedData; const clippedData = data.clippedData as ClippedData;
this.indexSize = clippedData!.indicesCount; this.indexSize = clippedData.indicesCount;
this.attributeSize = clippedData!.vertexCount; this.attributeSize = clippedData.vertexCount;
this.positions = clippedData!.vertices; this.positions = clippedData.vertices;
this.indices = clippedData!.indices; this.indices = clippedData.indices;
this.uvs = clippedData!.uvs; this.uvs = clippedData.uvs;
} }
else { else {
this.indexSize = data.indices.length; this.indexSize = data.indices.length;

View File

@ -43,7 +43,7 @@ import {
SkeletonBinary, SkeletonBinary,
SkeletonBounds, SkeletonBounds,
SkeletonClipping, SkeletonClipping,
type SkeletonData, SkeletonData,
SkeletonJson, SkeletonJson,
Skin, Skin,
type Slot, type Slot,
@ -261,6 +261,14 @@ export interface SpineEvents {
start: [trackEntry: TrackEntry]; start: [trackEntry: TrackEntry];
} }
export interface ClippedData {
vertices: Float32Array;
uvs: Float32Array;
indices: Uint16Array;
vertexCount: number;
indicesCount: number;
}
export interface AttachmentCacheData { export interface AttachmentCacheData {
id: string; id: string;
clipped: boolean; clipped: boolean;
@ -272,13 +280,7 @@ export interface AttachmentCacheData {
darkTint: boolean; darkTint: boolean;
skipRender: boolean; skipRender: boolean;
texture: Texture; texture: Texture;
clippedData?: { clippedData?: ClippedData;
vertices: Float32Array;
uvs: Float32Array;
indices: Uint16Array;
vertexCount: number;
indicesCount: number;
};
} }
interface SlotsToClipping { interface SlotsToClipping {
@ -385,10 +387,12 @@ export class Spine extends ViewContainer {
} }
private hasNeverUpdated = true; private hasNeverUpdated = true;
constructor (options: SpineOptions | SpineFromOptions) { constructor (options: SkeletonData | SpineOptions | SpineFromOptions) {
super({}); super({});
if ("skeleton" in options) if (options instanceof SkeletonData)
options = { skeletonData: options };
else if ("skeleton" in options)
options = new.target.createOptions(options); options = new.target.createOptions(options);
this.allowChildren = true; this.allowChildren = true;
@ -579,7 +583,7 @@ export class Spine extends ViewContainer {
const clippingAttachment = slotClipping.pose.attachment as ClippingAttachment; const clippingAttachment = slotClipping.pose.attachment as ClippingAttachment;
// create the pixi mask, only the first time and if the clipped slot is the first one clipped by this currentClippingSlot // create the pixi mask, only the first time and if the clipped slot is the first one clipped by this currentClippingSlot
let mask = currentClippingSlot.mask as Graphics; let mask = currentClippingSlot.mask;
if (!mask) { if (!mask) {
mask = maskPool.obtain(); mask = maskPool.obtain();
currentClippingSlot.mask = mask; currentClippingSlot.mask = mask;
@ -1030,11 +1034,11 @@ export class Spine extends ViewContainer {
Ticker.shared.remove(this.internalUpdate, this); Ticker.shared.remove(this.internalUpdate, this);
this.state.clearListeners(); this.state.clearListeners();
this.debug = undefined; this.debug = undefined;
this.skeleton = null as any; (this.skeleton as unknown) = null;
this.state = null as any; (this.state as unknown) = null;
(this._slotsObject as any) = null; (this._slotsObject as unknown) = null;
(this.attachmentCacheData as unknown) = null;
this._lastAttachments.length = 0; this._lastAttachments.length = 0;
this.attachmentCacheData = null as any;
} }
/** Converts a point from the skeleton coordinate system to the Pixi world coordinate system. */ /** Converts a point from the skeleton coordinate system to the Pixi world coordinate system. */

View File

@ -27,8 +27,8 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { Container, Graphics, Text } from 'pixi.js';
import { Spine } from './Spine.js'; import type { AnimationStateListener } from '@esotericsoftware/spine-core';
import { import {
ClippingAttachment, ClippingAttachment,
MeshAttachment, MeshAttachment,
@ -36,8 +36,8 @@ import {
RegionAttachment, RegionAttachment,
SkeletonBounds SkeletonBounds
} from '@esotericsoftware/spine-core'; } from '@esotericsoftware/spine-core';
import { Container, Graphics, Text } from 'pixi.js';
import type { AnimationStateListener } from '@esotericsoftware/spine-core'; import type { Spine } from './Spine.js';
/** /**
* Make a class that extends from this interface to create your own debug renderer. * Make a class that extends from this interface to create your own debug renderer.
@ -171,12 +171,12 @@ export class SpineDebugRenderer implements ISpineDebugRenderer {
debugDisplayObjects.parentDebugContainer.addChild(debugDisplayObjects.pathsLine); debugDisplayObjects.parentDebugContainer.addChild(debugDisplayObjects.pathsLine);
debugDisplayObjects.parentDebugContainer.addChild(debugDisplayObjects.eventText); debugDisplayObjects.parentDebugContainer.addChild(debugDisplayObjects.eventText);
(debugDisplayObjects.parentDebugContainer as any).zIndex = 9999999; debugDisplayObjects.parentDebugContainer.zIndex = 9999999;
// Disable screen reader and mouse input on debug objects. // Disable screen reader and mouse input on debug objects.
(debugDisplayObjects.parentDebugContainer as any).accessibleChildren = false; debugDisplayObjects.parentDebugContainer.accessibleChildren = false;
(debugDisplayObjects.parentDebugContainer as any).eventMode = 'none'; debugDisplayObjects.parentDebugContainer.eventMode = 'none';
(debugDisplayObjects.parentDebugContainer as any).interactiveChildren = false; debugDisplayObjects.parentDebugContainer.interactiveChildren = false;
spine.addChild(debugDisplayObjects.parentDebugContainer); spine.addChild(debugDisplayObjects.parentDebugContainer);

View File

@ -150,6 +150,7 @@ export class SpinePipe implements RenderPipe<Spine> {
if (!skipRender) { if (!skipRender) {
container.includeInBuild = true; container.includeInBuild = true;
// See https://github.com/pixijs/pixijs/blob/b4c050a791fe65e979e467c9cba2bda0c01a1c35/src/scene/container/utils/collectAllRenderables.ts#L28 // See https://github.com/pixijs/pixijs/blob/b4c050a791fe65e979e467c9cba2bda0c01a1c35/src/scene/container/utils/collectAllRenderables.ts#L28
// biome-ignore lint/style/noNonNullAssertion: it was in pixi code
container.collectRenderables(instructionSet, this.renderer, null!); container.collectRenderables(instructionSet, this.renderer, null!);
} }
@ -185,13 +186,13 @@ export class SpinePipe implements RenderPipe<Spine> {
} }
destroyRenderable (spine: Spine) { destroyRenderable (spine: Spine) {
this.gpuSpineData[spine.uid] = null as any; (this.gpuSpineData[spine.uid] as unknown) = null;
spine.off('destroyed', this._destroyRenderableBound); spine.off('destroyed', this._destroyRenderableBound);
} }
destroy () { destroy () {
this.gpuSpineData = null as any; (this.gpuSpineData as unknown) = null;
this.renderer = null as any; (this.renderer as unknown) = null;
} }
private _getSpineData (spine: Spine): GpuSpineDataElement { private _getSpineData (spine: Spine): GpuSpineDataElement {

View File

@ -27,10 +27,9 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { Texture as PixiTexture } from 'pixi.js';
import { BlendMode, Texture, TextureFilter, TextureWrap } from '@esotericsoftware/spine-core'; import { BlendMode, Texture, TextureFilter, TextureWrap } from '@esotericsoftware/spine-core';
import type { BLEND_MODES, SCALE_MODE, TextureSource, WRAP_MODE } from 'pixi.js'; import type { BLEND_MODES, SCALE_MODE, TextureSource, WRAP_MODE } from 'pixi.js';
import { Texture as PixiTexture } from 'pixi.js';
export class SpineTexture extends Texture { export class SpineTexture extends Texture {
private static readonly textureMap: Map<TextureSource, SpineTexture> = new Map<TextureSource, SpineTexture>(); private static readonly textureMap: Map<TextureSource, SpineTexture> = new Map<TextureSource, SpineTexture>();

View File

@ -120,7 +120,7 @@ const spineTextureAtlasLoader: AssetExtension<RawAtlas | TextureAtlas, ISpineAtl
} }
// we will wait for all promises for the textures at the same time at the end. // we will wait for all promises for the textures at the same time at the end.
const textureLoadingPromises: Promise<any>[] = []; const textureLoadingPromises: Promise<void>[] = [];
// fill the pages // fill the pages
for (const page of retval.pages) { for (const page of retval.pages) {
@ -161,6 +161,7 @@ extensions.add(spineTextureAtlasLoader);
export interface ISpineAtlasMetadata { export interface ISpineAtlasMetadata {
// If you are downloading an .atlas file, this metadata will go to the Texture loader // If you are downloading an .atlas file, this metadata will go to the Texture loader
// biome-ignore lint/suspicious/noExplicitAny: user can pass any
imageMetadata?: any; imageMetadata?: any;
// If you already have atlas pages loaded as pixi textures // If you already have atlas pages loaded as pixi textures
// and want to use that to create the atlas, you can pass them here // and want to use that to create the atlas, you can pass them here

View File

@ -37,15 +37,18 @@ import {
type ResolvedAsset type ResolvedAsset
} from 'pixi.js'; } from 'pixi.js';
// biome-ignore lint/suspicious/noExplicitAny: can receive any
type SkeletonJsonAsset = any; type SkeletonJsonAsset = any;
type SkeletonBinaryAsset = Uint8Array; type SkeletonBinaryAsset = Uint8Array;
const loaderName = "spineSkeletonLoader"; const loaderName = "spineSkeletonLoader";
// biome-ignore lint/suspicious/noExplicitAny: can receive any
function isJson (resource: any): resource is SkeletonJsonAsset { function isJson (resource: any): resource is SkeletonJsonAsset {
return Object.prototype.hasOwnProperty.call(resource, 'bones'); return Object.prototype.hasOwnProperty.call(resource, 'bones');
} }
// biome-ignore lint/suspicious/noExplicitAny: can receive any
function isBuffer (resource: any): resource is SkeletonBinaryAsset { function isBuffer (resource: any): resource is SkeletonBinaryAsset {
return resource instanceof Uint8Array; return resource instanceof Uint8Array;
} }
@ -74,8 +77,8 @@ const spineLoaderExtension: AssetExtension<SkeletonJsonAsset | SkeletonBinaryAss
return buffer; return buffer;
}, },
testParse (asset: unknown, options: ResolvedAsset): Promise<boolean> { testParse (asset: unknown, options: ResolvedAsset): Promise<boolean> {
const isJsonSpineModel = checkExtension(options.src!, '.json') && isJson(asset); const isJsonSpineModel = checkExtension(options.src as string, '.json') && isJson(asset);
const isBinarySpineModel = checkExtension(options.src!, '.skel') && isBuffer(asset); const isBinarySpineModel = checkExtension(options.src as string, '.skel') && isBuffer(asset);
const isExplicitLoadParserSet = options.parser === loaderName || options.loadParser === loaderName; const isExplicitLoadParserSet = options.parser === loaderName || options.loadParser === loaderName;
return Promise.resolve(isJsonSpineModel || isBinarySpineModel || isExplicitLoadParserSet); return Promise.resolve(isJsonSpineModel || isBinarySpineModel || isExplicitLoadParserSet);

View File

@ -30,11 +30,11 @@
import { import {
Batcher, Batcher,
Color, Color,
DefaultBatchableMeshElement, type DefaultBatchableMeshElement,
DefaultBatchableQuadElement, type DefaultBatchableQuadElement,
extensions,
ExtensionType, ExtensionType,
Shader extensions,
type Shader
} from 'pixi.js'; } from 'pixi.js';
import { DarkTintBatchGeometry } from './DarkTintBatchGeometry.js'; import { DarkTintBatchGeometry } from './DarkTintBatchGeometry.js';
import { DarkTintShader } from './DarkTintShader.js'; import { DarkTintShader } from './DarkTintShader.js';

View File

@ -33,6 +33,7 @@ import './assets/SkeletonLoader.js'; // Side effects install the loaders into pi
import './darktint/DarkTintBatcher.js'; // Side effects install the batcher into pixi import './darktint/DarkTintBatcher.js'; // Side effects install the batcher into pixi
import './SpinePipe.js'; import './SpinePipe.js';
export * from '@esotericsoftware/spine-core';
export * from './assets/AtlasLoader.js'; export * from './assets/AtlasLoader.js';
export * from './assets/SkeletonLoader.js'; export * from './assets/SkeletonLoader.js';
export * from './require-shim.js'; export * from './require-shim.js';
@ -40,4 +41,3 @@ export * from './Spine.js';
export * from './SpineDebugRenderer.js'; export * from './SpineDebugRenderer.js';
export * from './SpinePipe.js'; export * from './SpinePipe.js';
export * from './SpineTexture.js'; export * from './SpineTexture.js';
export * from '@esotericsoftware/spine-core';

View File

@ -27,6 +27,8 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
// biome-ignore-all lint: ignore biome for this file
if (typeof window !== 'undefined' && (window as any).PIXI) { if (typeof window !== 'undefined' && (window as any).PIXI) {
const prevRequire = window.require; const prevRequire = window.require;
(window as any).require = (x: string) => { (window as any).require = (x: string) => {

View File

@ -27,22 +27,16 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { Animation, AnimationState, AnimationStateData, AtlasAttachmentLoader, Bone, Color, Disposable, Downloader, MathUtils, MixBlend, MixDirection, Physics, Skeleton, SkeletonBinary, SkeletonData, SkeletonJson, StringMap, TextureAtlas, TextureFilter, TimeKeeper, TrackEntry, Vector2 } from "@esotericsoftware/spine-core" import { type Animation, AnimationState, AnimationStateData, AtlasAttachmentLoader, type Bone, Color, type Disposable, type Downloader, MathUtils, MixBlend, MixDirection, Physics, Skeleton, SkeletonBinary, type SkeletonData, SkeletonJson, type StringMap, type TextureAtlas, TextureFilter, TimeKeeper, type TrackEntry, Vector2 } from "@esotericsoftware/spine-core"
import { AssetManager, GLTexture, Input, LoadingScreen, ManagedWebGLRenderingContext, ResizeMode, SceneRenderer, Vector3 } from "@esotericsoftware/spine-webgl" import { AssetManager, type GLTexture, Input, LoadingScreen, ManagedWebGLRenderingContext, ResizeMode, SceneRenderer, Vector3 } from "@esotericsoftware/spine-webgl"
export interface SpinePlayerConfig { export interface SpinePlayerConfig {
/* The URL of the skeleton JSON (.json) or binary (.skel) file */ /* The URL of the skeleton JSON (.json) or binary (.skel) file */
skeleton?: string; skeleton?: string;
/* @deprecated Use skeleton instead. The URL of the skeleton JSON file (.json). Undefined if binaryUrl is given. */
jsonUrl?: string
/* Optional: The name of a field in the JSON that holds the skeleton data. Default: none */ /* Optional: The name of a field in the JSON that holds the skeleton data. Default: none */
jsonField?: string jsonField?: string
/* @deprecated Use skeleton instead. The URL of the skeleton binary file (.skel). Undefined if jsonUrl is given. */
binaryUrl?: string
/* The scale when loading the skeleton data. Default: 1 */ /* The scale when loading the skeleton data. Default: 1 */
scale?: number scale?: number
@ -245,12 +239,12 @@ export class SpinePlayer implements Disposable {
private input?: Input; private input?: Input;
constructor (parent: HTMLElement | string, private config: SpinePlayerConfig) { constructor (parent: HTMLElement | string, private config: SpinePlayerConfig) {
let parentDom = typeof parent === "string" ? document.getElementById(parent) : parent; const parentDom = typeof parent === "string" ? document.getElementById(parent) : parent;
if (parentDom == null) throw new Error("SpinePlayer parent not found: " + parent); if (parentDom == null) throw new Error(`SpinePlayer parent not found: ${parent}`);
this.parent = parentDom; this.parent = parentDom;
if (config.showControls === void 0) config.showControls = true; if (config.showControls === void 0) config.showControls = true;
let controls = config.showControls ? /*html*/` const controls = config.showControls ? /*html*/`
<div class="spine-player-controls spine-player-popup-parent spine-player-controls-hidden"> <div class="spine-player-controls spine-player-popup-parent spine-player-controls-hidden">
<div class="spine-player-timeline"></div> <div class="spine-player-timeline"></div>
<div class="spine-player-buttons"> <div class="spine-player-buttons">
@ -287,8 +281,8 @@ export class SpinePlayer implements Disposable {
this.loadingScreen?.dispose(); this.loadingScreen?.dispose();
this.assetManager?.dispose(); this.assetManager?.dispose();
this.context?.dispose(); this.context?.dispose();
for (var i = 0; i < this.eventListeners.length; i++) { for (let i = 0; i < this.eventListeners.length; i++) {
var eventListener = this.eventListeners[i]; const eventListener = this.eventListeners[i];
eventListener.target.removeEventListener(eventListener.event, eventListener.func); eventListener.target.removeEventListener(eventListener.event, eventListener.func);
} }
this.input?.dispose(); this.input?.dispose();
@ -308,12 +302,10 @@ export class SpinePlayer implements Disposable {
private validateConfig (config: SpinePlayerConfig) { private validateConfig (config: SpinePlayerConfig) {
if (!config) throw new Error("A configuration object must be passed to to new SpinePlayer()."); if (!config) throw new Error("A configuration object must be passed to to new SpinePlayer().");
if ((config as any).skelUrl) config.skeleton = (config as any).skelUrl; if ((config as any).skelUrl) config.skeleton = (config as any).skelUrl;
if (!config.skeleton && !config.jsonUrl && !config.binaryUrl) throw new Error("A URL must be specified for the skeleton JSON or binary file."); if (!config.skeleton) throw new Error("A URL must be specified for the skeleton JSON or binary file.");
if (!config.scale) config.scale = 1; if (!config.scale) config.scale = 1;
if (!config.atlas && !config.atlasUrl) throw new Error("A URL must be specified for the atlas file."); if (!config.atlas && !config.atlasUrl) throw new Error("A URL must be specified for the atlas file.");
if (config.jsonUrl && !config.skeleton) config.skeleton = config.jsonUrl;
if (config.binaryUrl && !config.skeleton) config.skeleton = config.binaryUrl;
if (config.atlasUrl && !config.atlas) config.atlas = config.atlasUrl; if (config.atlasUrl && !config.atlas) config.atlas = config.atlasUrl;
if (!config.backgroundColor) config.backgroundColor = config.alpha ? "00000000" : "000000"; if (!config.backgroundColor) config.backgroundColor = config.alpha ? "00000000" : "000000";
@ -334,9 +326,9 @@ export class SpinePlayer implements Disposable {
regions: false regions: false
}; };
if (config.animations && config.animation && config.animations.indexOf(config.animation) < 0) if (config.animations && config.animation && config.animations.indexOf(config.animation) < 0)
throw new Error("Animation '" + config.animation + "' is not in the config animation list: " + toString(config.animations)); throw new Error(`Animation '${config.animation}' is not in the config animation list: ${print(config.animations)}`);
if (config.skins && config.skin && config.skins.indexOf(config.skin) < 0) if (config.skins && config.skin && config.skins.indexOf(config.skin) < 0)
throw new Error("Default skin '" + config.skin + "' is not in the config skins list: " + toString(config.skins)); throw new Error(`Default skin '${config.skin}' is not in the config skins list: ${print(config.skins)}`);
if (!config.viewport) config.viewport = {} as any; if (!config.viewport) config.viewport = {} as any;
if (!config.viewport!.animations) config.viewport!.animations = {}; if (!config.viewport!.animations) config.viewport!.animations = {};
if (config.viewport!.debugRender === void 0) config.viewport!.debugRender = false; if (config.viewport!.debugRender === void 0) config.viewport!.debugRender = false;
@ -347,12 +339,12 @@ export class SpinePlayer implements Disposable {
} }
private initialize (): HTMLElement | null { private initialize (): HTMLElement | null {
let config = this.config; const config = this.config;
let dom = this.dom; const dom = this.dom;
if (!config.alpha) { // Prevents a flash before the first frame is drawn. if (!config.alpha) { // Prevents a flash before the first frame is drawn.
let hex = config.backgroundColor!; const hex = config.backgroundColor!;
this.dom.style.backgroundColor = (hex.charAt(0) == '#' ? hex : "#" + hex).substr(0, 7); this.dom.style.backgroundColor = (hex.charAt(0) === '#' ? hex : `#${hex}`).substr(0, 7);
} }
try { try {
@ -371,7 +363,7 @@ export class SpinePlayer implements Disposable {
// Load the assets. // Load the assets.
this.assetManager = new AssetManager(this.context, "", config.downloader); this.assetManager = new AssetManager(this.context, "", config.downloader);
if (config.rawDataURIs) { if (config.rawDataURIs) {
for (let path in config.rawDataURIs) for (const path in config.rawDataURIs)
this.assetManager.setRawDataURI(path, config.rawDataURIs[path]); this.assetManager.setRawDataURI(path, config.rawDataURIs[path]);
} }
if (config.skeleton!.endsWith(".json")) if (config.skeleton!.endsWith(".json"))
@ -386,23 +378,23 @@ export class SpinePlayer implements Disposable {
this.bgFullscreen.setFromString(config.fullScreenBackgroundColor!); this.bgFullscreen.setFromString(config.fullScreenBackgroundColor!);
if (config.showControls) { if (config.showControls) {
this.playerControls = dom.children[1] as HTMLElement; this.playerControls = dom.children[1] as HTMLElement;
let controls = this.playerControls.children; const controls = this.playerControls.children;
let timeline = controls[0] as HTMLElement; const timeline = controls[0] as HTMLElement;
let buttons = controls[1].children; const buttons = controls[1].children;
this.playButton = buttons[0] as HTMLElement; this.playButton = buttons[0] as HTMLElement;
let speedButton = buttons[2] as HTMLElement; const speedButton = buttons[2] as HTMLElement;
this.animationButton = buttons[3] as HTMLElement; this.animationButton = buttons[3] as HTMLElement;
this.skinButton = buttons[4] as HTMLElement; this.skinButton = buttons[4] as HTMLElement;
let settingsButton = buttons[5] as HTMLElement; const settingsButton = buttons[5] as HTMLElement;
let fullscreenButton = buttons[6] as HTMLElement; const fullscreenButton = buttons[6] as HTMLElement;
let logoButton = buttons[7] as HTMLElement; const logoButton = buttons[7] as HTMLElement;
this.timelineSlider = new Slider(); this.timelineSlider = new Slider();
timeline.appendChild(this.timelineSlider.create()); timeline.appendChild(this.timelineSlider.create());
this.timelineSlider.change = (percentage) => { this.timelineSlider.change = (percentage) => {
this.pause(); this.pause();
let animationDuration = this.animationState!.getCurrent(0)!.animation!.duration; const animationDuration = this.animationState!.getCurrent(0)!.animation!.duration;
let time = animationDuration * percentage; const time = animationDuration * percentage;
this.animationState!.update(time - this.playTime); this.animationState!.update(time - this.playTime);
this.animationState!.apply(this.skeleton!); this.animationState!.apply(this.skeleton!);
this.skeleton!.update(time - this.playTime); this.skeleton!.update(time - this.playTime);
@ -420,11 +412,11 @@ export class SpinePlayer implements Disposable {
let oldStyleWidth = this.canvas.style.width, oldStyleHeight = this.canvas.style.height; let oldStyleWidth = this.canvas.style.width, oldStyleHeight = this.canvas.style.height;
let isFullscreen = false; let isFullscreen = false;
fullscreenButton.onclick = () => { fullscreenButton.onclick = () => {
let fullscreenChanged = () => { const fullscreenChanged = () => {
isFullscreen = !isFullscreen; isFullscreen = !isFullscreen;
if (!isFullscreen) { if (!isFullscreen) {
this.canvas!.style.width = oldWidth + "px"; this.canvas!.style.width = `${oldWidth}px`;
this.canvas!.style.height = oldHeight + "px"; this.canvas!.style.height = `${oldHeight}px`;
this.drawFrame(false); this.drawFrame(false);
// Got to reset the style to whatever the user set after the next layouting. // Got to reset the style to whatever the user set after the next layouting.
requestAnimationFrame(() => { requestAnimationFrame(() => {
@ -434,11 +426,11 @@ export class SpinePlayer implements Disposable {
} }
}; };
let player = dom as any; const player = dom as any;
player.onfullscreenchange = fullscreenChanged; player.onfullscreenchange = fullscreenChanged;
player.onwebkitfullscreenchange = fullscreenChanged; player.onwebkitfullscreenchange = fullscreenChanged;
let doc = document as any; const doc = document as any;
if (doc.fullscreenElement || doc.webkitFullscreenElement || doc.mozFullScreenElement || doc.msFullscreenElement) { if (doc.fullscreenElement || doc.webkitFullscreenElement || doc.mozFullScreenElement || doc.msFullscreenElement) {
if (doc.exitFullscreen) doc.exitFullscreen(); if (doc.exitFullscreen) doc.exitFullscreen();
else if (doc.mozCancelFullScreen) doc.mozCancelFullScreen(); else if (doc.mozCancelFullScreen) doc.mozCancelFullScreen();
@ -465,18 +457,18 @@ export class SpinePlayer implements Disposable {
if (this.error) return; if (this.error) return;
if (this.assetManager!.hasErrors()) if (this.assetManager!.hasErrors())
this.showError("Error: Assets could not be loaded.\n" + toString(this.assetManager!.getErrors())); this.showError(`Error: Assets could not be loaded.\n${print(this.assetManager!.getErrors())}`);
let config = this.config; const config = this.config;
// Configure filtering, don't use mipmaps in WebGL1 if the atlas page is non-POT // Configure filtering, don't use mipmaps in WebGL1 if the atlas page is non-POT
let atlas = this.assetManager!.require(config.atlas!) as TextureAtlas; const atlas = this.assetManager!.require(config.atlas!) as TextureAtlas;
let gl = this.context!.gl, anisotropic = gl.getExtension("EXT_texture_filter_anisotropic"); const gl = this.context!.gl, anisotropic = gl.getExtension("EXT_texture_filter_anisotropic");
let isWebGL1 = gl.getParameter(gl.VERSION).indexOf("WebGL 1.0") != -1; const isWebGL1 = gl.getParameter(gl.VERSION).indexOf("WebGL 1.0") !== -1;
for (let page of atlas.pages) { for (const page of atlas.pages) {
let minFilter = page.minFilter; let minFilter = page.minFilter;
var useMipMaps: boolean = config.mipmaps!; let useMipMaps: boolean = config.mipmaps!;
var isPOT = MathUtils.isPowerOfTwo(page.width) && MathUtils.isPowerOfTwo(page.height); const isPOT = MathUtils.isPowerOfTwo(page.width) && MathUtils.isPowerOfTwo(page.height);
if (isWebGL1 && !isPOT) useMipMaps = false; if (isWebGL1 && !isPOT) useMipMaps = false;
if (useMipMaps) { if (useMipMaps) {
@ -487,7 +479,7 @@ export class SpinePlayer implements Disposable {
minFilter = TextureFilter.Linear; // Don't use mipmaps without anisotropic. minFilter = TextureFilter.Linear; // Don't use mipmaps without anisotropic.
page.texture!.setFilters(minFilter, TextureFilter.Nearest); page.texture!.setFilters(minFilter, TextureFilter.Nearest);
} }
if (minFilter != TextureFilter.Nearest && minFilter != TextureFilter.Linear) (page.texture as GLTexture).update(true); if (minFilter !== TextureFilter.Nearest && minFilter !== TextureFilter.Linear) (page.texture as GLTexture).update(true);
} }
// Load skeleton data. // Load skeleton data.
@ -499,7 +491,7 @@ export class SpinePlayer implements Disposable {
if (!data) throw new Error("Empty JSON data."); if (!data) throw new Error("Empty JSON data.");
if (config.jsonField) { if (config.jsonField) {
data = data[config.jsonField]; data = data[config.jsonField];
if (!data) throw new Error("JSON field does not exist: " + config.jsonField); if (!data) throw new Error(`JSON field does not exist: ${config.jsonField}`);
} }
loader = new SkeletonJson(attachmentLoader); loader = new SkeletonJson(attachmentLoader);
} else { } else {
@ -513,7 +505,7 @@ export class SpinePlayer implements Disposable {
return; return;
} }
this.skeleton = new Skeleton(skeletonData); this.skeleton = new Skeleton(skeletonData);
let stateData = new AnimationStateData(skeletonData); const stateData = new AnimationStateData(skeletonData);
stateData.defaultMix = config.defaultMix!; stateData.defaultMix = config.defaultMix!;
this.animationState = new AnimationState(stateData); this.animationState = new AnimationState(stateData);
@ -560,8 +552,8 @@ export class SpinePlayer implements Disposable {
if (config.showControls) { if (config.showControls) {
// Hide skin and animation if there's only the default skin / no animation // Hide skin and animation if there's only the default skin / no animation
if (skeletonData.skins.length == 1 || (config.skins && config.skins.length == 1)) this.skinButton!.classList.add("spine-player-hidden"); if (skeletonData.skins.length === 1 || (config.skins && config.skins.length === 1)) this.skinButton!.classList.add("spine-player-hidden");
if (skeletonData.animations.length == 1 || (config.animations && config.animations.length == 1)) this.animationButton!.classList.add("spine-player-hidden"); if (skeletonData.animations.length === 1 || (config.animations && config.animations.length === 1)) this.animationButton!.classList.add("spine-player-hidden");
} }
if (config.success) config.success(this); if (config.success) config.success(this);
@ -590,30 +582,30 @@ export class SpinePlayer implements Disposable {
} }
private setupInput () { private setupInput () {
let config = this.config; const config = this.config;
let controlBones = config.controlBones!; const controlBones = config.controlBones!;
if (!controlBones.length && !config.showControls) return; if (!controlBones.length && !config.showControls) return;
let selectedBones = this.selectedBones = new Array<Bone | null>(controlBones.length); const selectedBones = this.selectedBones = new Array<Bone | null>(controlBones.length);
let canvas = this.canvas!; const canvas = this.canvas!;
let target: Bone | null = null; let target: Bone | null = null;
let offset = new Vector2(); const offset = new Vector2();
let coords = new Vector3(); const coords = new Vector3();
let mouse = new Vector3(); const mouse = new Vector3();
let position = new Vector2(); const position = new Vector2();
let skeleton = this.skeleton!; const skeleton = this.skeleton!;
let renderer = this.sceneRenderer!; const renderer = this.sceneRenderer!;
if (config.interactive) { if (config.interactive) {
let closest = function (x: number, y: number): Bone | null { const closest = (x: number, y: number): Bone | null => {
mouse.set(x, canvas.clientHeight - y, 0) mouse.set(x, canvas.clientHeight - y, 0)
offset.x = offset.y = 0; offset.x = offset.y = 0;
let bestDistance = 24, index = 0; let bestDistance = 24, index = 0;
let best: Bone | null = null; let best: Bone | null = null;
for (let i = 0; i < controlBones.length; i++) { for (let i = 0; i < controlBones.length; i++) {
selectedBones[i] = null; selectedBones[i] = null;
let bone = skeleton.findBone(controlBones[i]); const bone = skeleton.findBone(controlBones[i]);
if (!bone) continue; if (!bone) continue;
let distance = renderer.camera.worldToScreen( const distance = renderer.camera.worldToScreen(
coords.set(bone.applied.worldX, bone.applied.worldY, 0), coords.set(bone.applied.worldX, bone.applied.worldY, 0),
canvas.clientWidth, canvas.clientHeight).distance(mouse); canvas.clientWidth, canvas.clientHeight).distance(mouse);
if (distance < bestDistance) { if (distance < bestDistance) {
@ -668,27 +660,27 @@ export class SpinePlayer implements Disposable {
}); });
this.addEventListener(document, "touchmove", (ev: UIEvent) => { this.addEventListener(document, "touchmove", (ev: UIEvent) => {
if (ev instanceof TouchEvent) { if (ev instanceof TouchEvent) {
let touches = ev.changedTouches; const touches = ev.changedTouches;
if (touches.length) { if (touches.length) {
let touch = touches[0]; const touch = touches[0];
handleHover(touch.clientX, touch.clientY); handleHover(touch.clientX, touch.clientY);
} }
} }
}); });
let overlap = (mouseX: number, mouseY: number, rect: DOMRect | ClientRect): boolean => { const overlap = (mouseX: number, mouseY: number, rect: DOMRect | ClientRect): boolean => {
let x = mouseX - rect.left, y = mouseY - rect.top; const x = mouseX - rect.left, y = mouseY - rect.top;
return x >= 0 && x <= rect.width && y >= 0 && y <= rect.height; return x >= 0 && x <= rect.width && y >= 0 && y <= rect.height;
} }
let mouseOverControls = true, mouseOverCanvas = false; let mouseOverControls = true, mouseOverCanvas = false;
let handleHover = (mouseX: number, mouseY: number) => { const handleHover = (mouseX: number, mouseY: number) => {
let popup = findWithClass(this.dom, "spine-player-popup"); const popup = findWithClass(this.dom, "spine-player-popup");
mouseOverControls = overlap(mouseX, mouseY, this.playerControls!.getBoundingClientRect()); mouseOverControls = overlap(mouseX, mouseY, this.playerControls!.getBoundingClientRect());
mouseOverCanvas = overlap(mouseX, mouseY, canvas.getBoundingClientRect()); mouseOverCanvas = overlap(mouseX, mouseY, canvas.getBoundingClientRect());
clearTimeout(this.cancelId); clearTimeout(this.cancelId);
let hide = !popup && !mouseOverControls && !mouseOverCanvas && !this.paused; const hide = !popup && !mouseOverControls && !mouseOverCanvas && !this.paused;
if (hide) if (hide)
this.playerControls!.classList.add("spine-player-controls-hidden"); this.playerControls!.classList.add("spine-player-controls-hidden");
else else
@ -704,7 +696,7 @@ export class SpinePlayer implements Disposable {
play () { play () {
this.paused = false; this.paused = false;
let config = this.config; const config = this.config;
if (config.showControls) { if (config.showControls) {
this.cancelId = setTimeout(() => { this.cancelId = setTimeout(() => {
if (!this.paused) this.playerControls!.classList.add("spine-player-controls-hidden"); if (!this.paused) this.playerControls!.classList.add("spine-player-controls-hidden");
@ -747,17 +739,17 @@ export class SpinePlayer implements Disposable {
/* Sets the viewport for the specified animation. */ /* Sets the viewport for the specified animation. */
setViewport (animation: string | Animation): Animation { setViewport (animation: string | Animation): Animation {
if (typeof animation == "string") { if (typeof animation === "string") {
let foundAnimation = this.skeleton!.data.findAnimation(animation); const foundAnimation = this.skeleton!.data.findAnimation(animation);
if (!foundAnimation) throw new Error("Animation not found: " + animation); if (!foundAnimation) throw new Error(`Animation not found: ${animation}`);
animation = foundAnimation; animation = foundAnimation;
} }
this.previousViewport = this.currentViewport; this.previousViewport = this.currentViewport;
// Determine the base viewport. // Determine the base viewport.
let globalViewport = this.config.viewport!; const globalViewport = this.config.viewport!;
let viewport = this.currentViewport = { const viewport = this.currentViewport = {
clip: globalViewport.clip, clip: globalViewport.clip,
padLeft: globalViewport.padLeft !== void 0 ? globalViewport.padLeft : "10%", padLeft: globalViewport.padLeft !== void 0 ? globalViewport.padLeft : "10%",
padRight: globalViewport.padRight !== void 0 ? globalViewport.padRight : "10%", padRight: globalViewport.padRight !== void 0 ? globalViewport.padRight : "10%",
@ -773,7 +765,7 @@ export class SpinePlayer implements Disposable {
this.calculateAnimationViewport(animation, viewport); this.calculateAnimationViewport(animation, viewport);
// Override with the animation specific viewport for the final result. // Override with the animation specific viewport for the final result.
let userAnimViewport = this.config.viewport!.animations![animation.name]; const userAnimViewport = this.config.viewport!.animations![animation.name];
if (userAnimViewport) { if (userAnimViewport) {
if (userAnimViewport.x !== void 0 && userAnimViewport.y !== void 0 && userAnimViewport.width && userAnimViewport.height) { if (userAnimViewport.x !== void 0 && userAnimViewport.y !== void 0 && userAnimViewport.width && userAnimViewport.height) {
viewport.x = userAnimViewport.x; viewport.x = userAnimViewport.x;
@ -809,7 +801,7 @@ export class SpinePlayer implements Disposable {
let steps = 100, stepTime = animation.duration ? animation.duration / steps : 0, time = 0; let steps = 100, stepTime = animation.duration ? animation.duration / steps : 0, time = 0;
let minX = 100000000, maxX = -100000000, minY = 100000000, maxY = -100000000; let minX = 100000000, maxX = -100000000, minY = 100000000, maxY = -100000000;
let offset = new Vector2(), size = new Vector2(); const offset = new Vector2(), size = new Vector2();
const tempArray = new Array<number>(2); const tempArray = new Array<number>(2);
for (let i = 0; i < steps; i++, time += stepTime) { for (let i = 0; i < steps; i++, time += stepTime) {
@ -817,13 +809,13 @@ export class SpinePlayer implements Disposable {
this.skeleton!.updateWorldTransform(Physics.update); this.skeleton!.updateWorldTransform(Physics.update);
this.skeleton!.getBounds(offset, size, tempArray, this.sceneRenderer!.skeletonRenderer.getSkeletonClipping()); this.skeleton!.getBounds(offset, size, tempArray, this.sceneRenderer!.skeletonRenderer.getSkeletonClipping());
if (!isNaN(offset.x) && !isNaN(offset.y) && !isNaN(size.x) && !isNaN(size.y)) { if (!Number.isNaN(offset.x) && !Number.isNaN(offset.y) && !Number.isNaN(size.x) && !Number.isNaN(size.y)) {
minX = Math.min(offset.x, minX); minX = Math.min(offset.x, minX);
maxX = Math.max(offset.x + size.x, maxX); maxX = Math.max(offset.x + size.x, maxX);
minY = Math.min(offset.y, minY); minY = Math.min(offset.y, minY);
maxY = Math.max(offset.y + size.y, maxY); maxY = Math.max(offset.y + size.y, maxY);
} else } else
this.showError("Animation bounds are invalid: " + animation.name); this.showError(`Animation bounds are invalid: ${animation.name}`);
} }
viewport.x = minX; viewport.x = minX;
@ -838,20 +830,20 @@ export class SpinePlayer implements Disposable {
if (this.disposed) return; if (this.disposed) return;
if (requestNextFrame && !this.stopRequestAnimationFrame) requestAnimationFrame(() => this.drawFrame()); if (requestNextFrame && !this.stopRequestAnimationFrame) requestAnimationFrame(() => this.drawFrame());
let doc = document as any; const doc = document as any;
let isFullscreen = doc.fullscreenElement || doc.webkitFullscreenElement || doc.mozFullScreenElement || doc.msFullscreenElement; const isFullscreen = doc.fullscreenElement || doc.webkitFullscreenElement || doc.mozFullScreenElement || doc.msFullscreenElement;
let bg = isFullscreen ? this.bgFullscreen : this.bg; const bg = isFullscreen ? this.bgFullscreen : this.bg;
this.time.update(); this.time.update();
let delta = this.time.delta; const delta = this.time.delta;
// Load the skeleton if the assets are ready. // Load the skeleton if the assets are ready.
let loading = !this.assetManager!.isLoadingComplete(); const loading = !this.assetManager!.isLoadingComplete();
if (!this.skeleton && !loading) this.loadSkeleton(); if (!this.skeleton && !loading) this.loadSkeleton();
let skeleton = this.skeleton!; const skeleton = this.skeleton!;
let config = this.config!; const config = this.config!;
if (skeleton) { if (skeleton) {
let playDelta = this.paused ? 0 : delta * this.speed; const playDelta = this.paused ? 0 : delta * this.speed;
if (config.frame) config.frame(this, playDelta); if (config.frame) config.frame(this, playDelta);
// Update animation time and pose the skeleton. // Update animation time and pose the skeleton.
@ -866,10 +858,10 @@ export class SpinePlayer implements Disposable {
if (config.showControls) { if (config.showControls) {
this.playTime += playDelta; this.playTime += playDelta;
let entry = this.animationState!.getCurrent(0); const entry = this.animationState!.getCurrent(0);
if (entry) { if (entry) {
let duration = entry.animation!.duration; const duration = entry.animation!.duration;
while (this.playTime >= duration && duration != 0) while (this.playTime >= duration && duration !== 0)
this.playTime -= duration; this.playTime -= duration;
this.playTime = Math.max(0, Math.min(this.playTime, duration)); this.playTime = Math.max(0, Math.min(this.playTime, duration));
this.timelineSlider!.setValue(this.playTime / duration); this.timelineSlider!.setValue(this.playTime / duration);
@ -878,19 +870,19 @@ export class SpinePlayer implements Disposable {
} }
// Determine the viewport. // Determine the viewport.
let viewport = this.viewport; const viewport = this.viewport;
viewport.x = this.currentViewport.x - (this.currentViewport.padLeft as number); viewport.x = this.currentViewport.x - (this.currentViewport.padLeft as number);
viewport.y = this.currentViewport.y - (this.currentViewport.padBottom as number); viewport.y = this.currentViewport.y - (this.currentViewport.padBottom as number);
viewport.width = this.currentViewport.width + (this.currentViewport.padLeft as number) + (this.currentViewport.padRight as number); viewport.width = this.currentViewport.width + (this.currentViewport.padLeft as number) + (this.currentViewport.padRight as number);
viewport.height = this.currentViewport.height + (this.currentViewport.padBottom as number) + (this.currentViewport.padTop as number); viewport.height = this.currentViewport.height + (this.currentViewport.padBottom as number) + (this.currentViewport.padTop as number);
if (this.previousViewport) { if (this.previousViewport) {
let transitionAlpha = (performance.now() - this.viewportTransitionStart) / 1000 / config.viewport!.transitionTime!; const transitionAlpha = (performance.now() - this.viewportTransitionStart) / 1000 / config.viewport!.transitionTime!;
if (transitionAlpha < 1) { if (transitionAlpha < 1) {
let x = this.previousViewport.x - (this.previousViewport.padLeft as number); const x = this.previousViewport.x - (this.previousViewport.padLeft as number);
let y = this.previousViewport.y - (this.previousViewport.padBottom as number); const y = this.previousViewport.y - (this.previousViewport.padBottom as number);
let width = this.previousViewport.width + (this.previousViewport.padLeft as number) + (this.previousViewport.padRight as number); const width = this.previousViewport.width + (this.previousViewport.padLeft as number) + (this.previousViewport.padRight as number);
let height = this.previousViewport.height + (this.previousViewport.padBottom as number) + (this.previousViewport.padTop as number); const height = this.previousViewport.height + (this.previousViewport.padBottom as number) + (this.previousViewport.padTop as number);
viewport.x = x + (viewport.x - x) * transitionAlpha; viewport.x = x + (viewport.x - x) * transitionAlpha;
viewport.y = y + (viewport.y - y) * transitionAlpha; viewport.y = y + (viewport.y - y) * transitionAlpha;
viewport.width = width + (viewport.width - width) * transitionAlpha; viewport.width = width + (viewport.width - width) * transitionAlpha;
@ -898,7 +890,7 @@ export class SpinePlayer implements Disposable {
} }
} }
let renderer = this.sceneRenderer!; const renderer = this.sceneRenderer!;
renderer.camera.zoom = this.canvas!.height / this.canvas!.width > viewport.height / viewport.width renderer.camera.zoom = this.canvas!.height / this.canvas!.width > viewport.height / viewport.width
? viewport.width / this.canvas!.width : viewport.height / this.canvas!.height; ? viewport.width / this.canvas!.width : viewport.height / this.canvas!.height;
renderer.camera.position.x = viewport.x + viewport.width / 2; renderer.camera.position.x = viewport.x + viewport.width / 2;
@ -908,7 +900,7 @@ export class SpinePlayer implements Disposable {
renderer.resize(this.currentViewport.clip ? ResizeMode.FitClip : ResizeMode.Fit, viewport.width, viewport.height); renderer.resize(this.currentViewport.clip ? ResizeMode.FitClip : ResizeMode.Fit, viewport.width, viewport.height);
// Clear the screen. // Clear the screen.
let gl = this.context!.gl; const gl = this.context!.gl;
gl.clearColor(bg.r, bg.g, bg.b, bg.a); gl.clearColor(bg.r, bg.g, bg.b, bg.a);
gl.clear(gl.COLOR_BUFFER_BIT); gl.clear(gl.COLOR_BUFFER_BIT);
@ -917,9 +909,9 @@ export class SpinePlayer implements Disposable {
renderer.begin(); renderer.begin();
// Draw the background image. // Draw the background image.
let bgImage = config.backgroundImage; const bgImage = config.backgroundImage;
if (bgImage) { if (bgImage) {
let texture = this.assetManager!.require(bgImage.url) as GLTexture; const texture = this.assetManager!.require(bgImage.url) as GLTexture;
if (bgImage.x !== void 0 && bgImage.y !== void 0 && bgImage.width && bgImage.height) if (bgImage.x !== void 0 && bgImage.y !== void 0 && bgImage.width && bgImage.height)
renderer.drawTexture(texture, bgImage.x, bgImage.y, bgImage.width, bgImage.height); renderer.drawTexture(texture, bgImage.x, bgImage.y, bgImage.width, bgImage.height);
else else
@ -940,15 +932,15 @@ export class SpinePlayer implements Disposable {
} }
// Draw the control bones. // Draw the control bones.
let controlBones = config.controlBones!; const controlBones = config.controlBones!;
if (controlBones.length) { if (controlBones.length) {
let selectedBones = this.selectedBones; const selectedBones = this.selectedBones;
gl.lineWidth(2); gl.lineWidth(2);
for (let i = 0; i < controlBones.length; i++) { for (let i = 0; i < controlBones.length; i++) {
let bone = skeleton.findBone(controlBones[i]); const bone = skeleton.findBone(controlBones[i]);
if (!bone) continue; if (!bone) continue;
let colorInner = selectedBones[i] ? BONE_INNER_OVER : BONE_INNER; const colorInner = selectedBones[i] ? BONE_INNER_OVER : BONE_INNER;
let colorOuter = selectedBones[i] ? BONE_OUTER_OVER : BONE_OUTER; const colorOuter = selectedBones[i] ? BONE_OUTER_OVER : BONE_OUTER;
const applied = bone.applied; const applied = bone.applied;
renderer.circle(true, skeleton.x + applied.worldX, skeleton.y + applied.worldY, 20, colorInner); renderer.circle(true, skeleton.x + applied.worldX, skeleton.y + applied.worldY, 20, colorInner);
renderer.circle(false, skeleton.x + applied.worldX, skeleton.y + applied.worldY, 20, colorOuter); renderer.circle(false, skeleton.x + applied.worldX, skeleton.y + applied.worldY, 20, colorOuter);
@ -992,10 +984,10 @@ export class SpinePlayer implements Disposable {
} }
private showSpeedDialog (speedButton: HTMLElement) { private showSpeedDialog (speedButton: HTMLElement) {
let id = "speed"; const id = "speed";
if (this.hidePopup(id)) return; if (this.hidePopup(id)) return;
let popup = new Popup(id, speedButton, this, this.playerControls!, /*html*/` const popup = new Popup(id, speedButton, this, this.playerControls!, /*html*/`
<div class="spine-player-popup-title">Speed</div> <div class="spine-player-popup-title">Speed</div>
<hr> <hr>
<div class="spine-player-row" style="align-items:center;padding:8px"> <div class="spine-player-row" style="align-items:center;padding:8px">
@ -1004,7 +996,7 @@ export class SpinePlayer implements Disposable {
<div class="spine-player-row" style="justify-content:space-between"><div>0.1x</div><div>1x</div><div>2x</div></div> <div class="spine-player-row" style="justify-content:space-between"><div>0.1x</div><div>1x</div><div>2x</div></div>
</div> </div>
</div>`); </div>`);
let slider = new Slider(2, 0.1, true); const slider = new Slider(2, 0.1, true);
findWithClass(popup.dom, "spine-player-speed-slider").appendChild(slider.create()); findWithClass(popup.dom, "spine-player-speed-slider").appendChild(slider.create());
slider.setValue(this.speed / 2); slider.setValue(this.speed / 2);
slider.change = (percentage) => this.speed = percentage * 2; slider.change = (percentage) => this.speed = percentage * 2;
@ -1012,21 +1004,21 @@ export class SpinePlayer implements Disposable {
} }
private showAnimationsDialog (animationsButton: HTMLElement) { private showAnimationsDialog (animationsButton: HTMLElement) {
let id = "animations"; const id = "animations";
if (this.hidePopup(id)) return; if (this.hidePopup(id)) return;
if (!this.skeleton || !this.skeleton.data.animations.length) return; if (!this.skeleton || !this.skeleton.data.animations.length) return;
let popup = new Popup(id, animationsButton, this, this.playerControls!, const popup = new Popup(id, animationsButton, this, this.playerControls!,
/*html*/`<div class="spine-player-popup-title">Animations</div><hr><ul class="spine-player-list"></ul>`); /*html*/`<div class="spine-player-popup-title">Animations</div><hr><ul class="spine-player-list"></ul>`);
let rows = findWithClass(popup.dom, "spine-player-list"); const rows = findWithClass(popup.dom, "spine-player-list");
this.skeleton.data.animations.forEach((animation) => { this.skeleton.data.animations.forEach((animation) => {
// Skip animations not whitelisted if a whitelist was given. // Skip animations not whitelisted if a whitelist was given.
if (this.config.animations && this.config.animations.indexOf(animation.name) < 0) return; if (this.config.animations && this.config.animations.indexOf(animation.name) < 0) return;
let row = createElement( const row = createElement(
/*html*/`<li class="spine-player-list-item selectable"><div class="selectable-circle"></div><div class="selectable-text"></div></li>`); /*html*/`<li class="spine-player-list-item selectable"><div class="selectable-circle"></div><div class="selectable-text"></div></li>`);
if (animation.name == this.config.animation) row.classList.add("selected"); if (animation.name === this.config.animation) row.classList.add("selected");
findWithClass(row, "selectable-text").innerText = animation.name; findWithClass(row, "selectable-text").innerText = animation.name;
rows.appendChild(row); rows.appendChild(row);
row.onclick = () => { row.onclick = () => {
@ -1042,20 +1034,20 @@ export class SpinePlayer implements Disposable {
} }
private showSkinsDialog (skinButton: HTMLElement) { private showSkinsDialog (skinButton: HTMLElement) {
let id = "skins"; const id = "skins";
if (this.hidePopup(id)) return; if (this.hidePopup(id)) return;
if (!this.skeleton || !this.skeleton.data.animations.length) return; if (!this.skeleton || !this.skeleton.data.animations.length) return;
let popup = new Popup(id, skinButton, this, this.playerControls!, const popup = new Popup(id, skinButton, this, this.playerControls!,
/*html*/`<div class="spine-player-popup-title">Skins</div><hr><ul class="spine-player-list"></ul>`); /*html*/`<div class="spine-player-popup-title">Skins</div><hr><ul class="spine-player-list"></ul>`);
let rows = findWithClass(popup.dom, "spine-player-list"); const rows = findWithClass(popup.dom, "spine-player-list");
this.skeleton.data.skins.forEach((skin) => { this.skeleton.data.skins.forEach((skin) => {
// Skip skins not whitelisted if a whitelist was given. // Skip skins not whitelisted if a whitelist was given.
if (this.config.skins && this.config.skins.indexOf(skin.name) < 0) return; if (this.config.skins && this.config.skins.indexOf(skin.name) < 0) return;
let row = createElement(/*html*/`<li class="spine-player-list-item selectable"><div class="selectable-circle"></div><div class="selectable-text"></div></li>`); const row = createElement(/*html*/`<li class="spine-player-list-item selectable"><div class="selectable-circle"></div><div class="selectable-text"></div></li>`);
if (skin.name == this.config.skin) row.classList.add("selected"); if (skin.name === this.config.skin) row.classList.add("selected");
findWithClass(row, "selectable-text").innerText = skin.name; findWithClass(row, "selectable-text").innerText = skin.name;
rows.appendChild(row); rows.appendChild(row);
row.onclick = () => { row.onclick = () => {
@ -1070,18 +1062,18 @@ export class SpinePlayer implements Disposable {
} }
private showSettingsDialog (settingsButton: HTMLElement) { private showSettingsDialog (settingsButton: HTMLElement) {
let id = "settings"; const id = "settings";
if (this.hidePopup(id)) return; if (this.hidePopup(id)) return;
if (!this.skeleton || !this.skeleton.data.animations.length) return; if (!this.skeleton || !this.skeleton.data.animations.length) return;
let popup = new Popup(id, settingsButton, this, this.playerControls!, /*html*/`<div class="spine-player-popup-title">Debug</div><hr><ul class="spine-player-list"></li>`); const popup = new Popup(id, settingsButton, this, this.playerControls!, /*html*/`<div class="spine-player-popup-title">Debug</div><hr><ul class="spine-player-list"></li>`);
let rows = findWithClass(popup.dom, "spine-player-list"); const rows = findWithClass(popup.dom, "spine-player-list");
let makeItem = (label: string, name: string) => { const makeItem = (label: string, name: string) => {
let row = createElement(/*html*/`<li class="spine-player-list-item"></li>`); const row = createElement(/*html*/`<li class="spine-player-list-item"></li>`);
let s = new Switch(label); const s = new Switch(label);
row.appendChild(s.create()); row.appendChild(s.create());
let debug = this.config.debug as any; const debug = this.config.debug as any;
s.setEnabled(debug[name]); s.setEnabled(debug[name]);
s.change = (value) => debug[name] = value; s.change = (value) => debug[name] = value;
rows.appendChild(row); rows.appendChild(row);
@ -1107,7 +1099,6 @@ export class SpinePlayer implements Disposable {
+ message.replace("\n", "<br><br>") + `</div>`)); + message.replace("\n", "<br><br>") + `</div>`));
if (this.config.error) this.config.error(this, message); if (this.config.error) this.config.error(this, message);
throw (error ? error : new Error(message)); throw (error ? error : new Error(message));
console.log(error);
} }
} }
} }
@ -1121,7 +1112,7 @@ class Popup {
this.dom = createElement(/*html*/`<div class="spine-player-popup spine-player-hidden"></div>`); this.dom = createElement(/*html*/`<div class="spine-player-popup spine-player-hidden"></div>`);
this.dom.innerHTML = htmlContent; this.dom.innerHTML = htmlContent;
parent.appendChild(this.dom); parent.appendChild(this.dom);
this.className = "spine-player-button-icon-" + id + "-selected"; this.className = `spine-player-button-icon-${id}-selected`;
} }
dispose () { dispose () {
@ -1131,7 +1122,7 @@ class Popup {
hide (id: string): boolean { hide (id: string): boolean {
this.dom.remove(); this.dom.remove();
this.button.classList.remove(this.className); this.button.classList.remove(this.className);
if (this.id == id) { if (this.id === id) {
this.player.popup = null; this.player.popup = null;
return true; return true;
} }
@ -1145,19 +1136,19 @@ class Popup {
// Make sure the popup isn't bigger than the player. // Make sure the popup isn't bigger than the player.
let dismissed = false; let dismissed = false;
let resize = () => { const resize = () => {
if (!dismissed) requestAnimationFrame(resize); if (!dismissed) requestAnimationFrame(resize);
let playerDom = this.player.dom; const playerDom = this.player.dom;
let bottomOffset = Math.abs(playerDom.getBoundingClientRect().bottom - playerDom.getBoundingClientRect().bottom); const bottomOffset = Math.abs(playerDom.getBoundingClientRect().bottom - playerDom.getBoundingClientRect().bottom);
let rightOffset = Math.abs(playerDom.getBoundingClientRect().right - playerDom.getBoundingClientRect().right); const rightOffset = Math.abs(playerDom.getBoundingClientRect().right - playerDom.getBoundingClientRect().right);
this.dom.style.maxHeight = (playerDom.clientHeight - bottomOffset - rightOffset) + "px"; this.dom.style.maxHeight = `${playerDom.clientHeight - bottomOffset - rightOffset}px`;
} }
requestAnimationFrame(resize); requestAnimationFrame(resize);
// Dismiss when clicking somewhere outside the popup. // Dismiss when clicking somewhere outside the popup.
let justClicked = true; let justClicked = true;
let windowClickListener = (event: any) => { const windowClickListener = (event: any) => {
if (justClicked || this.player.popup != this) { if (justClicked || this.player.popup !== this) {
justClicked = false; justClicked = false;
return; return;
} }
@ -1250,8 +1241,8 @@ class Slider {
setValue (percentage: number): number { setValue (percentage: number): number {
percentage = Math.max(0, Math.min(1, percentage)); percentage = Math.max(0, Math.min(1, percentage));
if (this.snaps) { if (this.snaps) {
let snap = 1 / this.snaps; const snap = 1 / this.snaps;
let modulo = percentage % snap; const modulo = percentage % snap;
// floor // floor
if (modulo < snap * this.snapPercentage) if (modulo < snap * this.snapPercentage)
percentage = percentage - modulo; percentage = percentage - modulo;
@ -1259,7 +1250,7 @@ class Slider {
percentage = percentage - modulo + snap; percentage = percentage - modulo + snap;
percentage = Math.max(0, Math.min(1, percentage)); percentage = Math.max(0, Math.min(1, percentage));
} }
this.value!.style.width = "" + (percentage * 100) + "%"; this.value!.style.width = `${percentage * 100}%`;
// this.knob.style.left = "" + (-8 + percentage * this.slider.clientWidth) + "px"; // this.knob.style.left = "" + (-8 + percentage * this.slider.clientWidth) + "px";
return percentage; return percentage;
} }
@ -1270,7 +1261,7 @@ function findWithClass (element: HTMLElement, className: string): HTMLElement {
} }
function createElement (html: string): HTMLElement { function createElement (html: string): HTMLElement {
let div = document.createElement("div"); const div = document.createElement("div");
div.innerHTML = html; div.innerHTML = html;
return div.children[0] as HTMLElement; return div.children[0] as HTMLElement;
} }
@ -1280,7 +1271,7 @@ function removeClass (elements: HTMLCollection, clazz: string) {
elements[i].classList.remove(clazz); elements[i].classList.remove(clazz);
} }
function toString (object: any) { function print (object: any) {
return JSON.stringify(object) return JSON.stringify(object)
.replace(/&/g, "&amp;") .replace(/&/g, "&amp;")
.replace(/</g, "&lt;") .replace(/</g, "&lt;")

View File

@ -88,14 +88,14 @@ body { margin: 0px; }
} }
private render (parent: HTMLElement) { private render (parent: HTMLElement) {
let dom = /*html*/` const dom = /*html*/`
<div style="display: flex; flex-direction: column; width: 100%; height: 100%;"> <div style="display: flex; flex-direction: column; width: 100%; height: 100%;">
<div style="width: 100%; height: 50%"></div> <div style="width: 100%; height: 50%"></div>
<iframe style="width: 100%; height: 50%; outline: none; border: none;"></iframe> <iframe style="width: 100%; height: 50%; outline: none; border: none;"></iframe>
</div> </div>
`; `;
parent.innerHTML = dom; parent.innerHTML = dom;
let codeElement = parent.children[0].children[0]; const codeElement = parent.children[0].children[0];
this.player = parent.children[0].children[1] as HTMLIFrameElement; this.player = parent.children[0].children[1] as HTMLIFrameElement;
requestAnimationFrame(() => { requestAnimationFrame(() => {

View File

@ -1,4 +1,4 @@
export * from './Player.js';
export * from './PlayerEditor.js';
export * from "@esotericsoftware/spine-core"; export * from "@esotericsoftware/spine-core";
export * from "@esotericsoftware/spine-webgl"; export * from "@esotericsoftware/spine-webgl";
export * from './Player.js';
export * from './PlayerEditor.js';

View File

@ -27,9 +27,9 @@
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { AssetCache, AssetManager, Color, Disposable, Input, LoadingScreen, ManagedWebGLRenderingContext, Physics, SceneRenderer, TimeKeeper, Vector2, Vector3 } from "@esotericsoftware/spine-webgl" import { AssetCache, AssetManager, type Bone, Color, type Disposable, Input, LoadingScreen, ManagedWebGLRenderingContext, Physics, SceneRenderer, type Skeleton, TimeKeeper, Vector2, Vector3 } from "@esotericsoftware/spine-webgl"
import { SpineWebComponentSkeleton } from "./SpineWebComponentSkeleton.js" import type { SpineWebComponentSkeleton } from "./SpineWebComponentSkeleton.js"
import { AttributeTypes, castValue, Point, Rectangle } from "./wcUtils.js" import { type AttributeTypes, castValue, type Point, type Rectangle } from "./wcUtils.js"
interface OverlayAttributes { interface OverlayAttributes {
overlayId?: string overlayId?: string
@ -41,6 +41,8 @@ interface OverlayAttributes {
} }
export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttributes, Disposable { export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttributes, Disposable {
declare parentElement: HTMLElement;
private static OVERLAY_ID = "spine-overlay-default-identifier"; private static OVERLAY_ID = "spine-overlay-default-identifier";
private static OVERLAY_LIST = new Map<string, SpineWebComponentOverlay>(); private static OVERLAY_LIST = new Map<string, SpineWebComponentOverlay>();
@ -66,7 +68,7 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
/** /**
* A list holding the widgets added to this overlay. * A list holding the widgets added to this overlay.
*/ */
public widgets = new Array<SpineWebComponentSkeleton>(); public widgets = [] as SpineWebComponentSkeleton[];
/** /**
* The {@link SceneRenderer} used by this overlay. * The {@link SceneRenderer} used by this overlay.
@ -256,7 +258,7 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
const { target, intersectionRatio } = elem; const { target, intersectionRatio } = elem;
let { isIntersecting } = elem; let { isIntersecting } = elem;
for (const widget of this.widgets) { for (const widget of this.widgets) {
if (widget.getHostElement() != target) continue; if (widget.getHostElement() !== target) continue;
// old browsers do not have isIntersecting // old browsers do not have isIntersecting
if (isIntersecting === undefined) { if (isIntersecting === undefined) {
@ -277,13 +279,13 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
if (this.hasCssTweakOff()) { if (this.hasCssTweakOff()) {
this.hasParentTransform = false; this.hasParentTransform = false;
} else { } else {
this.parentElement!.style.transform = `translateZ(0)`; this.parentElement.style.transform = `translateZ(0)`;
} }
} else { } else {
window.addEventListener("resize", this.windowResizeCallback); window.addEventListener("resize", this.windowResizeCallback);
} }
this.resizeObserver = new ResizeObserver(() => this.resizedCallback()); this.resizeObserver = new ResizeObserver(() => this.resizedCallback());
this.resizeObserver.observe(this.parentElement!); this.resizeObserver.observe(this.parentElement);
for (const widget of this.widgets) { for (const widget of this.widgets) {
this.intersectionObserver?.observe(widget.getHostElement()); this.intersectionObserver?.observe(widget.getHostElement());
@ -308,7 +310,7 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
this.input?.dispose(); this.input?.dispose();
} }
static attributesDescription: Record<string, { propertyName: keyof OverlayAttributes, type: AttributeTypes, defaultValue?: any }> = { static attributesDescription: Record<string, { propertyName: keyof OverlayAttributes, type: AttributeTypes, defaultValue?: OverlayAttributes[keyof OverlayAttributes] }> = {
"overlay-id": { propertyName: "overlayId", type: "string" }, "overlay-id": { propertyName: "overlayId", type: "string" },
"no-auto-parent-transform": { propertyName: "noAutoParentTransform", type: "boolean" }, "no-auto-parent-transform": { propertyName: "noAutoParentTransform", type: "boolean" },
"overflow-top": { propertyName: "overflowTop", type: "number" }, "overflow-top": { propertyName: "overflowTop", type: "number" },
@ -324,7 +326,7 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
attributeChangedCallback (name: string, oldValue: string | null, newValue: string | null): void { attributeChangedCallback (name: string, oldValue: string | null, newValue: string | null): void {
const { type, propertyName, defaultValue } = SpineWebComponentOverlay.attributesDescription[name]; const { type, propertyName, defaultValue } = SpineWebComponentOverlay.attributesDescription[name];
const val = castValue(type, newValue, defaultValue); const val = castValue(type, newValue, defaultValue);
(this as any)[propertyName] = val; (this[propertyName] as OverlayAttributes[typeof propertyName]) = val as OverlayAttributes[typeof propertyName];
return; return;
} }
@ -360,12 +362,12 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
this.scrolledCallback(); this.scrolledCallback();
if (!this.loaded) { if (!this.loaded) {
this.loaded = true; this.loaded = true;
this.parentElement!.appendChild(this); this.parentElement.appendChild(this);
} }
} }
private hasCssTweakOff () { private hasCssTweakOff () {
return this.noAutoParentTransform && getComputedStyle(this.parentElement!).transform === "none"; return this.noAutoParentTransform && getComputedStyle(this.parentElement).transform === "none";
} }
/** /**
@ -393,7 +395,7 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
const comparison = this.compareDocumentPosition(widget); const comparison = this.compareDocumentPosition(widget);
// DOCUMENT_POSITION_DISCONNECTED is needed when a widget is inside the overlay (due to followBone) // DOCUMENT_POSITION_DISCONNECTED is needed when a widget is inside the overlay (due to followBone)
if ((comparison & Node.DOCUMENT_POSITION_FOLLOWING) && !(comparison & Node.DOCUMENT_POSITION_DISCONNECTED)) { if ((comparison & Node.DOCUMENT_POSITION_FOLLOWING) && !(comparison & Node.DOCUMENT_POSITION_DISCONNECTED)) {
this.parentElement!.appendChild(this); this.parentElement.appendChild(this);
} }
} }
@ -448,7 +450,7 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
this.div.appendChild(this.fps); this.div.appendChild(this.fps);
this.fpsAppended = true; this.fpsAppended = true;
} }
this.fps.innerText = this.time.framesPerSecond.toFixed(2) + " fps"; this.fps.innerText = `${this.time.framesPerSecond.toFixed(2)} fps`;
} else { } else {
if (this.fpsAppended) { if (this.fpsAppended) {
this.div.removeChild(this.fps); this.div.removeChild(this.fps);
@ -482,15 +484,15 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
const renderWidgets = () => { const renderWidgets = () => {
clear(0, 0, 0, 0); clear(0, 0, 0, 0);
let renderer = this.renderer; const renderer = this.renderer;
renderer.begin(); renderer.begin();
let ref: DOMRect; let ref: DOMRect;
let offsetLeftForOevrlay = 0; let offsetLeftForOevrlay = 0;
let offsetTopForOverlay = 0; let offsetTopForOverlay = 0;
if (!this.appendedToBody) { if (!this.appendedToBody) {
ref = this.parentElement!.getBoundingClientRect(); ref = this.parentElement.getBoundingClientRect();
const computedStyle = getComputedStyle(this.parentElement!); const computedStyle = getComputedStyle(this.parentElement);
offsetLeftForOevrlay = ref.left + parseFloat(computedStyle.borderLeftWidth); offsetLeftForOevrlay = ref.left + parseFloat(computedStyle.borderLeftWidth);
offsetTopForOverlay = ref.top + parseFloat(computedStyle.borderTopWidth); offsetTopForOverlay = ref.top + parseFloat(computedStyle.borderTopWidth);
} }
@ -532,7 +534,7 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
if (loading) { if (loading) {
if (spinner) { if (spinner) {
if (!widget.loadingScreen) widget.loadingScreen = new LoadingScreen(renderer); if (!widget.loadingScreen) widget.loadingScreen = new LoadingScreen(renderer);
widget.loadingScreen!.drawInCoordinates(divOriginX, divOriginY); widget.loadingScreen?.drawInCoordinates(divOriginX, divOriginY);
} }
if (clip) endScissor(); if (clip) endScissor();
continue; continue;
@ -540,7 +542,7 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
if (skeleton) { if (skeleton) {
if (fit !== "origin") { if (fit !== "origin") {
let { x: ax, y: ay, width: aw, height: ah } = bounds; const { x: ax, y: ay, width: aw, height: ah } = bounds;
if (aw <= 0 || ah <= 0) continue; if (aw <= 0 || ah <= 0) continue;
// scale ratio // scale ratio
@ -622,7 +624,7 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
// drawing debug stuff // drawing debug stuff
if (debug) { if (debug) {
// if (true) { // if (true) {
let { x: ax, y: ay, width: aw, height: ah } = bounds; const { x: ax, y: ay, width: aw, height: ah } = bounds;
// show bounds and its center // show bounds and its center
if (drag) { if (drag) {
@ -645,7 +647,7 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
renderer.circle(true, bbCenterX, bbCenterY, 10, blue); renderer.circle(true, bbCenterX, bbCenterY, 10, blue);
// show skeleton root // show skeleton root
const root = skeleton.getRootBone()!; const root = skeleton.getRootBone() as Bone;
renderer.circle(true, root.applied.x + worldOffsetX, root.applied.y + worldOffsetY, 10, red); renderer.circle(true, root.applied.x + worldOffsetX, root.applied.y + worldOffsetY, 10, red);
// show shifted origin // show shifted origin
@ -720,7 +722,6 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
const red = new Color(1, 0, 0, 1); const red = new Color(1, 0, 0, 1);
const green = new Color(0, 1, 0, 1); const green = new Color(0, 1, 0, 1);
const blue = new Color(0, 0, 1, 1); const blue = new Color(0, 0, 1, 1);
const transparentWhite = new Color(1, 1, 1, .3);
const transparentRed = new Color(1, 0, 0, .3); const transparentRed = new Color(1, 0, 0, .3);
} }
@ -735,12 +736,12 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
this.pointerCanvasY = input.y - window.scrollY; this.pointerCanvasY = input.y - window.scrollY;
if (!this.appendedToBody) { if (!this.appendedToBody) {
const ref = this.parentElement!.getBoundingClientRect(); const ref = this.parentElement.getBoundingClientRect();
this.pointerCanvasX -= ref.left; this.pointerCanvasX -= ref.left;
this.pointerCanvasY -= ref.top; this.pointerCanvasY -= ref.top;
} }
let tempVector = this.tempVector; const tempVector = this.tempVector;
tempVector.set(this.pointerCanvasX, this.pointerCanvasY, 0); tempVector.set(this.pointerCanvasX, this.pointerCanvasY, 0);
this.renderer.camera.screenToWorld(tempVector, this.canvas.clientWidth, this.canvas.clientHeight); this.renderer.camera.screenToWorld(tempVector, this.canvas.clientWidth, this.canvas.clientHeight);
@ -763,8 +764,8 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
const inputManager = new Input(document.body, false) const inputManager = new Input(document.body, false)
const inputPointTemp: Point = new Vector2(); const inputPointTemp: Point = new Vector2();
const getInput = (ev?: MouseEvent | TouchEvent): Point => { const getInput = (ev: MouseEvent | TouchEvent): Point => {
const originalEvent = ev instanceof MouseEvent ? ev : ev!.changedTouches[0]; const originalEvent = ev instanceof MouseEvent ? ev : ev.changedTouches[0];
inputPointTemp.x = originalEvent.pageX + this.overflowLeftSize; inputPointTemp.x = originalEvent.pageX + this.overflowLeftSize;
inputPointTemp.y = originalEvent.pageY + this.overflowTopSize; inputPointTemp.y = originalEvent.pageY + this.overflowTopSize;
return inputPointTemp; return inputPointTemp;
@ -774,7 +775,7 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
let lastY = 0; let lastY = 0;
inputManager.addListener({ inputManager.addListener({
// moved is used to pass pointer position wrt to canvas and widget position and currently is EXPERIMENTAL // moved is used to pass pointer position wrt to canvas and widget position and currently is EXPERIMENTAL
moved: (x, y, ev) => { moved: (x, y, ev: MouseEvent | TouchEvent) => {
const input = getInput(ev); const input = getInput(ev);
this.updatePointer(input); this.updatePointer(input);
@ -784,7 +785,7 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
widget.pointerEventUpdate("move", ev); widget.pointerEventUpdate("move", ev);
} }
}, },
down: (x, y, ev) => { down: (x, y, ev: MouseEvent | TouchEvent) => {
const input = getInput(ev); const input = getInput(ev);
this.updatePointer(input); this.updatePointer(input);
@ -805,11 +806,11 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
lastX = input.x; lastX = input.x;
lastY = input.y; lastY = input.y;
}, },
dragged: (x, y, ev) => { dragged: (x, y, ev: MouseEvent | TouchEvent) => {
const input = getInput(ev); const input = getInput(ev);
let dragX = input.x - lastX; const dragX = input.x - lastX;
let dragY = input.y - lastY; const dragY = input.y - lastY;
this.updatePointer(input); this.updatePointer(input);
@ -820,7 +821,7 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
if (!widget.dragging) continue; if (!widget.dragging) continue;
const skeleton = widget.skeleton!; const skeleton = widget.skeleton as Skeleton;
widget.dragX += this.screenToWorldLength(dragX); widget.dragX += this.screenToWorldLength(dragX);
widget.dragY -= this.screenToWorldLength(dragY); widget.dragY -= this.screenToWorldLength(dragY);
skeleton.physicsTranslate(dragX, -dragY); skeleton.physicsTranslate(dragX, -dragY);
@ -861,8 +862,8 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
const totalWidth = width * (1 + (this.overflowLeft + this.overflowRight)); const totalWidth = width * (1 + (this.overflowLeft + this.overflowRight));
const totalHeight = height * (1 + (this.overflowTop + this.overflowBottom)); const totalHeight = height * (1 + (this.overflowTop + this.overflowBottom));
this.canvas.style.width = totalWidth + "px"; this.canvas.style.width = `${totalWidth}px`;
this.canvas.style.height = totalHeight + "px"; this.canvas.style.height = `${totalHeight}px`;
this.resize(totalWidth, totalHeight); this.resize(totalWidth, totalHeight);
} }
@ -872,28 +873,28 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
// this.div?.remove(); is it better width/height to zero? // this.div?.remove(); is it better width/height to zero?
// this.div!.style.width = 0 + "px"; // this.div!.style.width = 0 + "px";
// this.div!.style.height = 0 + "px"; // this.div!.style.height = 0 + "px";
this.div!.style.display = "none"; this.div.style.display = "none";
if (this.appendedToBody) { if (this.appendedToBody) {
const { width, height } = this.getPageSize(); const { width, height } = this.getPageSize();
this.div!.style.width = width + "px"; this.div.style.width = `${width}px`;
this.div!.style.height = height + "px"; this.div.style.height = `${height}px`;
} else { } else {
if (this.hasCssTweakOff()) { if (this.hasCssTweakOff()) {
// this case lags if scrolls or position fixed. Users should never use tweak off // this case lags if scrolls or position fixed. Users should never use tweak off
this.div!.style.width = this.parentElement!.clientWidth + "px"; this.div.style.width = `${this.parentElement.clientWidth}px`;
this.div!.style.height = this.parentElement!.clientHeight + "px"; this.div.style.height = `${this.parentElement.clientHeight}px`;
this.canvas.style.transform = `translate(${-this.overflowLeftSize}px,${-this.overflowTopSize}px)`; this.canvas.style.transform = `translate(${-this.overflowLeftSize}px,${-this.overflowTopSize}px)`;
} else { } else {
this.div!.style.width = this.parentElement!.scrollWidth + "px"; this.div.style.width = `${this.parentElement.scrollWidth}px`;
this.div!.style.height = this.parentElement!.scrollHeight + "px"; this.div.style.height = `${this.parentElement.scrollHeight}px`;
} }
} }
this.div!.style.display = ""; this.div.style.display = "";
// this.root.appendChild(this.div!); // this.root.appendChild(this.div!);
} }
private resize (width: number, height: number) { private resize (width: number, height: number) {
let canvas = this.canvas; const canvas = this.canvas;
canvas.width = Math.round(this.screenToWorldLength(width)); canvas.width = Math.round(this.screenToWorldLength(width));
canvas.height = Math.round(this.screenToWorldLength(height)); canvas.height = Math.round(this.screenToWorldLength(height));
this.renderer.context.gl.viewport(0, 0, canvas.width, canvas.height); this.renderer.context.gl.viewport(0, 0, canvas.width, canvas.height);
@ -921,13 +922,13 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
private getViewportSize (): { width: number, height: number } { private getViewportSize (): { width: number, height: number } {
if (!this.appendedToBody) { if (!this.appendedToBody) {
return { return {
width: this.parentElement!.clientWidth, width: this.parentElement.clientWidth,
height: this.parentElement!.clientHeight, height: this.parentElement.clientHeight,
} }
} }
let width = window.innerWidth; const width = window.innerWidth;
let height = window.innerHeight; const height = window.innerHeight;
const dpr = this.getDevicePixelRatio(); const dpr = this.getDevicePixelRatio();
if (dpr !== this.lastDPR) { if (dpr !== this.lastDPR) {
@ -993,10 +994,10 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
// Ideally this should be the only appendedToBody case (no-auto-parent-transform not enabled or at least an ancestor has transform) // Ideally this should be the only appendedToBody case (no-auto-parent-transform not enabled or at least an ancestor has transform)
// I'd like to get rid of the else case // I'd like to get rid of the else case
if (this.hasParentTransform) { if (this.hasParentTransform) {
scrollPositionX += this.parentElement!.scrollLeft; scrollPositionX += this.parentElement.scrollLeft;
scrollPositionY += this.parentElement!.scrollTop; scrollPositionY += this.parentElement.scrollTop;
} else { } else {
const { left, top } = this.parentElement!.getBoundingClientRect(); const { left, top } = this.parentElement.getBoundingClientRect();
scrollPositionX += left + window.scrollX; scrollPositionX += left + window.scrollX;
scrollPositionY += top + window.scrollY; scrollPositionY += top + window.scrollY;
@ -1027,10 +1028,10 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
let parent: HTMLElement | null = element; let parent: HTMLElement | null = element;
let zIndex: undefined | number; let zIndex: undefined | number;
do { do {
let currentZIndex = parseInt(getComputedStyle(parent).zIndex); const currentZIndex = parseInt(getComputedStyle(parent).zIndex);
// searching the shallowest z-index // searching the shallowest z-index
if (!isNaN(currentZIndex)) zIndex = currentZIndex; if (!Number.isNaN(currentZIndex)) zIndex = currentZIndex;
parent = parent.parentElement; parent = parent.parentElement;
} while (parent && parent !== document.body) } while (parent && parent !== document.body)

View File

@ -28,31 +28,32 @@
*****************************************************************************/ *****************************************************************************/
import { import {
Animation, type Animation,
AnimationState, AnimationState,
AnimationStateData, AnimationStateData,
AtlasAttachmentLoader, AtlasAttachmentLoader,
Bone, type Bone,
Disposable, type Disposable,
LoadingScreen, type LoadingScreen,
MeshAttachment, MeshAttachment,
MixBlend, MixBlend,
MixDirection, MixDirection,
NumberArrayLike, type NumberArrayLike,
Physics, Physics,
RegionAttachment, RegionAttachment,
Skeleton, Skeleton,
SkeletonBinary, SkeletonBinary,
SkeletonData, type SkeletonData,
SkeletonJson, SkeletonJson,
Skin, Skin,
Slot, type Slot,
TextureAtlas, type TextureAtlas,
type TrackEntry,
Utils, Utils,
Vector2, Vector2,
} from "@esotericsoftware/spine-webgl"; } from "@esotericsoftware/spine-webgl";
import { SpineWebComponentOverlay } from "./SpineWebComponentOverlay.js"; import { SpineWebComponentOverlay } from "./SpineWebComponentOverlay.js";
import { AttributeTypes, castValue, isBase64, Rectangle } from "./wcUtils.js"; import { type AttributeTypes, castValue, isBase64, type Rectangle } from "./wcUtils.js";
type UpdateSpineWidgetFunction = (delta: number, skeleton: Skeleton, state: AnimationState) => void; type UpdateSpineWidgetFunction = (delta: number, skeleton: Skeleton, state: AnimationState) => void;
@ -456,10 +457,10 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable
} }
private getSlotFromRef (slotRef: number | string | Slot): Slot { private getSlotFromRef (slotRef: number | string | Slot): Slot {
let slot: Slot | null; let slot: Slot | null | undefined;
if (typeof slotRef === 'number') slot = this.skeleton!.slots[slotRef]; if (typeof slotRef === 'number') slot = this.skeleton?.slots[slotRef];
else if (typeof slotRef === 'string') slot = this.skeleton!.findSlot(slotRef); else if (typeof slotRef === 'string') slot = this.skeleton?.findSlot(slotRef);
else slot = slotRef; else slot = slotRef;
if (!slot) throw new Error(`No slot found with the given slot reference: ${slotRef}`); if (!slot) throw new Error(`No slot found with the given slot reference: ${slotRef}`);
@ -686,7 +687,7 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable
// - remove appendTo that is just to avoid the user to use the overlayAssignedPromise when the widget is created using js // - remove appendTo that is just to avoid the user to use the overlayAssignedPromise when the widget is created using js
private overlayAssignedPromise: Promise<void>; private overlayAssignedPromise: Promise<void>;
static attributesDescription: Record<string, { propertyName: keyof WidgetAttributes, type: AttributeTypes, defaultValue?: any }> = { static attributesDescription: Record<string, { propertyName: keyof WidgetAttributes, type: AttributeTypes, defaultValue?: WidgetAttributes[keyof WidgetAttributes] }> = {
atlas: { propertyName: "atlasPath", type: "string" }, atlas: { propertyName: "atlasPath", type: "string" },
skeleton: { propertyName: "skeletonPath", type: "string" }, skeleton: { propertyName: "skeletonPath", type: "string" },
"raw-data": { propertyName: "rawData", type: "object" }, "raw-data": { propertyName: "rawData", type: "object" },
@ -776,7 +777,7 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable
window.removeEventListener("DOMContentLoaded", this.DOMContentLoadedCallback); window.removeEventListener("DOMContentLoaded", this.DOMContentLoadedCallback);
const index = this.overlay?.widgets.indexOf(this); const index = this.overlay?.widgets.indexOf(this);
if (index > 0) { if (index > 0) {
this.overlay!.widgets.splice(index, 1); this.overlay?.widgets.splice(index, 1);
} }
} }
@ -797,7 +798,7 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable
attributeChangedCallback (name: string, oldValue: string | null, newValue: string | null): void { attributeChangedCallback (name: string, oldValue: string | null, newValue: string | null): void {
const { type, propertyName, defaultValue } = SpineWebComponentSkeleton.attributesDescription[name]; const { type, propertyName, defaultValue } = SpineWebComponentSkeleton.attributesDescription[name];
const val = castValue(type, newValue, defaultValue); const val = castValue(type, newValue, defaultValue);
(this as any)[propertyName] = val; (this[propertyName] as WidgetAttributes[typeof propertyName]) = val as WidgetAttributes[typeof propertyName];
return; return;
} }
@ -830,12 +831,12 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable
* @param atlas the `TextureAtlas` from which to get the `TextureAtlasPage`s * @param atlas the `TextureAtlas` from which to get the `TextureAtlasPage`s
* @returns The list of loaded assets * @returns The list of loaded assets
*/ */
public async loadTexturesInPagesAttribute (): Promise<Array<any>> { public async loadTexturesInPagesAttribute (): Promise<Array<string>> {
const atlas = this.overlay.assetManager.require(this.atlasPath!) as TextureAtlas; const atlas = this.overlay.assetManager.require(this.atlasPath as string) as TextureAtlas;
const pagesIndexToLoad = this.pages ?? atlas.pages.map((_, i) => i); // if no pages provided, loads all const pagesIndexToLoad = this.pages ?? atlas.pages.map((_, i) => i); // if no pages provided, loads all
const atlasPath = this.atlasPath?.includes("/") ? this.atlasPath.substring(0, this.atlasPath.lastIndexOf("/") + 1) : ""; const atlasPath = this.atlasPath?.includes("/") ? this.atlasPath.substring(0, this.atlasPath.lastIndexOf("/") + 1) : "";
const promisePageList: Array<Promise<any>> = []; const promisePageList: Array<Promise<string>> = [];
const texturePaths = []; const texturePaths = [] as string[];
for (const index of pagesIndexToLoad) { for (const index of pagesIndexToLoad) {
const page = atlas.pages[index]; const page = atlas.pages[index];
@ -866,7 +867,7 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable
*/ */
public getHostElement (): HTMLElement { public getHostElement (): HTMLElement {
return (this.width <= 0 || this.width <= 0) && !this.getAttribute("style") && !this.getAttribute("class") return (this.width <= 0 || this.width <= 0) && !this.getAttribute("style") && !this.getAttribute("class")
? this.parentElement! ? this.parentElement as HTMLElement
: this; : this;
} }
@ -934,7 +935,7 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable
const isBinary = skeletonPath.endsWith(".skel"); const isBinary = skeletonPath.endsWith(".skel");
if (rawData) { if (rawData) {
for (let [key, value] of Object.entries(rawData)) { for (const [key, value] of Object.entries(rawData)) {
this.overlay.assetManager.setRawDataURI(key, isBase64(value) ? `data:application/octet-stream;base64,${value}` : value); this.overlay.assetManager.setRawDataURI(key, isBase64(value) ? `data:application/octet-stream;base64,${value}` : value);
} }
} }
@ -1032,7 +1033,7 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable
const cycleFn = () => { const cycleFn = () => {
const trackIndex = Number(trackIndexString); const trackIndex = Number(trackIndexString);
for (const [index, { animationName, delay, loop, mixDuration }] of animations.entries()) { for (const [index, { animationName, delay, loop, mixDuration }] of animations.entries()) {
let track; let track: TrackEntry;
if (index === 0) { if (index === 0) {
if (animationName === "#EMPTY#") { if (animationName === "#EMPTY#") {
track = state.setEmptyAnimation(trackIndex, mixDuration); track = state.setEmptyAnimation(trackIndex, mixDuration);
@ -1076,7 +1077,7 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable
} }
private render (): void { private render (): void {
let noSize = (!this.getAttribute("style") && !this.getAttribute("class")); const noSize = (!this.getAttribute("style") && !this.getAttribute("class"));
this.root.innerHTML = ` this.root.innerHTML = `
<style> <style>
:host { :host {
@ -1155,24 +1156,24 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable
} }
private checkSlotInteraction (type: PointerEventTypesInput, originalEvent?: UIEvent) { private checkSlotInteraction (type: PointerEventTypesInput, originalEvent?: UIEvent) {
for (let [slot, interactionState] of this.pointerSlotEventCallbacks) { for (const [slot, interactionState] of this.pointerSlotEventCallbacks) {
if (!slot.bone.active) continue; if (!slot.bone.active) continue;
let attachment = slot.applied.attachment; const attachment = slot.applied.attachment;
if (!(attachment instanceof RegionAttachment || attachment instanceof MeshAttachment)) continue; if (!(attachment instanceof RegionAttachment || attachment instanceof MeshAttachment)) continue;
const { slotFunction, inside } = interactionState const { slotFunction, inside } = interactionState
let vertices = this.verticesTemp; const vertices = this.verticesTemp;
let hullLength = 8; let hullLength = 8;
// we could probably cache the vertices from rendering if interaction with this slot is enabled // we could probably cache the vertices from rendering if interaction with this slot is enabled
if (attachment instanceof RegionAttachment) { if (attachment instanceof RegionAttachment) {
let regionAttachment = <RegionAttachment>attachment; const regionAttachment = <RegionAttachment>attachment;
regionAttachment.computeWorldVertices(slot, vertices, 0, 2); regionAttachment.computeWorldVertices(slot, vertices, 0, 2);
} else if (attachment instanceof MeshAttachment) { } else if (attachment instanceof MeshAttachment) {
let mesh = <MeshAttachment>attachment; const mesh = <MeshAttachment>attachment;
mesh.computeWorldVertices(this.skeleton!, slot, 0, mesh.worldVerticesLength, vertices, 0, 2); mesh.computeWorldVertices(this.skeleton as Skeleton, slot, 0, mesh.worldVerticesLength, vertices, 0, 2);
hullLength = mesh.hullLength; hullLength = mesh.hullLength;
} }
@ -1274,7 +1275,7 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable
if (!skeleton) return { x: 0, y: 0, width: 0, height: 0 }; if (!skeleton) return { x: 0, y: 0, width: 0, height: 0 };
skeleton.setupPose(); skeleton.setupPose();
let offset = new Vector2(), size = new Vector2(); const offset = new Vector2(), size = new Vector2();
const tempArray = new Array<number>(2); const tempArray = new Array<number>(2);
if (!animation) { if (!animation) {
skeleton.updateWorldTransform(Physics.update); skeleton.updateWorldTransform(Physics.update);
@ -1294,8 +1295,8 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable
skeleton.updateWorldTransform(Physics.update); skeleton.updateWorldTransform(Physics.update);
skeleton.getBounds(offset, size, tempArray, renderer.skeletonRenderer.getSkeletonClipping()); skeleton.getBounds(offset, size, tempArray, renderer.skeletonRenderer.getSkeletonClipping());
if (!isNaN(offset.x) && !isNaN(offset.y) && !isNaN(size.x) && !isNaN(size.y) && if (!Number.isNaN(offset.x) && !Number.isNaN(offset.y) && !Number.isNaN(size.x) && !Number.isNaN(size.y) &&
!isNaN(minX) && !isNaN(minY) && !isNaN(maxX) && !isNaN(maxY)) { !Number.isNaN(minX) && !Number.isNaN(minY) && !Number.isNaN(maxX) && !Number.isNaN(maxY)) {
minX = Math.min(offset.x, minX); minX = Math.min(offset.x, minX);
maxX = Math.max(offset.x + size.x, maxX); maxX = Math.max(offset.x + size.x, maxX);
minY = Math.min(offset.y, minY); minY = Math.min(offset.y, minY);
@ -1345,7 +1346,7 @@ export function createSkeleton (parameters: WidgetAttributes): SpineWebComponent
Object.entries(SpineWebComponentSkeleton.attributesDescription).forEach(entry => { Object.entries(SpineWebComponentSkeleton.attributesDescription).forEach(entry => {
const [key, { propertyName }] = entry; const [key, { propertyName }] = entry;
const value = parameters[propertyName]; const value = parameters[propertyName];
if (value) widget.setAttribute(key, value as any); if (value) widget.setAttribute(key, value as string);
}); });
return widget; return widget;

View File

@ -1,4 +1,4 @@
export * from './SpineWebComponentSkeleton.js';
export * from './SpineWebComponentOverlay.js';
export * from "@esotericsoftware/spine-core"; export * from "@esotericsoftware/spine-core";
export * from "@esotericsoftware/spine-webgl"; export * from "@esotericsoftware/spine-webgl";
export * from './SpineWebComponentOverlay.js';
export * from './SpineWebComponentSkeleton.js';

View File

@ -28,8 +28,8 @@
*****************************************************************************/ *****************************************************************************/
import { AssetManagerBase, Downloader } from "@esotericsoftware/spine-core" import { AssetManagerBase, Downloader } from "@esotericsoftware/spine-core"
import { ManagedWebGLRenderingContext } from "./WebGL.js";
import { GLTexture } from "./GLTexture.js"; import { GLTexture } from "./GLTexture.js";
import type { ManagedWebGLRenderingContext } from "./WebGL.js";
export class AssetManager extends AssetManagerBase { export class AssetManager extends AssetManagerBase {
constructor (context: ManagedWebGLRenderingContext | WebGLRenderingContext, pathPrefix: string = "", downloader: Downloader = new Downloader()) { constructor (context: ManagedWebGLRenderingContext | WebGLRenderingContext, pathPrefix: string = "", downloader: Downloader = new Downloader()) {

View File

@ -51,11 +51,11 @@ export class OrthoCamera {
} }
update () { update () {
let projection = this.projection; const projection = this.projection;
let view = this.view; const view = this.view;
let projectionView = this.projectionView; const projectionView = this.projectionView;
let inverseProjectionView = this.inverseProjectionView; const inverseProjectionView = this.inverseProjectionView;
let zoom = this.zoom, viewportWidth = this.viewportWidth, viewportHeight = this.viewportHeight; const zoom = this.zoom, viewportWidth = this.viewportWidth, viewportHeight = this.viewportHeight;
projection.ortho(zoom * (-viewportWidth / 2), zoom * (viewportWidth / 2), projection.ortho(zoom * (-viewportWidth / 2), zoom * (viewportWidth / 2),
zoom * (-viewportHeight / 2), zoom * (viewportHeight / 2), zoom * (-viewportHeight / 2), zoom * (viewportHeight / 2),
this.near, this.far); this.near, this.far);
@ -66,7 +66,7 @@ export class OrthoCamera {
} }
screenToWorld (screenCoords: Vector3, screenWidth: number, screenHeight: number) { screenToWorld (screenCoords: Vector3, screenWidth: number, screenHeight: number) {
let x = screenCoords.x, y = screenHeight - screenCoords.y - 1; const x = screenCoords.x, y = screenHeight - screenCoords.y - 1;
screenCoords.x = (2 * x) / screenWidth - 1; screenCoords.x = (2 * x) / screenWidth - 1;
screenCoords.y = (2 * y) / screenHeight - 1; screenCoords.y = (2 * y) / screenHeight - 1;
screenCoords.z = (2 * screenCoords.z) - 1; screenCoords.z = (2 * screenCoords.z) - 1;

View File

@ -27,13 +27,13 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import type { OrthoCamera } from "./Camera.js";
import { Input } from "./Input.js"; import { Input } from "./Input.js";
import { OrthoCamera } from "./Camera.js";
import { Vector3 } from "./Vector3.js"; import { Vector3 } from "./Vector3.js";
export class CameraController { export class CameraController {
constructor (public canvas: HTMLElement, public camera: OrthoCamera) { constructor (public canvas: HTMLElement, public camera: OrthoCamera) {
let cameraX = 0, cameraY = 0, cameraZoom = 0; let cameraX = 0, cameraY = 0;
let mouseX = 0, mouseY = 0; let mouseX = 0, mouseY = 0;
let lastX = 0, lastY = 0; let lastX = 0, lastY = 0;
let initialZoom = 0; let initialZoom = 0;
@ -47,39 +47,39 @@ export class CameraController {
initialZoom = camera.zoom; initialZoom = camera.zoom;
}, },
dragged: (x: number, y: number) => { dragged: (x: number, y: number) => {
let deltaX = x - mouseX; const deltaX = x - mouseX;
let deltaY = y - mouseY; const deltaY = y - mouseY;
let originWorld = camera.screenToWorld(new Vector3(0, 0), canvas.clientWidth, canvas.clientHeight); const originWorld = camera.screenToWorld(new Vector3(0, 0), canvas.clientWidth, canvas.clientHeight);
let deltaWorld = camera.screenToWorld(new Vector3(deltaX, deltaY), canvas.clientWidth, canvas.clientHeight).sub(originWorld); const deltaWorld = camera.screenToWorld(new Vector3(deltaX, deltaY), canvas.clientWidth, canvas.clientHeight).sub(originWorld);
camera.position.set(cameraX - deltaWorld.x, cameraY - deltaWorld.y, 0); camera.position.set(cameraX - deltaWorld.x, cameraY - deltaWorld.y, 0);
camera.update(); camera.update();
lastX = x; lastX = x;
lastY = y; lastY = y;
}, },
wheel: (delta: number) => { wheel: (delta: number) => {
let zoomAmount = delta / 200 * camera.zoom; const zoomAmount = delta / 200 * camera.zoom;
let newZoom = camera.zoom + zoomAmount; const newZoom = camera.zoom + zoomAmount;
if (newZoom > 0) { if (newZoom > 0) {
let x = 0, y = 0; let x = 0, y = 0;
if (delta < 0) { if (delta < 0) {
x = lastX; y = lastY; x = lastX; y = lastY;
} else { } else {
let viewCenter = new Vector3(canvas.clientWidth / 2 + 15, canvas.clientHeight / 2); const viewCenter = new Vector3(canvas.clientWidth / 2 + 15, canvas.clientHeight / 2);
let mouseToCenterX = lastX - viewCenter.x; const mouseToCenterX = lastX - viewCenter.x;
let mouseToCenterY = canvas.clientHeight - 1 - lastY - viewCenter.y; const mouseToCenterY = canvas.clientHeight - 1 - lastY - viewCenter.y;
x = viewCenter.x - mouseToCenterX; x = viewCenter.x - mouseToCenterX;
y = canvas.clientHeight - 1 - viewCenter.y + mouseToCenterY; y = canvas.clientHeight - 1 - viewCenter.y + mouseToCenterY;
} }
let oldDistance = camera.screenToWorld(new Vector3(x, y), canvas.clientWidth, canvas.clientHeight); const oldDistance = camera.screenToWorld(new Vector3(x, y), canvas.clientWidth, canvas.clientHeight);
camera.zoom = newZoom; camera.zoom = newZoom;
camera.update(); camera.update();
let newDistance = camera.screenToWorld(new Vector3(x, y), canvas.clientWidth, canvas.clientHeight); const newDistance = camera.screenToWorld(new Vector3(x, y), canvas.clientWidth, canvas.clientHeight);
camera.position.add(oldDistance.sub(newDistance)); camera.position.add(oldDistance.sub(newDistance));
camera.update(); camera.update();
} }
}, },
zoom: (initialDistance, distance) => { zoom: (initialDistance, distance) => {
let newZoom = initialDistance / distance; const newZoom = initialDistance / distance;
camera.zoom = initialZoom * newZoom; camera.zoom = initialZoom * newZoom;
}, },
up: (x: number, y: number) => { up: (x: number, y: number) => {

View File

@ -27,7 +27,7 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { Texture, Disposable, Restorable, TextureFilter, TextureWrap } from "@esotericsoftware/spine-core"; import { type Disposable, type Restorable, Texture, TextureFilter, type TextureWrap } from "@esotericsoftware/spine-core";
import { ManagedWebGLRenderingContext } from "./WebGL.js"; import { ManagedWebGLRenderingContext } from "./WebGL.js";
export class GLTexture extends Texture implements Disposable, Restorable { export class GLTexture extends Texture implements Disposable, Restorable {
@ -47,7 +47,7 @@ export class GLTexture extends Texture implements Disposable, Restorable {
} }
setFilters (minFilter: TextureFilter, magFilter: TextureFilter) { setFilters (minFilter: TextureFilter, magFilter: TextureFilter) {
let gl = this.context.gl; const gl = this.context.gl;
this.bind(); this.bind();
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, minFilter); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, minFilter);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, GLTexture.validateMagFilter(magFilter)); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, GLTexture.validateMagFilter(magFilter));
@ -80,14 +80,14 @@ export class GLTexture extends Texture implements Disposable, Restorable {
} }
setWraps (uWrap: TextureWrap, vWrap: TextureWrap) { setWraps (uWrap: TextureWrap, vWrap: TextureWrap) {
let gl = this.context.gl; const gl = this.context.gl;
this.bind(); this.bind();
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, uWrap); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, uWrap);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, vWrap); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, vWrap);
} }
update (useMipMaps: boolean) { update (useMipMaps: boolean) {
let gl = this.context.gl; const gl = this.context.gl;
if (!this.texture) this.texture = this.context.gl.createTexture(); if (!this.texture) this.texture = this.context.gl.createTexture();
this.bind(); this.bind();
if (GLTexture.DISABLE_UNPACK_PREMULTIPLIED_ALPHA_WEBGL) gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false); if (GLTexture.DISABLE_UNPACK_PREMULTIPLIED_ALPHA_WEBGL) gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
@ -105,21 +105,21 @@ export class GLTexture extends Texture implements Disposable, Restorable {
} }
bind (unit: number = 0) { bind (unit: number = 0) {
let gl = this.context.gl; const gl = this.context.gl;
this.boundUnit = unit; this.boundUnit = unit;
gl.activeTexture(gl.TEXTURE0 + unit); gl.activeTexture(gl.TEXTURE0 + unit);
gl.bindTexture(gl.TEXTURE_2D, this.texture); gl.bindTexture(gl.TEXTURE_2D, this.texture);
} }
unbind () { unbind () {
let gl = this.context.gl; const gl = this.context.gl;
gl.activeTexture(gl.TEXTURE0 + this.boundUnit); gl.activeTexture(gl.TEXTURE0 + this.boundUnit);
gl.bindTexture(gl.TEXTURE_2D, null); gl.bindTexture(gl.TEXTURE_2D, null);
} }
dispose () { dispose () {
this.context.removeRestorable(this); this.context.removeRestorable(this);
let gl = this.context.gl; const gl = this.context.gl;
gl.deleteTexture(this.texture); gl.deleteTexture(this.texture);
} }
} }

View File

@ -27,7 +27,7 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { Disposable } from "./index.js" import type { Disposable } from "./index.js"
export class Input implements Disposable { export class Input implements Disposable {
element: HTMLElement; element: HTMLElement;
mouseX = 0; mouseX = 0;
@ -36,7 +36,7 @@ export class Input implements Disposable {
touch0: Touch | null = null; touch0: Touch | null = null;
touch1: Touch | null = null; touch1: Touch | null = null;
initialPinchDistance = 0; initialPinchDistance = 0;
private listeners = new Array<InputListener>(); private listeners = [] as InputListener[];
private autoPreventDefault: boolean; private autoPreventDefault: boolean;
// this is needed because browsers sends mousedown-mousemove-mousesup after a touch sequence, unless touch end preventDefault // this is needed because browsers sends mousedown-mousemove-mousesup after a touch sequence, unless touch end preventDefault
@ -62,7 +62,7 @@ export class Input implements Disposable {
private setupCallbacks (element: HTMLElement) { private setupCallbacks (element: HTMLElement) {
const mouseDown = (ev: UIEvent) => { const mouseDown = (ev: UIEvent) => {
if (ev instanceof MouseEvent && !this.isTouch) { if (ev instanceof MouseEvent && !this.isTouch) {
let rect = element.getBoundingClientRect(); const rect = element.getBoundingClientRect();
this.mouseX = ev.clientX - rect.left; this.mouseX = ev.clientX - rect.left;
this.mouseY = ev.clientY - rect.top; this.mouseY = ev.clientY - rect.top;
this.buttonDown = true; this.buttonDown = true;
@ -72,7 +72,7 @@ export class Input implements Disposable {
const mouseMove = (ev: UIEvent) => { const mouseMove = (ev: UIEvent) => {
if (ev instanceof MouseEvent && !this.isTouch) { if (ev instanceof MouseEvent && !this.isTouch) {
let rect = element.getBoundingClientRect(); const rect = element.getBoundingClientRect();
this.mouseX = ev.clientX - rect.left; this.mouseX = ev.clientX - rect.left;
this.mouseY = ev.clientY - rect.top; this.mouseY = ev.clientY - rect.top;
@ -88,7 +88,7 @@ export class Input implements Disposable {
const mouseUp = (ev: UIEvent) => { const mouseUp = (ev: UIEvent) => {
if (ev instanceof MouseEvent && !this.isTouch) { if (ev instanceof MouseEvent && !this.isTouch) {
let rect = element.getBoundingClientRect(); const rect = element.getBoundingClientRect();
this.mouseX = ev.clientX - rect.left;; this.mouseX = ev.clientX - rect.left;;
this.mouseY = ev.clientY - rect.top; this.mouseY = ev.clientY - rect.top;
this.buttonDown = false; this.buttonDown = false;
@ -99,21 +99,21 @@ export class Input implements Disposable {
const mouseWheel = (ev: WheelEvent) => { const mouseWheel = (ev: WheelEvent) => {
if (this.autoPreventDefault) ev.preventDefault(); if (this.autoPreventDefault) ev.preventDefault();
let deltaY = ev.deltaY; let deltaY = ev.deltaY;
if (ev.deltaMode == WheelEvent.DOM_DELTA_LINE) deltaY *= 8; if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) deltaY *= 8;
if (ev.deltaMode == WheelEvent.DOM_DELTA_PAGE) deltaY *= 24; if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) deltaY *= 24;
this.listeners.map((listener) => { if (listener.wheel) listener.wheel(ev.deltaY, ev); }); this.listeners.map((listener) => { if (listener.wheel) listener.wheel(deltaY, ev); });
}; };
const touchStart = (ev: TouchEvent) => { const touchStart = (ev: TouchEvent) => {
this.isTouch = true; this.isTouch = true;
if (!this.touch0 || !this.touch1) { if (!this.touch0 || !this.touch1) {
var touches = ev.changedTouches; const touches = ev.changedTouches;
let nativeTouch = touches.item(0); const nativeTouch = touches.item(0);
if (!nativeTouch) return; if (!nativeTouch) return;
let rect = element.getBoundingClientRect(); const rect = element.getBoundingClientRect();
let x = nativeTouch.clientX - rect.left; const x = nativeTouch.clientX - rect.left;
let y = nativeTouch.clientY - rect.top; const y = nativeTouch.clientY - rect.top;
let touch = new Touch(nativeTouch.identifier, x, y); const touch = new Touch(nativeTouch.identifier, x, y);
this.mouseX = x; this.mouseX = x;
this.mouseY = y; this.mouseY = y;
this.buttonDown = true; this.buttonDown = true;
@ -123,8 +123,8 @@ export class Input implements Disposable {
this.listeners.map((listener) => { if (listener.down) listener.down(touch.x, touch.y, ev) }) this.listeners.map((listener) => { if (listener.down) listener.down(touch.x, touch.y, ev) })
} else if (!this.touch1) { } else if (!this.touch1) {
this.touch1 = touch; this.touch1 = touch;
let dx = this.touch1.x - this.touch0.x; const dx = this.touch1.x - this.touch0.x;
let dy = this.touch1.x - this.touch0.x; const dy = this.touch1.x - this.touch0.x;
this.initialPinchDistance = Math.sqrt(dx * dx + dy * dy); this.initialPinchDistance = Math.sqrt(dx * dx + dy * dy);
this.listeners.map((listener) => { if (listener.zoom) listener.zoom(this.initialPinchDistance, this.initialPinchDistance, ev) }); this.listeners.map((listener) => { if (listener.zoom) listener.zoom(this.initialPinchDistance, this.initialPinchDistance, ev) });
} }
@ -135,12 +135,12 @@ export class Input implements Disposable {
const touchMove = (ev: TouchEvent) => { const touchMove = (ev: TouchEvent) => {
this.isTouch = true; this.isTouch = true;
if (this.touch0) { if (this.touch0) {
var touches = ev.changedTouches; const touches = ev.changedTouches;
let rect = element.getBoundingClientRect(); const rect = element.getBoundingClientRect();
for (var i = 0; i < touches.length; i++) { for (let i = 0; i < touches.length; i++) {
var nativeTouch = touches[i]; const nativeTouch = touches[i];
let x = nativeTouch.clientX - rect.left; const x = nativeTouch.clientX - rect.left;
let y = nativeTouch.clientY - rect.top; const y = nativeTouch.clientY - rect.top;
if (this.touch0.identifier === nativeTouch.identifier) { if (this.touch0.identifier === nativeTouch.identifier) {
this.touch0.x = this.mouseX = x; this.touch0.x = this.mouseX = x;
@ -153,9 +153,9 @@ export class Input implements Disposable {
} }
} }
if (this.touch0 && this.touch1) { if (this.touch0 && this.touch1) {
let dx = this.touch1.x - this.touch0.x; const dx = this.touch1.x - this.touch0.x;
let dy = this.touch1.x - this.touch0.x; const dy = this.touch1.x - this.touch0.x;
let distance = Math.sqrt(dx * dx + dy * dy); const distance = Math.sqrt(dx * dx + dy * dy);
this.listeners.map((listener) => { if (listener.zoom) listener.zoom(this.initialPinchDistance, distance, ev) }); this.listeners.map((listener) => { if (listener.zoom) listener.zoom(this.initialPinchDistance, distance, ev) });
} }
} }
@ -164,16 +164,17 @@ export class Input implements Disposable {
const touchEnd = (ev: TouchEvent) => { const touchEnd = (ev: TouchEvent) => {
this.isTouch = true; this.isTouch = true;
if (this.touch0) { const touch0 = this.touch0 as Touch;
var touches = ev.changedTouches; if (touch0) {
let rect = element.getBoundingClientRect(); const touches = ev.changedTouches;
const rect = element.getBoundingClientRect();
for (var i = 0; i < touches.length; i++) { for (let i = 0; i < touches.length; i++) {
var nativeTouch = touches[i]; const nativeTouch = touches[i];
let x = nativeTouch.clientX - rect.left; const x = nativeTouch.clientX - rect.left;
let y = nativeTouch.clientY - rect.top; const y = nativeTouch.clientY - rect.top;
if (this.touch0.identifier === nativeTouch.identifier) { if (touch0.identifier === nativeTouch.identifier) {
this.touch0 = null; this.touch0 = null;
this.mouseX = x; this.mouseX = x;
this.mouseY = y; this.mouseY = y;
@ -183,16 +184,16 @@ export class Input implements Disposable {
this.buttonDown = false; this.buttonDown = false;
break; break;
} else { } else {
this.touch0 = this.touch1; const touch0 = this.touch0 = this.touch1;
this.touch1 = null; this.touch1 = null;
this.mouseX = this.touch0.x; this.mouseX = touch0.x;
this.mouseX = this.touch0.x; this.mouseY = touch0.y;
this.buttonDown = true; this.buttonDown = true;
this.listeners.map((listener) => { if (listener.down) listener.down(this.touch0!.x, this.touch0!.y, ev) }); this.listeners.map((listener) => { if (listener.down) listener.down(touch0.x, touch0.y, ev) });
} }
} }
if (this.touch1 && this.touch1.identifier) { if (this.touch1?.identifier) {
this.touch1 = null; this.touch1 = null;
} }
} }
@ -238,7 +239,7 @@ export class Input implements Disposable {
} }
removeListener (listener: InputListener) { removeListener (listener: InputListener) {
let idx = this.listeners.indexOf(listener); const idx = this.listeners.indexOf(listener);
if (idx > -1) { if (idx > -1) {
this.listeners.splice(idx, 1); this.listeners.splice(idx, 1);
} }

File diff suppressed because one or more lines are too long

View File

@ -56,7 +56,7 @@ export class Matrix4 {
private static tmpMatrix = new Matrix4(); private static tmpMatrix = new Matrix4();
constructor () { constructor () {
let v = this.values; const v = this.values;
v[M00] = 1; v[M00] = 1;
v[M11] = 1; v[M11] = 1;
v[M22] = 1; v[M22] = 1;
@ -69,8 +69,8 @@ export class Matrix4 {
} }
transpose (): Matrix4 { transpose (): Matrix4 {
let t = this.temp; const t = this.temp;
let v = this.values; const v = this.values;
t[M00] = v[M00]; t[M00] = v[M00];
t[M01] = v[M10]; t[M01] = v[M10];
t[M02] = v[M20]; t[M02] = v[M20];
@ -91,7 +91,7 @@ export class Matrix4 {
} }
identity (): Matrix4 { identity (): Matrix4 {
let v = this.values; const v = this.values;
v[M00] = 1; v[M00] = 1;
v[M01] = 0; v[M01] = 0;
v[M02] = 0; v[M02] = 0;
@ -112,9 +112,9 @@ export class Matrix4 {
} }
invert (): Matrix4 { invert (): Matrix4 {
let v = this.values; const v = this.values;
let t = this.temp; const t = this.temp;
let l_det = v[M30] * v[M21] * v[M12] * v[M03] - v[M20] * v[M31] * v[M12] * v[M03] - v[M30] * v[M11] * v[M22] * v[M03] const l_det = v[M30] * v[M21] * v[M12] * v[M03] - v[M20] * v[M31] * v[M12] * v[M03] - v[M30] * v[M11] * v[M22] * v[M03]
+ v[M10] * v[M31] * v[M22] * v[M03] + v[M20] * v[M11] * v[M32] * v[M03] - v[M10] * v[M21] * v[M32] * v[M03] + v[M10] * v[M31] * v[M22] * v[M03] + v[M20] * v[M11] * v[M32] * v[M03] - v[M10] * v[M21] * v[M32] * v[M03]
- v[M30] * v[M21] * v[M02] * v[M13] + v[M20] * v[M31] * v[M02] * v[M13] + v[M30] * v[M01] * v[M22] * v[M13] - v[M30] * v[M21] * v[M02] * v[M13] + v[M20] * v[M31] * v[M02] * v[M13] + v[M30] * v[M01] * v[M22] * v[M13]
- v[M00] * v[M31] * v[M22] * v[M13] - v[M20] * v[M01] * v[M32] * v[M13] + v[M00] * v[M21] * v[M32] * v[M13] - v[M00] * v[M31] * v[M22] * v[M13] - v[M20] * v[M01] * v[M32] * v[M13] + v[M00] * v[M21] * v[M32] * v[M13]
@ -122,8 +122,8 @@ export class Matrix4 {
+ v[M00] * v[M31] * v[M12] * v[M23] + v[M10] * v[M01] * v[M32] * v[M23] - v[M00] * v[M11] * v[M32] * v[M23] + v[M00] * v[M31] * v[M12] * v[M23] + v[M10] * v[M01] * v[M32] * v[M23] - v[M00] * v[M11] * v[M32] * v[M23]
- v[M20] * v[M11] * v[M02] * v[M33] + v[M10] * v[M21] * v[M02] * v[M33] + v[M20] * v[M01] * v[M12] * v[M33] - v[M20] * v[M11] * v[M02] * v[M33] + v[M10] * v[M21] * v[M02] * v[M33] + v[M20] * v[M01] * v[M12] * v[M33]
- v[M00] * v[M21] * v[M12] * v[M33] - v[M10] * v[M01] * v[M22] * v[M33] + v[M00] * v[M11] * v[M22] * v[M33]; - v[M00] * v[M21] * v[M12] * v[M33] - v[M10] * v[M01] * v[M22] * v[M33] + v[M00] * v[M11] * v[M22] * v[M33];
if (l_det == 0) throw new Error("non-invertible matrix"); if (l_det === 0) throw new Error("non-invertible matrix");
let inv_det = 1.0 / l_det; const inv_det = 1.0 / l_det;
t[M00] = v[M12] * v[M23] * v[M31] - v[M13] * v[M22] * v[M31] + v[M13] * v[M21] * v[M32] t[M00] = v[M12] * v[M23] * v[M31] - v[M13] * v[M22] * v[M31] + v[M13] * v[M21] * v[M32]
- v[M11] * v[M23] * v[M32] - v[M12] * v[M21] * v[M33] + v[M11] * v[M22] * v[M33]; - v[M11] * v[M23] * v[M32] - v[M12] * v[M21] * v[M33] + v[M11] * v[M22] * v[M33];
t[M01] = v[M03] * v[M22] * v[M31] - v[M02] * v[M23] * v[M31] - v[M03] * v[M21] * v[M32] t[M01] = v[M03] * v[M22] * v[M31] - v[M02] * v[M23] * v[M31] - v[M03] * v[M21] * v[M32]
@ -176,7 +176,7 @@ export class Matrix4 {
} }
determinant (): number { determinant (): number {
let v = this.values; const v = this.values;
return v[M30] * v[M21] * v[M12] * v[M03] - v[M20] * v[M31] * v[M12] * v[M03] - v[M30] * v[M11] * v[M22] * v[M03] return v[M30] * v[M21] * v[M12] * v[M03] - v[M20] * v[M31] * v[M12] * v[M03] - v[M30] * v[M11] * v[M22] * v[M03]
+ v[M10] * v[M31] * v[M22] * v[M03] + v[M20] * v[M11] * v[M32] * v[M03] - v[M10] * v[M21] * v[M32] * v[M03] + v[M10] * v[M31] * v[M22] * v[M03] + v[M20] * v[M11] * v[M32] * v[M03] - v[M10] * v[M21] * v[M32] * v[M03]
- v[M30] * v[M21] * v[M02] * v[M13] + v[M20] * v[M31] * v[M02] * v[M13] + v[M30] * v[M01] * v[M22] * v[M13] - v[M30] * v[M21] * v[M02] * v[M13] + v[M20] * v[M31] * v[M02] * v[M13] + v[M30] * v[M01] * v[M22] * v[M13]
@ -188,7 +188,7 @@ export class Matrix4 {
} }
translate (x: number, y: number, z: number): Matrix4 { translate (x: number, y: number, z: number): Matrix4 {
let v = this.values; const v = this.values;
v[M03] += x; v[M03] += x;
v[M13] += y; v[M13] += y;
v[M23] += z; v[M23] += z;
@ -201,10 +201,10 @@ export class Matrix4 {
projection (near: number, far: number, fovy: number, aspectRatio: number): Matrix4 { projection (near: number, far: number, fovy: number, aspectRatio: number): Matrix4 {
this.identity(); this.identity();
let l_fd = (1.0 / Math.tan((fovy * (Math.PI / 180)) / 2.0)); const l_fd = (1.0 / Math.tan((fovy * (Math.PI / 180)) / 2.0));
let l_a1 = (far + near) / (near - far); const l_a1 = (far + near) / (near - far);
let l_a2 = (2 * far * near) / (near - far); const l_a2 = (2 * far * near) / (near - far);
let v = this.values; const v = this.values;
v[M00] = l_fd / aspectRatio; v[M00] = l_fd / aspectRatio;
v[M10] = 0; v[M10] = 0;
v[M20] = 0; v[M20] = 0;
@ -230,15 +230,15 @@ export class Matrix4 {
ortho (left: number, right: number, bottom: number, top: number, near: number, far: number): Matrix4 { ortho (left: number, right: number, bottom: number, top: number, near: number, far: number): Matrix4 {
this.identity(); this.identity();
let x_orth = 2 / (right - left); const x_orth = 2 / (right - left);
let y_orth = 2 / (top - bottom); const y_orth = 2 / (top - bottom);
let z_orth = -2 / (far - near); const z_orth = -2 / (far - near);
let tx = -(right + left) / (right - left); const tx = -(right + left) / (right - left);
let ty = -(top + bottom) / (top - bottom); const ty = -(top + bottom) / (top - bottom);
let tz = -(far + near) / (far - near); const tz = -(far + near) / (far - near);
let v = this.values; const v = this.values;
v[M00] = x_orth; v[M00] = x_orth;
v[M10] = 0; v[M10] = 0;
v[M20] = 0; v[M20] = 0;
@ -259,9 +259,9 @@ export class Matrix4 {
} }
multiply (matrix: Matrix4): Matrix4 { multiply (matrix: Matrix4): Matrix4 {
let t = this.temp; const t = this.temp;
let v = this.values; const v = this.values;
let m = matrix.values; const m = matrix.values;
t[M00] = v[M00] * m[M00] + v[M01] * m[M10] + v[M02] * m[M20] + v[M03] * m[M30]; t[M00] = v[M00] * m[M00] + v[M01] * m[M10] + v[M02] * m[M20] + v[M03] * m[M30];
t[M01] = v[M00] * m[M01] + v[M01] * m[M11] + v[M02] * m[M21] + v[M03] * m[M31]; t[M01] = v[M00] * m[M01] + v[M01] * m[M11] + v[M02] * m[M21] + v[M03] * m[M31];
t[M02] = v[M00] * m[M02] + v[M01] * m[M12] + v[M02] * m[M22] + v[M03] * m[M32]; t[M02] = v[M00] * m[M02] + v[M01] * m[M12] + v[M02] * m[M22] + v[M03] * m[M32];
@ -282,9 +282,9 @@ export class Matrix4 {
} }
multiplyLeft (matrix: Matrix4): Matrix4 { multiplyLeft (matrix: Matrix4): Matrix4 {
let t = this.temp; const t = this.temp;
let v = this.values; const v = this.values;
let m = matrix.values; const m = matrix.values;
t[M00] = m[M00] * v[M00] + m[M01] * v[M10] + m[M02] * v[M20] + m[M03] * v[M30]; t[M00] = m[M00] * v[M00] + m[M01] * v[M10] + m[M02] * v[M20] + m[M03] * v[M30];
t[M01] = m[M00] * v[M01] + m[M01] * v[M11] + m[M02] * v[M21] + m[M03] * v[M31]; t[M01] = m[M00] * v[M01] + m[M01] * v[M11] + m[M02] * v[M21] + m[M03] * v[M31];
t[M02] = m[M00] * v[M02] + m[M01] * v[M12] + m[M02] * v[M22] + m[M03] * v[M32]; t[M02] = m[M00] * v[M02] + m[M01] * v[M12] + m[M02] * v[M22] + m[M03] * v[M32];
@ -305,13 +305,13 @@ export class Matrix4 {
} }
lookAt (position: Vector3, direction: Vector3, up: Vector3) { lookAt (position: Vector3, direction: Vector3, up: Vector3) {
let xAxis = Matrix4.xAxis, yAxis = Matrix4.yAxis, zAxis = Matrix4.zAxis; const xAxis = Matrix4.xAxis, yAxis = Matrix4.yAxis, zAxis = Matrix4.zAxis;
zAxis.setFrom(direction).normalize(); zAxis.setFrom(direction).normalize();
xAxis.setFrom(direction).normalize(); xAxis.setFrom(direction).normalize();
xAxis.cross(up).normalize(); xAxis.cross(up).normalize();
yAxis.setFrom(xAxis).cross(zAxis).normalize(); yAxis.setFrom(xAxis).cross(zAxis).normalize();
this.identity(); this.identity();
let val = this.values; const val = this.values;
val[M00] = xAxis.x; val[M00] = xAxis.x;
val[M01] = xAxis.y; val[M01] = xAxis.y;
val[M02] = xAxis.z; val[M02] = xAxis.z;

View File

@ -27,7 +27,7 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { Disposable, Restorable } from "@esotericsoftware/spine-core"; import type { Disposable, Restorable } from "@esotericsoftware/spine-core";
import { Shader } from "./Shader.js"; import { Shader } from "./Shader.js";
import { ManagedWebGLRenderingContext } from "./WebGL.js"; import { ManagedWebGLRenderingContext } from "./WebGL.js";
@ -64,8 +64,8 @@ export class Mesh implements Disposable, Restorable {
getVertexSizeInFloats (): number { getVertexSizeInFloats (): number {
let size = 0; let size = 0;
for (var i = 0; i < this.attributes.length; i++) { for (let i = 0; i < this.attributes.length; i++) {
let attribute = this.attributes[i]; const attribute = this.attributes[i];
size += attribute.numElements; size += attribute.numElements;
} }
return size; return size;
@ -84,14 +84,14 @@ export class Mesh implements Disposable, Restorable {
setVertices (vertices: Array<number>) { setVertices (vertices: Array<number>) {
this.dirtyVertices = true; this.dirtyVertices = true;
if (vertices.length > this.vertices.length) throw Error("Mesh can't store more than " + this.maxVertices() + " vertices"); if (vertices.length > this.vertices.length) throw Error(`Mesh can't store more than ${this.maxVertices()} vertices`);
this.vertices.set(vertices, 0); this.vertices.set(vertices, 0);
this.verticesLength = vertices.length; this.verticesLength = vertices.length;
} }
setIndices (indices: Array<number>) { setIndices (indices: Array<number>) {
this.dirtyIndices = true; this.dirtyIndices = true;
if (indices.length > this.indices.length) throw Error("Mesh can't store more than " + this.maxIndices() + " indices"); if (indices.length > this.indices.length) throw Error(`Mesh can't store more than ${this.maxIndices()} indices`);
this.indices.set(indices, 0); this.indices.set(indices, 0);
this.indicesLength = indices.length; this.indicesLength = indices.length;
} }
@ -101,7 +101,7 @@ export class Mesh implements Disposable, Restorable {
} }
drawWithOffset (shader: Shader, primitiveType: number, offset: number, count: number) { drawWithOffset (shader: Shader, primitiveType: number, offset: number, count: number) {
let gl = this.context.gl; const gl = this.context.gl;
if (this.dirtyVertices || this.dirtyIndices) this.update(); if (this.dirtyVertices || this.dirtyIndices) this.update();
this.bind(shader); this.bind(shader);
if (this.indicesLength > 0) { if (this.indicesLength > 0) {
@ -113,12 +113,12 @@ export class Mesh implements Disposable, Restorable {
} }
bind (shader: Shader) { bind (shader: Shader) {
let gl = this.context.gl; const gl = this.context.gl;
gl.bindBuffer(gl.ARRAY_BUFFER, this.verticesBuffer); gl.bindBuffer(gl.ARRAY_BUFFER, this.verticesBuffer);
let offset = 0; let offset = 0;
for (let i = 0; i < this.attributes.length; i++) { for (let i = 0; i < this.attributes.length; i++) {
let attrib = this.attributes[i]; const attrib = this.attributes[i];
let location = shader.getAttributeLocation(attrib.name); const location = shader.getAttributeLocation(attrib.name);
gl.enableVertexAttribArray(location); gl.enableVertexAttribArray(location);
gl.vertexAttribPointer(location, attrib.numElements, gl.FLOAT, false, this.elementsPerVertex * 4, offset * 4); gl.vertexAttribPointer(location, attrib.numElements, gl.FLOAT, false, this.elementsPerVertex * 4, offset * 4);
offset += attrib.numElements; offset += attrib.numElements;
@ -127,10 +127,10 @@ export class Mesh implements Disposable, Restorable {
} }
unbind (shader: Shader) { unbind (shader: Shader) {
let gl = this.context.gl; const gl = this.context.gl;
for (let i = 0; i < this.attributes.length; i++) { for (let i = 0; i < this.attributes.length; i++) {
let attrib = this.attributes[i]; const attrib = this.attributes[i];
let location = shader.getAttributeLocation(attrib.name); const location = shader.getAttributeLocation(attrib.name);
gl.disableVertexAttribArray(location); gl.disableVertexAttribArray(location);
} }
gl.bindBuffer(gl.ARRAY_BUFFER, null); gl.bindBuffer(gl.ARRAY_BUFFER, null);
@ -138,7 +138,7 @@ export class Mesh implements Disposable, Restorable {
} }
private update () { private update () {
let gl = this.context.gl; const gl = this.context.gl;
if (this.dirtyVertices) { if (this.dirtyVertices) {
if (!this.verticesBuffer) { if (!this.verticesBuffer) {
this.verticesBuffer = gl.createBuffer(); this.verticesBuffer = gl.createBuffer();
@ -166,7 +166,7 @@ export class Mesh implements Disposable, Restorable {
dispose () { dispose () {
this.context.removeRestorable(this); this.context.removeRestorable(this);
let gl = this.context.gl; const gl = this.context.gl;
gl.deleteBuffer(this.verticesBuffer); gl.deleteBuffer(this.verticesBuffer);
gl.deleteBuffer(this.indicesBuffer); gl.deleteBuffer(this.indicesBuffer);
} }
@ -190,7 +190,7 @@ export class Position3Attribute extends VertexAttribute {
export class TexCoordAttribute extends VertexAttribute { export class TexCoordAttribute extends VertexAttribute {
constructor (unit: number = 0) { constructor (unit: number = 0) {
super(Shader.TEXCOORDS + (unit == 0 ? "" : unit), VertexAttributeType.Float, 2); super(Shader.TEXCOORDS + (unit === 0 ? "" : unit), VertexAttributeType.Float, 2);
} }
} }

View File

@ -27,16 +27,17 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { BlendMode, Disposable } from "@esotericsoftware/spine-core"; import type { BlendMode, Disposable } from "@esotericsoftware/spine-core";
import { GLTexture } from "./GLTexture.js"; import type { GLTexture } from "./GLTexture.js";
import { Mesh, Position2Attribute, ColorAttribute, TexCoordAttribute, Color2Attribute } from "./Mesh.js"; import { Color2Attribute, ColorAttribute, Mesh, Position2Attribute, TexCoordAttribute } from "./Mesh.js";
import { Shader } from "./Shader.js"; import type { Shader } from "./Shader.js";
import { ManagedWebGLRenderingContext } from "./WebGL.js"; import { ManagedWebGLRenderingContext } from "./WebGL.js";
const GL_ONE = 1; const GL_ONE = 1;
const GL_ONE_MINUS_SRC_COLOR = 0x0301; const GL_ONE_MINUS_SRC_COLOR = 0x0301;
const GL_SRC_ALPHA = 0x0302; const GL_SRC_ALPHA = 0x0302;
const GL_ONE_MINUS_SRC_ALPHA = 0x0303; const GL_ONE_MINUS_SRC_ALPHA = 0x0303;
// biome-ignore lint/correctness/noUnusedVariables: intentional
const GL_ONE_MINUS_DST_ALPHA = 0x0305; const GL_ONE_MINUS_DST_ALPHA = 0x0305;
const GL_DST_COLOR = 0x0306; const GL_DST_COLOR = 0x0306;
@ -58,13 +59,13 @@ export class PolygonBatcher implements Disposable {
private cullWasEnabled = false; private cullWasEnabled = false;
constructor (context: ManagedWebGLRenderingContext | WebGLRenderingContext, twoColorTint: boolean = true, maxVertices: number = 10920) { constructor (context: ManagedWebGLRenderingContext | WebGLRenderingContext, twoColorTint: boolean = true, maxVertices: number = 10920) {
if (maxVertices > 10920) throw new Error("Can't have more than 10920 triangles per batch: " + maxVertices); if (maxVertices > 10920) throw new Error(`Can't have more than 10920 triangles per batch: ${maxVertices}`);
this.context = context instanceof ManagedWebGLRenderingContext ? context : new ManagedWebGLRenderingContext(context); this.context = context instanceof ManagedWebGLRenderingContext ? context : new ManagedWebGLRenderingContext(context);
let attributes = twoColorTint ? const attributes = twoColorTint ?
[new Position2Attribute(), new ColorAttribute(), new TexCoordAttribute(), new Color2Attribute()] : [new Position2Attribute(), new ColorAttribute(), new TexCoordAttribute(), new Color2Attribute()] :
[new Position2Attribute(), new ColorAttribute(), new TexCoordAttribute()]; [new Position2Attribute(), new ColorAttribute(), new TexCoordAttribute()];
this.mesh = new Mesh(context, attributes, maxVertices, maxVertices * 3); this.mesh = new Mesh(context, attributes, maxVertices, maxVertices * 3);
let gl = this.context.gl; const gl = this.context.gl;
this.srcColorBlend = gl.SRC_ALPHA; this.srcColorBlend = gl.SRC_ALPHA;
this.srcAlphaBlend = gl.ONE; this.srcAlphaBlend = gl.ONE;
this.dstBlend = gl.ONE_MINUS_SRC_ALPHA; this.dstBlend = gl.ONE_MINUS_SRC_ALPHA;
@ -77,7 +78,7 @@ export class PolygonBatcher implements Disposable {
this.lastTexture = null; this.lastTexture = null;
this.isDrawing = true; this.isDrawing = true;
let gl = this.context.gl; const gl = this.context.gl;
gl.enable(gl.BLEND); gl.enable(gl.BLEND);
gl.blendFuncSeparate(this.srcColorBlend, this.dstBlend, this.srcAlphaBlend, this.dstBlend); gl.blendFuncSeparate(this.srcColorBlend, this.dstBlend, this.srcAlphaBlend, this.dstBlend);
@ -100,19 +101,19 @@ export class PolygonBatcher implements Disposable {
const srcAlphaBlend = blendModeGL.srcAlpha; const srcAlphaBlend = blendModeGL.srcAlpha;
const dstBlend = blendModeGL.dstRgb; const dstBlend = blendModeGL.dstRgb;
if (this.srcColorBlend == srcColorBlend && this.srcAlphaBlend == srcAlphaBlend && this.dstBlend == dstBlend) return; if (this.srcColorBlend === srcColorBlend && this.srcAlphaBlend === srcAlphaBlend && this.dstBlend === dstBlend) return;
this.srcColorBlend = srcColorBlend; this.srcColorBlend = srcColorBlend;
this.srcAlphaBlend = srcAlphaBlend; this.srcAlphaBlend = srcAlphaBlend;
this.dstBlend = dstBlend; this.dstBlend = dstBlend;
if (this.isDrawing) { if (this.isDrawing) {
this.flush(); this.flush();
} }
let gl = this.context.gl; const gl = this.context.gl;
gl.blendFuncSeparate(srcColorBlend, dstBlend, srcAlphaBlend, dstBlend); gl.blendFuncSeparate(srcColorBlend, dstBlend, srcAlphaBlend, dstBlend);
} }
draw (texture: GLTexture, vertices: ArrayLike<number>, indices: Array<number>) { draw (texture: GLTexture, vertices: ArrayLike<number>, indices: Array<number>) {
if (texture != this.lastTexture) { if (texture !== this.lastTexture) {
this.flush(); this.flush();
this.lastTexture = texture; this.lastTexture = texture;
} else if (this.verticesLength + vertices.length > this.mesh.getVertices().length || } else if (this.verticesLength + vertices.length > this.mesh.getVertices().length ||
@ -120,12 +121,12 @@ export class PolygonBatcher implements Disposable {
this.flush(); this.flush();
} }
let indexStart = this.mesh.numVertices(); const indexStart = this.mesh.numVertices();
this.mesh.getVertices().set(vertices, this.verticesLength); this.mesh.getVertices().set(vertices, this.verticesLength);
this.verticesLength += vertices.length; this.verticesLength += vertices.length;
this.mesh.setVerticesLength(this.verticesLength) this.mesh.setVerticesLength(this.verticesLength)
let indicesArray = this.mesh.getIndices(); const indicesArray = this.mesh.getIndices();
for (let i = this.indicesLength, j = 0; j < indices.length; i++, j++) for (let i = this.indicesLength, j = 0; j < indices.length; i++, j++)
indicesArray[i] = indices[j] + indexStart; indicesArray[i] = indices[j] + indexStart;
this.indicesLength += indices.length; this.indicesLength += indices.length;
@ -133,7 +134,7 @@ export class PolygonBatcher implements Disposable {
} }
flush () { flush () {
if (this.verticesLength == 0) return; if (this.verticesLength === 0) return;
if (!this.lastTexture) throw new Error("No texture set."); if (!this.lastTexture) throw new Error("No texture set.");
if (!this.shader) throw new Error("No shader set."); if (!this.shader) throw new Error("No shader set.");
this.lastTexture.bind(); this.lastTexture.bind();
@ -154,7 +155,7 @@ export class PolygonBatcher implements Disposable {
this.lastTexture = null; this.lastTexture = null;
this.isDrawing = false; this.isDrawing = false;
let gl = this.context.gl; const gl = this.context.gl;
gl.disable(gl.BLEND); gl.disable(gl.BLEND);
if (PolygonBatcher.disableCulling) { if (PolygonBatcher.disableCulling) {
if (this.cullWasEnabled) gl.enable(gl.CULL_FACE); if (this.cullWasEnabled) gl.enable(gl.CULL_FACE);
@ -166,7 +167,7 @@ export class PolygonBatcher implements Disposable {
} }
static getAndResetGlobalDrawCalls () { static getAndResetGlobalDrawCalls () {
let result = PolygonBatcher.globalDrawCalls; const result = PolygonBatcher.globalDrawCalls;
PolygonBatcher.globalDrawCalls = 0; PolygonBatcher.globalDrawCalls = 0;
return result; return result;
} }

View File

@ -27,15 +27,16 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { Color, Disposable, Skeleton, MathUtils, TextureAtlasRegion } from "@esotericsoftware/spine-core"; import { Color, type Disposable, MathUtils, type Skeleton, type TextureAtlasRegion } from "@esotericsoftware/spine-core";
import { OrthoCamera } from "./Camera.js"; import { OrthoCamera } from "./Camera.js";
import { GLTexture } from "./GLTexture.js"; import type { GLTexture } from "./GLTexture.js";
import { PolygonBatcher } from "./PolygonBatcher.js"; import { PolygonBatcher } from "./PolygonBatcher.js";
import { Shader } from "./Shader.js"; import { Shader } from "./Shader.js";
import { ShapeRenderer } from "./ShapeRenderer.js"; import { ShapeRenderer } from "./ShapeRenderer.js";
import { SkeletonDebugRenderer } from "./SkeletonDebugRenderer.js"; import { SkeletonDebugRenderer } from "./SkeletonDebugRenderer.js";
import { SkeletonRenderer, VertexTransformer } from "./SkeletonRenderer.js"; import { SkeletonRenderer, type VertexTransformer } from "./SkeletonRenderer.js";
import { ManagedWebGLRenderingContext } from "./WebGL.js"; import { ManagedWebGLRenderingContext } from "./WebGL.js";
; ;
const quad = [ const quad = [
@ -229,22 +230,22 @@ export class SceneRenderer implements Disposable {
if (!color) color = WHITE; if (!color) color = WHITE;
// bottom left and top right corner points relative to origin // bottom left and top right corner points relative to origin
let worldOriginX = x + pivotX; const worldOriginX = x + pivotX;
let worldOriginY = y + pivotY; const worldOriginY = y + pivotY;
let fx = -pivotX; const fx = -pivotX;
let fy = -pivotY; const fy = -pivotY;
let fx2 = width - pivotX; const fx2 = width - pivotX;
let fy2 = height - pivotY; const fy2 = height - pivotY;
// construct corner points, start from top left and go counter clockwise // construct corner points, start from top left and go counter clockwise
let p1x = fx; const p1x = fx;
let p1y = fy; const p1y = fy;
let p2x = fx; const p2x = fx;
let p2y = fy2; const p2y = fy2;
let p3x = fx2; const p3x = fx2;
let p3y = fy2; const p3y = fy2;
let p4x = fx2; const p4x = fx2;
let p4y = fy; const p4y = fy;
let x1 = 0; let x1 = 0;
let y1 = 0; let y1 = 0;
@ -256,9 +257,9 @@ export class SceneRenderer implements Disposable {
let y4 = 0; let y4 = 0;
// rotate // rotate
if (angle != 0) { if (angle !== 0) {
let cos = MathUtils.cosDeg(angle); const cos = MathUtils.cosDeg(angle);
let sin = MathUtils.sinDeg(angle); const sin = MathUtils.sinDeg(angle);
x1 = cos * p1x - sin * p1y; x1 = cos * p1x - sin * p1y;
y1 = sin * p1x + cos * p1y; y1 = sin * p1x + cos * p1y;
@ -464,27 +465,27 @@ export class SceneRenderer implements Disposable {
} }
resize (resizeMode: ResizeMode, worldWidth?: number, worldHeight?: number) { resize (resizeMode: ResizeMode, worldWidth?: number, worldHeight?: number) {
let canvas = this.canvas; const canvas = this.canvas;
var dpr = window.devicePixelRatio || 1; var dpr = window.devicePixelRatio || 1;
var w = Math.round(canvas.clientWidth * dpr); var w = Math.round(canvas.clientWidth * dpr);
var h = Math.round(canvas.clientHeight * dpr); var h = Math.round(canvas.clientHeight * dpr);
if (canvas.width != w || canvas.height != h) { if (canvas.width !== w || canvas.height !== h) {
canvas.width = w; canvas.width = w;
canvas.height = h; canvas.height = h;
} }
if (resizeMode === ResizeMode.FitClip && worldWidth !== undefined && worldHeight !== undefined) { if (resizeMode === ResizeMode.FitClip && worldWidth !== undefined && worldHeight !== undefined) {
let targetRatio = h / w, sourceRatio = worldHeight / worldWidth; const targetRatio = h / w, sourceRatio = worldHeight / worldWidth;
let scale = targetRatio > sourceRatio ? w / worldWidth : h / worldHeight; const scale = targetRatio > sourceRatio ? w / worldWidth : h / worldHeight;
worldWidth *= scale; worldWidth *= scale;
worldHeight *= scale; worldHeight *= scale;
this.camera.setViewport(worldWidth, worldHeight); this.camera.setViewport(worldWidth, worldHeight);
this.context.gl.viewport((w - worldWidth) / 2, (h - worldHeight) / 2, worldWidth, worldHeight); this.context.gl.viewport((w - worldWidth) / 2, (h - worldHeight) / 2, worldWidth, worldHeight);
} else { } else {
if (resizeMode === ResizeMode.Fit) { if (resizeMode === ResizeMode.Fit) {
let targetWidth = this.camera.viewportWidth, targetHeight = this.camera.viewportHeight; const targetWidth = this.camera.viewportWidth, targetHeight = this.camera.viewportHeight;
let targetRatio = targetHeight / targetWidth, sourceRatio = h / w; const targetRatio = targetHeight / targetWidth, sourceRatio = h / w;
let scale = targetRatio < sourceRatio ? targetWidth / w : targetHeight / h; const scale = targetRatio < sourceRatio ? targetWidth / w : targetHeight / h;
this.camera.setViewport(w * scale, h * scale); this.camera.setViewport(w * scale, h * scale);
} else if (resizeMode === ResizeMode.Expand) } else if (resizeMode === ResizeMode.Expand)
this.camera.setViewport(w, h); this.camera.setViewport(w, h);

View File

@ -27,7 +27,7 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { Disposable, Restorable } from "@esotericsoftware/spine-core"; import type { Disposable, Restorable } from "@esotericsoftware/spine-core";
import { ManagedWebGLRenderingContext } from "./WebGL.js"; import { ManagedWebGLRenderingContext } from "./WebGL.js";
export class Shader implements Disposable, Restorable { export class Shader implements Disposable, Restorable {
@ -63,7 +63,7 @@ export class Shader implements Disposable, Restorable {
} }
private compile () { private compile () {
let gl = this.context.gl; const gl = this.context.gl;
try { try {
this.vs = this.compileShader(gl.VERTEX_SHADER, this.vertexShader); this.vs = this.compileShader(gl.VERTEX_SHADER, this.vertexShader);
if (!this.vs) throw new Error("Couldn't compile vertex shader."); if (!this.vs) throw new Error("Couldn't compile vertex shader.");
@ -77,13 +77,13 @@ export class Shader implements Disposable, Restorable {
} }
private compileShader (type: number, source: string) { private compileShader (type: number, source: string) {
let gl = this.context.gl; const gl = this.context.gl;
let shader = gl.createShader(type); const shader = gl.createShader(type);
if (!shader) throw new Error("Couldn't create shader."); if (!shader) throw new Error("Couldn't create shader.");
gl.shaderSource(shader, source); gl.shaderSource(shader, source);
gl.compileShader(shader); gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
let error = "Couldn't compile shader: " + gl.getShaderInfoLog(shader); const error = `Couldn't compile shader: ${gl.getShaderInfoLog(shader)}`;
gl.deleteShader(shader); gl.deleteShader(shader);
if (!gl.isContextLost()) throw new Error(error); if (!gl.isContextLost()) throw new Error(error);
} }
@ -91,15 +91,15 @@ export class Shader implements Disposable, Restorable {
} }
private compileProgram (vs: WebGLShader, fs: WebGLShader) { private compileProgram (vs: WebGLShader, fs: WebGLShader) {
let gl = this.context.gl; const gl = this.context.gl;
let program = gl.createProgram(); const program = gl.createProgram();
if (!program) throw new Error("Couldn't compile program."); if (!program) throw new Error("Couldn't compile program.");
gl.attachShader(program, vs); gl.attachShader(program, vs);
gl.attachShader(program, fs); gl.attachShader(program, fs);
gl.linkProgram(program); gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
let error = "Couldn't compile shader program: " + gl.getProgramInfoLog(program); const error = `Couldn't compile shader program: ${gl.getProgramInfoLog(program)}`;
gl.deleteProgram(program); gl.deleteProgram(program);
if (!gl.isContextLost()) throw new Error(error); if (!gl.isContextLost()) throw new Error(error);
} }
@ -139,43 +139,43 @@ export class Shader implements Disposable, Restorable {
} }
public setUniform2x2f (uniform: string, value: ArrayLike<number>) { public setUniform2x2f (uniform: string, value: ArrayLike<number>) {
let gl = this.context.gl; const gl = this.context.gl;
this.tmp2x2.set(value); this.tmp2x2.set(value);
gl.uniformMatrix2fv(this.getUniformLocation(uniform), false, this.tmp2x2); gl.uniformMatrix2fv(this.getUniformLocation(uniform), false, this.tmp2x2);
} }
public setUniform3x3f (uniform: string, value: ArrayLike<number>) { public setUniform3x3f (uniform: string, value: ArrayLike<number>) {
let gl = this.context.gl; const gl = this.context.gl;
this.tmp3x3.set(value); this.tmp3x3.set(value);
gl.uniformMatrix3fv(this.getUniformLocation(uniform), false, this.tmp3x3); gl.uniformMatrix3fv(this.getUniformLocation(uniform), false, this.tmp3x3);
} }
public setUniform4x4f (uniform: string, value: ArrayLike<number>) { public setUniform4x4f (uniform: string, value: ArrayLike<number>) {
let gl = this.context.gl; const gl = this.context.gl;
this.tmp4x4.set(value); this.tmp4x4.set(value);
gl.uniformMatrix4fv(this.getUniformLocation(uniform), false, this.tmp4x4); gl.uniformMatrix4fv(this.getUniformLocation(uniform), false, this.tmp4x4);
} }
public getUniformLocation (uniform: string): WebGLUniformLocation | null { public getUniformLocation (uniform: string): WebGLUniformLocation | null {
let gl = this.context.gl; const gl = this.context.gl;
if (!this.program) throw new Error("Shader not compiled."); if (!this.program) throw new Error("Shader not compiled.");
let location = gl.getUniformLocation(this.program, uniform); const location = gl.getUniformLocation(this.program, uniform);
if (!location && !gl.isContextLost()) throw new Error(`Couldn't find location for uniform ${uniform}`); if (!location && !gl.isContextLost()) throw new Error(`Couldn't find location for uniform ${uniform}`);
return location; return location;
} }
public getAttributeLocation (attribute: string): number { public getAttributeLocation (attribute: string): number {
let gl = this.context.gl; const gl = this.context.gl;
if (!this.program) throw new Error("Shader not compiled."); if (!this.program) throw new Error("Shader not compiled.");
let location = gl.getAttribLocation(this.program, attribute); const location = gl.getAttribLocation(this.program, attribute);
if (location == -1 && !gl.isContextLost()) throw new Error(`Couldn't find location for attribute ${attribute}`); if (location === -1 && !gl.isContextLost()) throw new Error(`Couldn't find location for attribute ${attribute}`);
return location; return location;
} }
public dispose () { public dispose () {
this.context.removeRestorable(this); this.context.removeRestorable(this);
let gl = this.context.gl; const gl = this.context.gl;
if (this.vs) { if (this.vs) {
gl.deleteShader(this.vs); gl.deleteShader(this.vs);
this.vs = null; this.vs = null;
@ -193,7 +193,7 @@ export class Shader implements Disposable, Restorable {
} }
public static newColoredTextured (context: ManagedWebGLRenderingContext | WebGLRenderingContext): Shader { public static newColoredTextured (context: ManagedWebGLRenderingContext | WebGLRenderingContext): Shader {
let vs = ` const vs = `
attribute vec4 ${Shader.POSITION}; attribute vec4 ${Shader.POSITION};
attribute vec4 ${Shader.COLOR}; attribute vec4 ${Shader.COLOR};
attribute vec2 ${Shader.TEXCOORDS}; attribute vec2 ${Shader.TEXCOORDS};
@ -208,7 +208,7 @@ void main () {
} }
`; `;
let fs = ` const fs = `
#ifdef GL_ES #ifdef GL_ES
#define LOWP lowp #define LOWP lowp
precision mediump float; precision mediump float;
@ -228,7 +228,7 @@ void main () {
} }
public static newTwoColoredTextured (context: ManagedWebGLRenderingContext | WebGLRenderingContext): Shader { public static newTwoColoredTextured (context: ManagedWebGLRenderingContext | WebGLRenderingContext): Shader {
let vs = ` const vs = `
attribute vec4 ${Shader.POSITION}; attribute vec4 ${Shader.POSITION};
attribute vec4 ${Shader.COLOR}; attribute vec4 ${Shader.COLOR};
attribute vec4 ${Shader.COLOR2}; attribute vec4 ${Shader.COLOR2};
@ -246,7 +246,7 @@ void main () {
} }
`; `;
let fs = ` const fs = `
#ifdef GL_ES #ifdef GL_ES
#define LOWP lowp #define LOWP lowp
precision mediump float; precision mediump float;
@ -269,7 +269,7 @@ void main () {
} }
public static newColored (context: ManagedWebGLRenderingContext | WebGLRenderingContext): Shader { public static newColored (context: ManagedWebGLRenderingContext | WebGLRenderingContext): Shader {
let vs = ` const vs = `
attribute vec4 ${Shader.POSITION}; attribute vec4 ${Shader.POSITION};
attribute vec4 ${Shader.COLOR}; attribute vec4 ${Shader.COLOR};
uniform mat4 ${Shader.MVP_MATRIX}; uniform mat4 ${Shader.MVP_MATRIX};
@ -281,7 +281,7 @@ void main () {
} }
`; `;
let fs = ` const fs = `
#ifdef GL_ES #ifdef GL_ES
#define LOWP lowp #define LOWP lowp
precision mediump float; precision mediump float;

View File

@ -27,9 +27,9 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { Disposable, Color, Vector2, MathUtils } from "@esotericsoftware/spine-core"; import { Color, type Disposable, MathUtils, Vector2 } from "@esotericsoftware/spine-core";
import { Mesh, Position2Attribute, ColorAttribute } from "./Mesh.js"; import { ColorAttribute, Mesh, Position2Attribute } from "./Mesh.js";
import { Shader } from "./Shader.js"; import type { Shader } from "./Shader.js";
import { ManagedWebGLRenderingContext } from "./WebGL.js"; import { ManagedWebGLRenderingContext } from "./WebGL.js";
export class ShapeRenderer implements Disposable { export class ShapeRenderer implements Disposable {
@ -46,10 +46,10 @@ export class ShapeRenderer implements Disposable {
private dstBlend: number; private dstBlend: number;
constructor (context: ManagedWebGLRenderingContext | WebGLRenderingContext, maxVertices: number = 10920) { constructor (context: ManagedWebGLRenderingContext | WebGLRenderingContext, maxVertices: number = 10920) {
if (maxVertices > 10920) throw new Error("Can't have more than 10920 triangles per batch: " + maxVertices); if (maxVertices > 10920) throw new Error(`Can't have more than 10920 triangles per batch: ${maxVertices}`);
this.context = context instanceof ManagedWebGLRenderingContext ? context : new ManagedWebGLRenderingContext(context); this.context = context instanceof ManagedWebGLRenderingContext ? context : new ManagedWebGLRenderingContext(context);
this.mesh = new Mesh(context, [new Position2Attribute(), new ColorAttribute()], maxVertices, 0); this.mesh = new Mesh(context, [new Position2Attribute(), new ColorAttribute()], maxVertices, 0);
let gl = this.context.gl; const gl = this.context.gl;
this.srcColorBlend = gl.SRC_ALPHA; this.srcColorBlend = gl.SRC_ALPHA;
this.srcAlphaBlend = gl.ONE; this.srcAlphaBlend = gl.ONE;
this.dstBlend = gl.ONE_MINUS_SRC_ALPHA; this.dstBlend = gl.ONE_MINUS_SRC_ALPHA;
@ -61,7 +61,7 @@ export class ShapeRenderer implements Disposable {
this.vertexIndex = 0; this.vertexIndex = 0;
this.isDrawing = true; this.isDrawing = true;
let gl = this.context.gl; const gl = this.context.gl;
gl.enable(gl.BLEND); gl.enable(gl.BLEND);
gl.blendFuncSeparate(this.srcColorBlend, this.dstBlend, this.srcAlphaBlend, this.dstBlend); gl.blendFuncSeparate(this.srcColorBlend, this.dstBlend, this.srcAlphaBlend, this.dstBlend);
} }
@ -72,7 +72,7 @@ export class ShapeRenderer implements Disposable {
this.dstBlend = dstBlend; this.dstBlend = dstBlend;
if (this.isDrawing) { if (this.isDrawing) {
this.flush(); this.flush();
let gl = this.context.gl; const gl = this.context.gl;
gl.blendFuncSeparate(srcColorBlend, dstBlend, srcAlphaBlend, dstBlend); gl.blendFuncSeparate(srcColorBlend, dstBlend, srcAlphaBlend, dstBlend);
} }
} }
@ -93,8 +93,6 @@ export class ShapeRenderer implements Disposable {
line (x: number, y: number, x2: number, y2: number, color?: Color) { line (x: number, y: number, x2: number, y2: number, color?: Color) {
this.check(ShapeType.Line, 2); this.check(ShapeType.Line, 2);
let vertices = this.mesh.getVertices();
let idx = this.vertexIndex;
if (!color) color = this.color; if (!color) color = this.color;
this.vertex(x, y, color); this.vertex(x, y, color);
this.vertex(x2, y2, color); this.vertex(x2, y2, color);
@ -102,8 +100,6 @@ export class ShapeRenderer implements Disposable {
triangle (filled: boolean, x: number, y: number, x2: number, y2: number, x3: number, y3: number, color?: Color, color2?: Color, color3?: Color) { triangle (filled: boolean, x: number, y: number, x2: number, y2: number, x3: number, y3: number, color?: Color, color2?: Color, color3?: Color) {
this.check(filled ? ShapeType.Filled : ShapeType.Line, 3); this.check(filled ? ShapeType.Filled : ShapeType.Line, 3);
let vertices = this.mesh.getVertices();
let idx = this.vertexIndex;
if (!color) color = this.color; if (!color) color = this.color;
if (!color2) color2 = this.color; if (!color2) color2 = this.color;
if (!color3) color3 = this.color; if (!color3) color3 = this.color;
@ -125,8 +121,6 @@ export class ShapeRenderer implements Disposable {
quad (filled: boolean, x: number, y: number, x2: number, y2: number, x3: number, y3: number, x4: number, y4: number, color?: Color, color2?: Color, color3?: Color, color4?: Color) { quad (filled: boolean, x: number, y: number, x2: number, y2: number, x3: number, y3: number, x4: number, y4: number, color?: Color, color2?: Color, color3?: Color, color4?: Color) {
this.check(filled ? ShapeType.Filled : ShapeType.Line, 3); this.check(filled ? ShapeType.Filled : ShapeType.Line, 3);
let vertices = this.mesh.getVertices();
let idx = this.vertexIndex;
if (!color) color = this.color; if (!color) color = this.color;
if (!color2) color2 = this.color; if (!color2) color2 = this.color;
if (!color3) color3 = this.color; if (!color3) color3 = this.color;
@ -149,11 +143,11 @@ export class ShapeRenderer implements Disposable {
rectLine (filled: boolean, x1: number, y1: number, x2: number, y2: number, width: number, color?: Color) { rectLine (filled: boolean, x1: number, y1: number, x2: number, y2: number, width: number, color?: Color) {
this.check(filled ? ShapeType.Filled : ShapeType.Line, 8); this.check(filled ? ShapeType.Filled : ShapeType.Line, 8);
if (!color) color = this.color; if (!color) color = this.color;
let t = this.tmp.set(y2 - y1, x1 - x2); const t = this.tmp.set(y2 - y1, x1 - x2);
t.normalize(); t.normalize();
width *= 0.5; width *= 0.5;
let tx = t.x * width; const tx = t.x * width;
let ty = t.y * width; const ty = t.y * width;
if (!filled) { if (!filled) {
this.vertex(x1 + tx, y1 + ty, color); this.vertex(x1 + tx, y1 + ty, color);
this.vertex(x1 - tx, y1 - ty, color); this.vertex(x1 - tx, y1 - ty, color);
@ -185,19 +179,17 @@ export class ShapeRenderer implements Disposable {
if (count < 3) throw new Error("Polygon must contain at least 3 vertices"); if (count < 3) throw new Error("Polygon must contain at least 3 vertices");
this.check(ShapeType.Line, count * 2); this.check(ShapeType.Line, count * 2);
if (!color) color = this.color; if (!color) color = this.color;
let vertices = this.mesh.getVertices();
let idx = this.vertexIndex;
offset <<= 1; offset <<= 1;
count <<= 1; count <<= 1;
let firstX = polygonVertices[offset]; const firstX = polygonVertices[offset];
let firstY = polygonVertices[offset + 1]; const firstY = polygonVertices[offset + 1];
let last = offset + count; const last = offset + count;
for (let i = offset, n = offset + count - 2; i < n; i += 2) { for (let i = offset, n = offset + count - 2; i < n; i += 2) {
let x1 = polygonVertices[i]; const x1 = polygonVertices[i];
let y1 = polygonVertices[i + 1]; const y1 = polygonVertices[i + 1];
let x2 = 0; let x2 = 0;
let y2 = 0; let y2 = 0;
@ -210,24 +202,24 @@ export class ShapeRenderer implements Disposable {
y2 = polygonVertices[i + 3]; y2 = polygonVertices[i + 3];
} }
this.vertex(x1, y1, color!); this.vertex(x1, y1, color);
this.vertex(x2, y2, color!); this.vertex(x2, y2, color);
} }
} }
circle (filled: boolean, x: number, y: number, radius: number, color?: Color, segments: number = 0) { circle (filled: boolean, x: number, y: number, radius: number, color?: Color, segments: number = 0) {
if (segments == 0) segments = Math.max(1, (6 * MathUtils.cbrt(radius)) | 0); if (segments === 0) segments = Math.max(1, (6 * MathUtils.cbrt(radius)) | 0);
if (segments <= 0) throw new Error("segments must be > 0."); if (segments <= 0) throw new Error("segments must be > 0.");
if (!color) color = this.color; if (!color) color = this.color;
let angle = 2 * MathUtils.PI / segments; const angle = 2 * MathUtils.PI / segments;
let cos = Math.cos(angle); const cos = Math.cos(angle);
let sin = Math.sin(angle); const sin = Math.sin(angle);
let cx = radius, cy = 0; let cx = radius, cy = 0;
if (!filled) { if (!filled) {
this.check(ShapeType.Line, segments * 2 + 2); this.check(ShapeType.Line, segments * 2 + 2);
for (let i = 0; i < segments; i++) { for (let i = 0; i < segments; i++) {
this.vertex(x + cx, y + cy, color); this.vertex(x + cx, y + cy, color);
let temp = cx; const temp = cx;
cx = cos * cx - sin * cy; cx = cos * cx - sin * cy;
cy = sin * temp + cos * cy; cy = sin * temp + cos * cy;
this.vertex(x + cx, y + cy, color); this.vertex(x + cx, y + cy, color);
@ -240,7 +232,7 @@ export class ShapeRenderer implements Disposable {
for (let i = 0; i < segments; i++) { for (let i = 0; i < segments; i++) {
this.vertex(x, y, color); this.vertex(x, y, color);
this.vertex(x + cx, y + cy, color); this.vertex(x + cx, y + cy, color);
let temp = cx; const temp = cx;
cx = cos * cx - sin * cy; cx = cos * cx - sin * cy;
cy = sin * temp + cos * cy; cy = sin * temp + cos * cy;
this.vertex(x + cx, y + cy, color); this.vertex(x + cx, y + cy, color);
@ -250,7 +242,6 @@ export class ShapeRenderer implements Disposable {
this.vertex(x + cx, y + cy, color); this.vertex(x + cx, y + cy, color);
} }
let temp = cx;
cx = radius; cx = radius;
cy = 0; cy = 0;
this.vertex(x + cx, y + cy, color); this.vertex(x + cx, y + cy, color);
@ -261,20 +252,20 @@ export class ShapeRenderer implements Disposable {
if (!color) color = this.color; if (!color) color = this.color;
// Algorithm from: http://www.antigrain.com/research/bezier_interpolation/index.html#PAGE_BEZIER_INTERPOLATION // Algorithm from: http://www.antigrain.com/research/bezier_interpolation/index.html#PAGE_BEZIER_INTERPOLATION
let subdiv_step = 1 / segments; const subdiv_step = 1 / segments;
let subdiv_step2 = subdiv_step * subdiv_step; const subdiv_step2 = subdiv_step * subdiv_step;
let subdiv_step3 = subdiv_step * subdiv_step * subdiv_step; const subdiv_step3 = subdiv_step * subdiv_step * subdiv_step;
let pre1 = 3 * subdiv_step; const pre1 = 3 * subdiv_step;
let pre2 = 3 * subdiv_step2; const pre2 = 3 * subdiv_step2;
let pre4 = 6 * subdiv_step2; const pre4 = 6 * subdiv_step2;
let pre5 = 6 * subdiv_step3; const pre5 = 6 * subdiv_step3;
let tmp1x = x1 - cx1 * 2 + cx2; const tmp1x = x1 - cx1 * 2 + cx2;
let tmp1y = y1 - cy1 * 2 + cy2; const tmp1y = y1 - cy1 * 2 + cy2;
let tmp2x = (cx1 - cx2) * 3 - x1 + x2; const tmp2x = (cx1 - cx2) * 3 - x1 + x2;
let tmp2y = (cy1 - cy2) * 3 - y1 + y2; const tmp2y = (cy1 - cy2) * 3 - y1 + y2;
let fx = x1; let fx = x1;
let fy = y1; let fy = y1;
@ -285,26 +276,26 @@ export class ShapeRenderer implements Disposable {
let ddfx = tmp1x * pre4 + tmp2x * pre5; let ddfx = tmp1x * pre4 + tmp2x * pre5;
let ddfy = tmp1y * pre4 + tmp2y * pre5; let ddfy = tmp1y * pre4 + tmp2y * pre5;
let dddfx = tmp2x * pre5; const dddfx = tmp2x * pre5;
let dddfy = tmp2y * pre5; const dddfy = tmp2y * pre5;
while (segments-- > 0) { while (segments-- > 0) {
this.vertex(fx, fy, color!); this.vertex(fx, fy, color);
fx += dfx; fx += dfx;
fy += dfy; fy += dfy;
dfx += ddfx; dfx += ddfx;
dfy += ddfy; dfy += ddfy;
ddfx += dddfx; ddfx += dddfx;
ddfy += dddfy; ddfy += dddfy;
this.vertex(fx, fy, color!); this.vertex(fx, fy, color);
} }
this.vertex(fx, fy, color!); this.vertex(fx, fy, color);
this.vertex(x2, y2, color!); this.vertex(x2, y2, color);
} }
private vertex (x: number, y: number, color: Color) { private vertex (x: number, y: number, color: Color) {
let idx = this.vertexIndex; let idx = this.vertexIndex;
let vertices = this.mesh.getVertices(); const vertices = this.mesh.getVertices();
vertices[idx++] = x; vertices[idx++] = x;
vertices[idx++] = y; vertices[idx++] = y;
vertices[idx++] = color.r; vertices[idx++] = color.r;
@ -317,13 +308,13 @@ export class ShapeRenderer implements Disposable {
end () { end () {
if (!this.isDrawing) throw new Error("ShapeRenderer.begin() has not been called"); if (!this.isDrawing) throw new Error("ShapeRenderer.begin() has not been called");
this.flush(); this.flush();
let gl = this.context.gl; const gl = this.context.gl;
gl.disable(gl.BLEND); gl.disable(gl.BLEND);
this.isDrawing = false; this.isDrawing = false;
} }
private flush () { private flush () {
if (this.vertexIndex == 0) return; if (this.vertexIndex === 0) return;
if (!this.shader) throw new Error("No shader set."); if (!this.shader) throw new Error("No shader set.");
this.mesh.setVerticesLength(this.vertexIndex); this.mesh.setVerticesLength(this.vertexIndex);
this.mesh.draw(this.shader, this.shapeType); this.mesh.draw(this.shader, this.shapeType);
@ -332,7 +323,7 @@ export class ShapeRenderer implements Disposable {
private check (shapeType: ShapeType, numVertices: number) { private check (shapeType: ShapeType, numVertices: number) {
if (!this.isDrawing) throw new Error("ShapeRenderer.begin() has not been called"); if (!this.isDrawing) throw new Error("ShapeRenderer.begin() has not been called");
if (this.shapeType == shapeType) { if (this.shapeType === shapeType) {
if (this.mesh.maxVertices() - this.mesh.numVertices() < numVertices) this.flush(); if (this.mesh.maxVertices() - this.mesh.numVertices() < numVertices) this.flush();
else return; else return;
} else { } else {

View File

@ -27,8 +27,8 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { Disposable, Color, SkeletonBounds, Utils, Skeleton, RegionAttachment, MeshAttachment, PathAttachment, ClippingAttachment } from "@esotericsoftware/spine-core"; import { ClippingAttachment, Color, type Disposable, MeshAttachment, PathAttachment, RegionAttachment, type Skeleton, SkeletonBounds, Utils } from "@esotericsoftware/spine-core";
import { ShapeRenderer } from "./ShapeRenderer.js"; import type { ShapeRenderer } from "./ShapeRenderer.js";
import { ManagedWebGLRenderingContext } from "./WebGL.js"; import { ManagedWebGLRenderingContext } from "./WebGL.js";
export class SkeletonDebugRenderer implements Disposable { export class SkeletonDebugRenderer implements Disposable {
@ -53,7 +53,7 @@ export class SkeletonDebugRenderer implements Disposable {
private context: ManagedWebGLRenderingContext; private context: ManagedWebGLRenderingContext;
private bounds = new SkeletonBounds(); private bounds = new SkeletonBounds();
private temp = new Array<number>(); private temp = [] as number[];
private vertices = Utils.newFloatArray(2 * 1024); private vertices = Utils.newFloatArray(2 * 1024);
private static LIGHT_GRAY = new Color(192 / 255, 192 / 255, 192 / 255, 1); private static LIGHT_GRAY = new Color(192 / 255, 192 / 255, 192 / 255, 1);
private static GREEN = new Color(0, 1, 0, 1); private static GREEN = new Color(0, 1, 0, 1);
@ -63,22 +63,22 @@ export class SkeletonDebugRenderer implements Disposable {
} }
draw (shapes: ShapeRenderer, skeleton: Skeleton, ignoredBones?: Array<string>) { draw (shapes: ShapeRenderer, skeleton: Skeleton, ignoredBones?: Array<string>) {
let skeletonX = skeleton.x; const skeletonX = skeleton.x;
let skeletonY = skeleton.y; const skeletonY = skeleton.y;
let gl = this.context.gl; const gl = this.context.gl;
let srcFunc = this.premultipliedAlpha ? gl.ONE : gl.SRC_ALPHA; const srcFunc = this.premultipliedAlpha ? gl.ONE : gl.SRC_ALPHA;
shapes.setBlendMode(srcFunc, gl.ONE, gl.ONE_MINUS_SRC_ALPHA); shapes.setBlendMode(srcFunc, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
let bones = skeleton.bones; const bones = skeleton.bones;
if (this.drawBones) { if (this.drawBones) {
shapes.setColor(this.boneLineColor); shapes.setColor(this.boneLineColor);
for (let i = 0, n = bones.length; i < n; i++) { for (let i = 0, n = bones.length; i < n; i++) {
let bone = bones[i]; const bone = bones[i];
if (ignoredBones && ignoredBones.indexOf(bone.data.name) > -1) continue; if (ignoredBones && ignoredBones.indexOf(bone.data.name) > -1) continue;
if (!bone.parent) continue; if (!bone.parent) continue;
const boneApplied = bone.applied; const boneApplied = bone.applied;
let x = bone.data.length * boneApplied.a + boneApplied.worldX; const x = bone.data.length * boneApplied.a + boneApplied.worldX;
let y = bone.data.length * boneApplied.c + boneApplied.worldY; const y = bone.data.length * boneApplied.c + boneApplied.worldY;
shapes.rectLine(true, boneApplied.worldX, boneApplied.worldY, x, y, this.boneWidth * this.scale); shapes.rectLine(true, boneApplied.worldX, boneApplied.worldY, x, y, this.boneWidth * this.scale);
} }
if (this.drawSkeletonXY) shapes.x(skeletonX, skeletonY, 4 * this.scale); if (this.drawSkeletonXY) shapes.x(skeletonX, skeletonY, 4 * this.scale);
@ -86,13 +86,13 @@ export class SkeletonDebugRenderer implements Disposable {
if (this.drawRegionAttachments) { if (this.drawRegionAttachments) {
shapes.setColor(this.attachmentLineColor); shapes.setColor(this.attachmentLineColor);
let slots = skeleton.slots; const slots = skeleton.slots;
for (let i = 0, n = slots.length; i < n; i++) { for (let i = 0, n = slots.length; i < n; i++) {
let slot = slots[i]; const slot = slots[i];
if (!slot.bone.active) continue; if (!slot.bone.active) continue;
let attachment = slot.applied.attachment; const attachment = slot.applied.attachment;
if (attachment instanceof RegionAttachment) { if (attachment instanceof RegionAttachment) {
let vertices = this.vertices; const vertices = this.vertices;
attachment.computeWorldVertices(slot, vertices, 0, 2); attachment.computeWorldVertices(slot, vertices, 0, 2);
shapes.line(vertices[0], vertices[1], vertices[2], vertices[3]); shapes.line(vertices[0], vertices[1], vertices[2], vertices[3]);
shapes.line(vertices[2], vertices[3], vertices[4], vertices[5]); shapes.line(vertices[2], vertices[3], vertices[4], vertices[5]);
@ -103,20 +103,20 @@ export class SkeletonDebugRenderer implements Disposable {
} }
if (this.drawMeshHull || this.drawMeshTriangles) { if (this.drawMeshHull || this.drawMeshTriangles) {
let slots = skeleton.slots; const slots = skeleton.slots;
for (let i = 0, n = slots.length; i < n; i++) { for (let i = 0, n = slots.length; i < n; i++) {
let slot = slots[i]; const slot = slots[i];
if (!slot.bone.active) continue; if (!slot.bone.active) continue;
let attachment = slot.applied.attachment; const attachment = slot.applied.attachment;
if (!(attachment instanceof MeshAttachment)) continue; if (!(attachment instanceof MeshAttachment)) continue;
let vertices = this.vertices; const vertices = this.vertices;
attachment.computeWorldVertices(skeleton, slot, 0, attachment.worldVerticesLength, vertices, 0, 2); attachment.computeWorldVertices(skeleton, slot, 0, attachment.worldVerticesLength, vertices, 0, 2);
let triangles = attachment.triangles; const triangles = attachment.triangles;
let hullLength = attachment.hullLength; let hullLength = attachment.hullLength;
if (this.drawMeshTriangles) { if (this.drawMeshTriangles) {
shapes.setColor(this.triangleLineColor); shapes.setColor(this.triangleLineColor);
for (let ii = 0, nn = triangles.length; ii < nn; ii += 3) { for (let ii = 0, nn = triangles.length; ii < nn; ii += 3) {
let v1 = triangles[ii] * 2, v2 = triangles[ii + 1] * 2, v3 = triangles[ii + 2] * 2; const v1 = triangles[ii] * 2, v2 = triangles[ii + 1] * 2, v3 = triangles[ii + 2] * 2;
shapes.triangle(false, vertices[v1], vertices[v1 + 1], // shapes.triangle(false, vertices[v1], vertices[v1 + 1], //
vertices[v2], vertices[v2 + 1], // vertices[v2], vertices[v2 + 1], //
vertices[v3], vertices[v3 + 1] // vertices[v3], vertices[v3 + 1] //
@ -128,7 +128,7 @@ export class SkeletonDebugRenderer implements Disposable {
hullLength = (hullLength >> 1) * 2; hullLength = (hullLength >> 1) * 2;
let lastX = vertices[hullLength - 2], lastY = vertices[hullLength - 1]; let lastX = vertices[hullLength - 2], lastY = vertices[hullLength - 1];
for (let ii = 0, nn = hullLength; ii < nn; ii += 2) { for (let ii = 0, nn = hullLength; ii < nn; ii += 2) {
let x = vertices[ii], y = vertices[ii + 1]; const x = vertices[ii], y = vertices[ii + 1];
shapes.line(x, y, lastX, lastY); shapes.line(x, y, lastX, lastY);
lastX = x; lastX = x;
lastY = y; lastY = y;
@ -138,34 +138,34 @@ export class SkeletonDebugRenderer implements Disposable {
} }
if (this.drawBoundingBoxes) { if (this.drawBoundingBoxes) {
let bounds = this.bounds; const bounds = this.bounds;
bounds.update(skeleton, true); bounds.update(skeleton, true);
shapes.setColor(this.aabbColor); shapes.setColor(this.aabbColor);
shapes.rect(false, bounds.minX, bounds.minY, bounds.getWidth(), bounds.getHeight()); shapes.rect(false, bounds.minX, bounds.minY, bounds.getWidth(), bounds.getHeight());
let polygons = bounds.polygons; const polygons = bounds.polygons;
let boxes = bounds.boundingBoxes; const boxes = bounds.boundingBoxes;
for (let i = 0, n = polygons.length; i < n; i++) { for (let i = 0, n = polygons.length; i < n; i++) {
let polygon = polygons[i]; const polygon = polygons[i];
shapes.setColor(boxes[i].color); shapes.setColor(boxes[i].color);
shapes.polygon(polygon, 0, polygon.length); shapes.polygon(polygon, 0, polygon.length);
} }
} }
if (this.drawPaths) { if (this.drawPaths) {
let slots = skeleton.slots; const slots = skeleton.slots;
for (let i = 0, n = slots.length; i < n; i++) { for (let i = 0, n = slots.length; i < n; i++) {
let slot = slots[i]; const slot = slots[i];
if (!slot.bone.active) continue; if (!slot.bone.active) continue;
let attachment = slot.applied.attachment; const attachment = slot.applied.attachment;
if (!(attachment instanceof PathAttachment)) continue; if (!(attachment instanceof PathAttachment)) continue;
let nn = attachment.worldVerticesLength; let nn = attachment.worldVerticesLength;
let world = this.temp = Utils.setArraySize(this.temp, nn, 0); const world = this.temp = Utils.setArraySize(this.temp, nn, 0);
attachment.computeWorldVertices(skeleton, slot, 0, nn, world, 0, 2); attachment.computeWorldVertices(skeleton, slot, 0, nn, world, 0, 2);
let color = this.pathColor; const color = this.pathColor;
let x1 = world[2], y1 = world[3], x2 = 0, y2 = 0; let x1 = world[2], y1 = world[3], x2 = 0, y2 = 0;
if (attachment.closed) { if (attachment.closed) {
shapes.setColor(color); shapes.setColor(color);
let cx1 = world[0], cy1 = world[1], cx2 = world[nn - 2], cy2 = world[nn - 1]; const cx1 = world[0], cy1 = world[1], cx2 = world[nn - 2], cy2 = world[nn - 1];
x2 = world[nn - 4]; x2 = world[nn - 4];
y2 = world[nn - 3]; y2 = world[nn - 3];
shapes.curve(x1, y1, cx1, cy1, cx2, cy2, x2, y2, 32); shapes.curve(x1, y1, cx1, cy1, cx2, cy2, x2, y2, 32);
@ -175,7 +175,7 @@ export class SkeletonDebugRenderer implements Disposable {
} }
nn -= 4; nn -= 4;
for (let ii = 4; ii < nn; ii += 6) { for (let ii = 4; ii < nn; ii += 6) {
let cx1 = world[ii], cy1 = world[ii + 1], cx2 = world[ii + 2], cy2 = world[ii + 3]; const cx1 = world[ii], cy1 = world[ii + 1], cx2 = world[ii + 2], cy2 = world[ii + 3];
x2 = world[ii + 4]; x2 = world[ii + 4];
y2 = world[ii + 5]; y2 = world[ii + 5];
shapes.setColor(color); shapes.setColor(color);
@ -192,29 +192,29 @@ export class SkeletonDebugRenderer implements Disposable {
if (this.drawBones) { if (this.drawBones) {
shapes.setColor(this.boneOriginColor); shapes.setColor(this.boneOriginColor);
for (let i = 0, n = bones.length; i < n; i++) { for (let i = 0, n = bones.length; i < n; i++) {
let bone = bones[i]; const bone = bones[i];
if (ignoredBones && ignoredBones.indexOf(bone.data.name) > -1) continue; if (ignoredBones && ignoredBones.indexOf(bone.data.name) > -1) continue;
let boneApplied = bone.applied; const boneApplied = bone.applied;
shapes.circle(true, boneApplied.worldX, boneApplied.worldY, 3 * this.scale, this.boneOriginColor, 8); shapes.circle(true, boneApplied.worldX, boneApplied.worldY, 3 * this.scale, this.boneOriginColor, 8);
} }
} }
if (this.drawClipping) { if (this.drawClipping) {
let slots = skeleton.slots; const slots = skeleton.slots;
shapes.setColor(this.clipColor) shapes.setColor(this.clipColor)
for (let i = 0, n = slots.length; i < n; i++) { for (let i = 0, n = slots.length; i < n; i++) {
let slot = slots[i]; const slot = slots[i];
if (!slot.bone.active) continue; if (!slot.bone.active) continue;
let attachment = slot.applied.attachment; const attachment = slot.applied.attachment;
if (!(attachment instanceof ClippingAttachment)) continue; if (!(attachment instanceof ClippingAttachment)) continue;
let nn = attachment.worldVerticesLength; const nn = attachment.worldVerticesLength;
let world = this.temp = Utils.setArraySize(this.temp, nn, 0); const world = this.temp = Utils.setArraySize(this.temp, nn, 0);
attachment.computeWorldVertices(skeleton, slot, 0, nn, world, 0, 2); attachment.computeWorldVertices(skeleton, slot, 0, nn, world, 0, 2);
for (let i = 0, n = world.length; i < n; i += 2) { for (let i = 0, n = world.length; i < n; i += 2) {
let x = world[i]; const x = world[i];
let y = world[i + 1]; const y = world[i + 1];
let x2 = world[(i + 2) % world.length]; const x2 = world[(i + 2) % world.length];
let y2 = world[(i + 3) % world.length]; const y2 = world[(i + 3) % world.length];
shapes.line(x, y, x2, y2); shapes.line(x, y, x2, y2);
} }
} }

View File

@ -27,10 +27,10 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { NumberArrayLike, Color, SkeletonClipping, Vector2, Utils, Skeleton, BlendMode, RegionAttachment, TextureAtlasRegion, MeshAttachment, ClippingAttachment } from "@esotericsoftware/spine-core"; import { type BlendMode, ClippingAttachment, Color, MeshAttachment, type NumberArrayLike, RegionAttachment, type Skeleton, SkeletonClipping, type TextureRegion, Utils, Vector2 } from "@esotericsoftware/spine-core";
import { GLTexture } from "./GLTexture.js"; import type { GLTexture } from "./GLTexture.js";
import { PolygonBatcher } from "./PolygonBatcher.js"; import type { PolygonBatcher } from "./PolygonBatcher.js";
import { ManagedWebGLRenderingContext } from "./WebGL.js"; import type { ManagedWebGLRenderingContext } from "./WebGL.js";
class Renderable { class Renderable {
@ -63,28 +63,28 @@ export class SkeletonRenderer {
} }
draw (batcher: PolygonBatcher, skeleton: Skeleton, slotRangeStart: number = -1, slotRangeEnd: number = -1, transformer: VertexTransformer | null = null) { draw (batcher: PolygonBatcher, skeleton: Skeleton, slotRangeStart: number = -1, slotRangeEnd: number = -1, transformer: VertexTransformer | null = null) {
let clipper = this.clipper; const clipper = this.clipper;
let premultipliedAlpha = this.premultipliedAlpha; const premultipliedAlpha = this.premultipliedAlpha;
let twoColorTint = this.twoColorTint; const twoColorTint = this.twoColorTint;
let blendMode: BlendMode | null = null; let blendMode: BlendMode | null = null;
let renderable: Renderable = this.renderable; const renderable: Renderable = this.renderable;
let uvs: NumberArrayLike; let uvs: NumberArrayLike;
let triangles: Array<number>; let triangles: Array<number>;
let drawOrder = skeleton.drawOrder; const drawOrder = skeleton.drawOrder;
let attachmentColor: Color; let attachmentColor: Color;
let skeletonColor = skeleton.color; const skeletonColor = skeleton.color;
let vertexSize = twoColorTint ? 12 : 8; const vertexSize = twoColorTint ? 12 : 8;
let inRange = false; let inRange = false;
if (slotRangeStart == -1) inRange = true; if (slotRangeStart === -1) inRange = true;
for (let i = 0, n = drawOrder.length; i < n; i++) { for (let i = 0, n = drawOrder.length; i < n; i++) {
let slot = drawOrder[i]; const slot = drawOrder[i];
if (!slot.bone.active) { if (!slot.bone.active) {
clipper.clipEnd(slot); clipper.clipEnd(slot);
continue; continue;
} }
if (slotRangeStart >= 0 && slotRangeStart == slot.data.index) { if (slotRangeStart >= 0 && slotRangeStart === slot.data.index) {
inRange = true; inRange = true;
} }
@ -93,7 +93,7 @@ export class SkeletonRenderer {
continue; continue;
} }
if (slotRangeEnd >= 0 && slotRangeEnd == slot.data.index) { if (slotRangeEnd >= 0 && slotRangeEnd === slot.data.index) {
inRange = false; inRange = false;
} }
@ -107,7 +107,7 @@ export class SkeletonRenderer {
attachment.computeWorldVertices(slot, renderable.vertices, 0, vertexSize); attachment.computeWorldVertices(slot, renderable.vertices, 0, vertexSize);
triangles = SkeletonRenderer.QUAD_TRIANGLES; triangles = SkeletonRenderer.QUAD_TRIANGLES;
uvs = attachment.uvs; uvs = attachment.uvs;
texture = <GLTexture>attachment.region!.texture; texture = (attachment.region as TextureRegion).texture as GLTexture;
attachmentColor = attachment.color; attachmentColor = attachment.color;
} else if (attachment instanceof MeshAttachment) { } else if (attachment instanceof MeshAttachment) {
renderable.vertices = this.vertices; renderable.vertices = this.vertices;
@ -119,7 +119,7 @@ export class SkeletonRenderer {
} }
attachment.computeWorldVertices(skeleton, slot, 0, attachment.worldVerticesLength, renderable.vertices, 0, vertexSize); attachment.computeWorldVertices(skeleton, slot, 0, attachment.worldVerticesLength, renderable.vertices, 0, vertexSize);
triangles = attachment.triangles; triangles = attachment.triangles;
texture = <GLTexture>attachment.region!.texture; texture = (attachment.region as TextureRegion).texture as GLTexture;
uvs = attachment.uvs; uvs = attachment.uvs;
attachmentColor = attachment.color; attachmentColor = attachment.color;
} else if (attachment instanceof ClippingAttachment) { } else if (attachment instanceof ClippingAttachment) {
@ -132,8 +132,8 @@ export class SkeletonRenderer {
} }
if (texture) { if (texture) {
let slotColor = pose.color; const slotColor = pose.color;
let finalColor = this.tempColor; const finalColor = this.tempColor;
finalColor.r = skeletonColor.r * slotColor.r * attachmentColor.r; finalColor.r = skeletonColor.r * slotColor.r * attachmentColor.r;
finalColor.g = skeletonColor.g * slotColor.g * attachmentColor.g; finalColor.g = skeletonColor.g * slotColor.g * attachmentColor.g;
finalColor.b = skeletonColor.b * slotColor.b * attachmentColor.b; finalColor.b = skeletonColor.b * slotColor.b * attachmentColor.b;
@ -143,7 +143,7 @@ export class SkeletonRenderer {
finalColor.g *= finalColor.a; finalColor.g *= finalColor.a;
finalColor.b *= finalColor.a; finalColor.b *= finalColor.a;
} }
let darkColor = this.tempColor2; const darkColor = this.tempColor2;
if (!pose.darkColor) if (!pose.darkColor)
darkColor.set(0, 0, 0, 1.0); darkColor.set(0, 0, 0, 1.0);
else { else {
@ -157,19 +157,19 @@ export class SkeletonRenderer {
darkColor.a = premultipliedAlpha ? 1.0 : 0.0; darkColor.a = premultipliedAlpha ? 1.0 : 0.0;
} }
let slotBlendMode = slot.data.blendMode; const slotBlendMode = slot.data.blendMode;
if (slotBlendMode != blendMode) { if (slotBlendMode !== blendMode) {
blendMode = slotBlendMode; blendMode = slotBlendMode;
batcher.setBlendMode(blendMode, premultipliedAlpha); batcher.setBlendMode(blendMode, premultipliedAlpha);
} }
if (clipper.isClipping() && clipper.clipTriangles(renderable.vertices, triangles, triangles.length, uvs, finalColor, darkColor, twoColorTint, vertexSize)) { if (clipper.isClipping() && clipper.clipTriangles(renderable.vertices, triangles, triangles.length, uvs, finalColor, darkColor, twoColorTint, vertexSize)) {
let clippedVertices = new Float32Array(clipper.clippedVertices); const clippedVertices = new Float32Array(clipper.clippedVertices);
let clippedTriangles = clipper.clippedTriangles; const clippedTriangles = clipper.clippedTriangles;
if (transformer) transformer(clippedVertices, clippedVertices.length, vertexSize); if (transformer) transformer(clippedVertices, clippedVertices.length, vertexSize);
batcher.draw(texture, clippedVertices, clippedTriangles); batcher.draw(texture, clippedVertices, clippedTriangles);
} else { } else {
let verts = renderable.vertices; const verts = renderable.vertices;
if (!twoColorTint) { if (!twoColorTint) {
for (let v = 2, u = 0, n = renderable.numFloats; v < n; v += vertexSize, u += 2) { for (let v = 2, u = 0, n = renderable.numFloats; v < n; v += vertexSize, u += 2) {
verts[v] = finalColor.r; verts[v] = finalColor.r;
@ -193,7 +193,7 @@ export class SkeletonRenderer {
verts[v + 9] = darkColor.a; verts[v + 9] = darkColor.a;
} }
} }
let view = (renderable.vertices as Float32Array).subarray(0, renderable.numFloats); const view = (renderable.vertices as Float32Array).subarray(0, renderable.numFloats);
if (transformer) transformer(renderable.vertices, renderable.numFloats, vertexSize); if (transformer) transformer(renderable.vertices, renderable.numFloats, vertexSize);
batcher.draw(texture, view, triangles); batcher.draw(texture, view, triangles);
} }

View File

@ -27,7 +27,7 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { TimeKeeper, AssetManager, ManagedWebGLRenderingContext, SceneRenderer, Input, StringMap } from "./index.js"; import { AssetManager, Input, ManagedWebGLRenderingContext, SceneRenderer, type StringMap, TimeKeeper } from "./index.js";
/** An app running inside a {@link SpineCanvas}. The app life-cycle /** An app running inside a {@link SpineCanvas}. The app life-cycle
* is as follows: * is as follows:
@ -55,7 +55,7 @@ export interface SpineCanvasConfig {
/* The path prefix to be used by the {@link AssetManager}. */ /* The path prefix to be used by the {@link AssetManager}. */
pathPrefix?: string; pathPrefix?: string;
/* The WebGL context configuration */ /* The WebGL context configuration */
webglConfig?: any; webglConfig?: WebGLContextAttributes;
} }
/** Manages the life-cycle and WebGL context of a {@link SpineCanvasApp}. The app loads /** Manages the life-cycle and WebGL context of a {@link SpineCanvasApp}. The app loads
@ -100,7 +100,7 @@ export class SpineCanvas {
if (config.app.loadAssets) config.app.loadAssets(this); if (config.app.loadAssets) config.app.loadAssets(this);
let loop = () => { const loop = () => {
if (this.disposed) return; if (this.disposed) return;
requestAnimationFrame(loop); requestAnimationFrame(loop);
this.time.update(); this.time.update();
@ -108,7 +108,7 @@ export class SpineCanvas {
if (config.app.render) config.app.render(this); if (config.app.render) config.app.render(this);
} }
let waitForAssets = () => { const waitForAssets = () => {
if (this.disposed) return; if (this.disposed) return;
if (this.assetManager.isLoadingComplete()) { if (this.assetManager.isLoadingComplete()) {
if (this.assetManager.hasErrors()) { if (this.assetManager.hasErrors()) {

View File

@ -27,7 +27,7 @@
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/ *****************************************************************************/
import { Matrix4, M00, M01, M02, M03, M10, M11, M12, M13, M20, M21, M22, M23, M30, M31, M32, M33 } from "./Matrix4.js"; import { M00, M01, M02, M03, M10, M11, M12, M13, M20, M21, M22, M23, M30, M31, M32, M33, type Matrix4 } from "./Matrix4.js";
export class Vector3 { export class Vector3 {
x = 0; x = 0;
@ -77,7 +77,7 @@ export class Vector3 {
normalize (): Vector3 { normalize (): Vector3 {
let len = this.length(); let len = this.length();
if (len == 0) return this; if (len === 0) return this;
len = 1 / len; len = 1 / len;
this.x *= len; this.x *= len;
this.y *= len; this.y *= len;
@ -90,15 +90,15 @@ export class Vector3 {
} }
multiply (matrix: Matrix4): Vector3 { multiply (matrix: Matrix4): Vector3 {
let l_mat = matrix.values; const l_mat = matrix.values;
return this.set(this.x * l_mat[M00] + this.y * l_mat[M01] + this.z * l_mat[M02] + l_mat[M03], return this.set(this.x * l_mat[M00] + this.y * l_mat[M01] + this.z * l_mat[M02] + l_mat[M03],
this.x * l_mat[M10] + this.y * l_mat[M11] + this.z * l_mat[M12] + l_mat[M13], this.x * l_mat[M10] + this.y * l_mat[M11] + this.z * l_mat[M12] + l_mat[M13],
this.x * l_mat[M20] + this.y * l_mat[M21] + this.z * l_mat[M22] + l_mat[M23]); this.x * l_mat[M20] + this.y * l_mat[M21] + this.z * l_mat[M22] + l_mat[M23]);
} }
project (matrix: Matrix4): Vector3 { project (matrix: Matrix4): Vector3 {
let l_mat = matrix.values; const l_mat = matrix.values;
let l_w = 1 / (this.x * l_mat[M30] + this.y * l_mat[M31] + this.z * l_mat[M32] + l_mat[M33]); const l_w = 1 / (this.x * l_mat[M30] + this.y * l_mat[M31] + this.z * l_mat[M32] + l_mat[M33]);
return this.set((this.x * l_mat[M00] + this.y * l_mat[M01] + this.z * l_mat[M02] + l_mat[M03]) * l_w, return this.set((this.x * l_mat[M00] + this.y * l_mat[M01] + this.z * l_mat[M02] + l_mat[M03]) * l_w,
(this.x * l_mat[M10] + this.y * l_mat[M11] + this.z * l_mat[M12] + l_mat[M13]) * l_w, (this.x * l_mat[M10] + this.y * l_mat[M11] + this.z * l_mat[M12] + l_mat[M13]) * l_w,
(this.x * l_mat[M20] + this.y * l_mat[M21] + this.z * l_mat[M22] + l_mat[M23]) * l_w); (this.x * l_mat[M20] + this.y * l_mat[M21] + this.z * l_mat[M22] + l_mat[M23]) * l_w);
@ -113,9 +113,9 @@ export class Vector3 {
} }
distance (v: Vector3): number { distance (v: Vector3): number {
let a = v.x - this.x; const a = v.x - this.x;
let b = v.y - this.y; const b = v.y - this.y;
let c = v.z - this.z; const c = v.z - this.z;
return Math.sqrt(a * a + b * b + c * c); return Math.sqrt(a * a + b * b + c * c);
} }
} }

View File

@ -1,3 +1,4 @@
export * from "@esotericsoftware/spine-core";
export * from "./AssetManager.js"; export * from "./AssetManager.js";
export * from "./Camera.js"; export * from "./Camera.js";
export * from "./CameraController.js"; export * from "./CameraController.js";
@ -14,5 +15,4 @@ export * from "./SkeletonDebugRenderer.js";
export * from "./SkeletonRenderer.js"; export * from "./SkeletonRenderer.js";
export * from "./SpineCanvas.js"; export * from "./SpineCanvas.js";
export * from "./Vector3.js"; export * from "./Vector3.js";
export * from "./WebGL.js"; export * from "./WebGL.js";
export * from "@esotericsoftware/spine-core";